diff --git a/.drone.yml b/.drone.yml index 22fdde9af3..d36025ce89 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1,9 +1,6 @@ pipeline: build: - image: scalaplatform/jdk8-ruby2-coursier:0.1 + image: scalacenter/scala-rvm-jvm-coursier:2.0 pull: true commands: - - bundle install - - ./scripts/run-tut.sh - - rm -r tut-tmp - - bundle exec jekyll build + - ./scripts/ci.sh diff --git a/.drone.yml.sig b/.drone.yml.sig index e492c55065..8240a38b07 100644 --- a/.drone.yml.sig +++ b/.drone.yml.sig @@ -1 +1 @@ -eyJhbGciOiJIUzI1NiJ9.cGlwZWxpbmU6CiAgYnVpbGQ6CiAgICBpbWFnZTogc2NhbGFwbGF0Zm9ybS9qZGs4LXJ1YnkyLWNvdXJzaWVyOjAuMQogICAgcHVsbDogdHJ1ZQogICAgY29tbWFuZHM6CiAgICAgIC0gYnVuZGxlIGluc3RhbGwKICAgICAgLSAuL3NjcmlwdHMvcnVuLXR1dC5zaAogICAgICAtIHJtIC1yIHR1dC10bXAKICAgICAgLSBidW5kbGUgZXhlYyBqZWt5bGwgYnVpbGQK.uCV-tIDp9xbL2u2y27B9id6SL89dBfiiiTvVXYxHAbw \ No newline at end of file +eyJhbGciOiJIUzI1NiJ9.cGlwZWxpbmU6CiAgYnVpbGQ6CiAgICBpbWFnZTogamRrLXJ2bS1jb3Vyc2llcjpsYXRlc3QKICAgIHB1bGw6IHRydWUKICAgIGNvbW1hbmRzOgogICAgICAtIHNvdXJjZSB-Ly5wcm9maWxlCiAgICAgIC0gYnVuZGxlIGluc3RhbGwKICAgICAgLSAuL3NjcmlwdHMvcnVuLXR1dC5zaAogICAgICAtIHJtIC1yIHR1dC10bXAKICAgICAgLSBidW5kbGUgZXhlYyBqZWt5bGwgYnVpbGQK.7Rp37FEwRqAo85EdFYZh1PoyU8mxpFdEnpchWaQkHCc diff --git a/.gitignore b/.gitignore index 22a496be82..c73823b558 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ _site +.ruby-version .DS_Store .project .settings @@ -8,3 +9,4 @@ vendor/bundle .idea/ /coursier /tut-tmp/ +.sass-cache/ diff --git a/Gemfile b/Gemfile index 37f5eaa42e..3b040ad134 100644 --- a/Gemfile +++ b/Gemfile @@ -1,2 +1,11 @@ source 'https://rubygems.org' -gem 'github-pages', group: :jekyll_plugins +gem 'jekyll-redirect-from' + +# group :jekyll_plugins do +# gem 'hawkins' +# end + +# ^ Useful for live reloading the site in your +# browser during development. To use, uncomment +# and do: +# bundle exec jekyll liveserve --incremental diff --git a/Gemfile.lock b/Gemfile.lock index e330f8907b..61c9c6cb3d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,173 +1,34 @@ GEM remote: https://rubygems.org/ specs: - activesupport (4.2.8) - i18n (~> 0.7) - minitest (~> 5.1) - thread_safe (~> 0.3, >= 0.3.4) - tzinfo (~> 1.1) addressable (2.5.1) public_suffix (~> 2.0, >= 2.0.2) - coffee-script (2.4.1) - coffee-script-source - execjs - coffee-script-source (1.12.2) colorator (1.1.0) - ethon (0.10.1) - ffi (>= 1.3.0) - execjs (2.7.0) - faraday (0.12.1) - multipart-post (>= 1.2, < 3) ffi (1.9.18) forwardable-extended (2.6.0) - gemoji (3.0.0) - github-pages (143) - activesupport (= 4.2.8) - github-pages-health-check (= 1.3.4) - jekyll (= 3.4.5) - jekyll-avatar (= 0.4.2) - jekyll-coffeescript (= 1.0.1) - jekyll-default-layout (= 0.1.4) - jekyll-feed (= 0.9.2) - jekyll-gist (= 1.4.0) - jekyll-github-metadata (= 2.4.0) - jekyll-mentions (= 1.2.0) - jekyll-optional-front-matter (= 0.1.2) - jekyll-paginate (= 1.1.0) - jekyll-readme-index (= 0.1.0) - jekyll-redirect-from (= 0.12.1) - jekyll-relative-links (= 0.4.1) - jekyll-sass-converter (= 1.5.0) - jekyll-seo-tag (= 2.2.3) - jekyll-sitemap (= 1.0.0) - jekyll-swiss (= 0.4.0) - jekyll-theme-architect (= 0.0.4) - jekyll-theme-cayman (= 0.0.4) - jekyll-theme-dinky (= 0.0.4) - jekyll-theme-hacker (= 0.0.4) - jekyll-theme-leap-day (= 0.0.4) - jekyll-theme-merlot (= 0.0.4) - jekyll-theme-midnight (= 0.0.4) - jekyll-theme-minimal (= 0.0.4) - jekyll-theme-modernist (= 0.0.4) - jekyll-theme-primer (= 0.3.0) - jekyll-theme-slate (= 0.0.4) - jekyll-theme-tactile (= 0.0.4) - jekyll-theme-time-machine (= 0.0.4) - jekyll-titles-from-headings (= 0.2.0) - jemoji (= 0.8.0) - kramdown (= 1.13.2) - liquid (= 3.0.6) - listen (= 3.0.6) - mercenary (~> 0.3) - minima (= 2.1.1) - rouge (= 1.11.1) - terminal-table (~> 1.4) - github-pages-health-check (1.3.4) - addressable (~> 2.3) - net-dns (~> 0.8) - octokit (~> 4.0) - public_suffix (~> 2.0) - typhoeus (~> 0.7) - html-pipeline (2.6.0) - activesupport (>= 2) - nokogiri (>= 1.4) - i18n (0.8.6) - jekyll (3.4.5) + jekyll (3.5.1) addressable (~> 2.4) colorator (~> 1.0) jekyll-sass-converter (~> 1.0) jekyll-watch (~> 1.1) kramdown (~> 1.3) - liquid (~> 3.0) + liquid (~> 4.0) mercenary (~> 0.3.3) pathutil (~> 0.9) rouge (~> 1.7) safe_yaml (~> 1.0) - jekyll-avatar (0.4.2) - jekyll (~> 3.0) - jekyll-coffeescript (1.0.1) - coffee-script (~> 2.2) - jekyll-default-layout (0.1.4) - jekyll (~> 3.0) - jekyll-feed (0.9.2) - jekyll (~> 3.3) - jekyll-gist (1.4.0) - octokit (~> 4.2) - jekyll-github-metadata (2.4.0) - jekyll (~> 3.1) - octokit (~> 4.0, != 4.4.0) - jekyll-mentions (1.2.0) - activesupport (~> 4.0) - html-pipeline (~> 2.3) - jekyll (~> 3.0) - jekyll-optional-front-matter (0.1.2) - jekyll (~> 3.0) - jekyll-paginate (1.1.0) - jekyll-readme-index (0.1.0) - jekyll (~> 3.0) jekyll-redirect-from (0.12.1) jekyll (~> 3.3) - jekyll-relative-links (0.4.1) - jekyll (~> 3.3) jekyll-sass-converter (1.5.0) sass (~> 3.4) - jekyll-seo-tag (2.2.3) - jekyll (~> 3.3) - jekyll-sitemap (1.0.0) - jekyll (~> 3.3) - jekyll-swiss (0.4.0) - jekyll-theme-architect (0.0.4) - jekyll (~> 3.3) - jekyll-theme-cayman (0.0.4) - jekyll (~> 3.3) - jekyll-theme-dinky (0.0.4) - jekyll (~> 3.3) - jekyll-theme-hacker (0.0.4) - jekyll (~> 3.3) - jekyll-theme-leap-day (0.0.4) - jekyll (~> 3.3) - jekyll-theme-merlot (0.0.4) - jekyll (~> 3.3) - jekyll-theme-midnight (0.0.4) - jekyll (~> 3.3) - jekyll-theme-minimal (0.0.4) - jekyll (~> 3.3) - jekyll-theme-modernist (0.0.4) - jekyll (~> 3.3) - jekyll-theme-primer (0.3.0) - jekyll (~> 3.3) - jekyll-theme-slate (0.0.4) - jekyll (~> 3.3) - jekyll-theme-tactile (0.0.4) - jekyll (~> 3.3) - jekyll-theme-time-machine (0.0.4) - jekyll (~> 3.3) - jekyll-titles-from-headings (0.2.0) - jekyll (~> 3.3) jekyll-watch (1.5.0) listen (~> 3.0, < 3.1) - jemoji (0.8.0) - activesupport (~> 4.0) - gemoji (~> 3.0) - html-pipeline (~> 2.2) - jekyll (>= 3.0) - kramdown (1.13.2) - liquid (3.0.6) - listen (3.0.6) - rb-fsevent (>= 0.9.3) - rb-inotify (>= 0.9.7) + kramdown (1.14.0) + liquid (4.0.0) + listen (3.0.8) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) mercenary (0.3.6) - mini_portile2 (2.1.0) - minima (2.1.1) - jekyll (~> 3.3) - minitest (5.10.2) - multipart-post (2.0.0) - net-dns (0.8.0) - nokogiri (1.6.8.1) - mini_portile2 (~> 2.1.0) - octokit (4.7.0) - sawyer (~> 0.8.0, >= 0.5.3) pathutil (0.14.0) forwardable-extended (~> 2.6) public_suffix (2.0.5) @@ -181,23 +42,12 @@ GEM sass-listen (4.0.0) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) - sawyer (0.8.1) - addressable (>= 2.3.5, < 2.6) - faraday (~> 0.8, < 1.0) - terminal-table (1.8.0) - unicode-display_width (~> 1.1, >= 1.1.1) - thread_safe (0.3.6) - typhoeus (0.8.0) - ethon (>= 0.8.0) - tzinfo (1.2.3) - thread_safe (~> 0.1) - unicode-display_width (1.3.0) PLATFORMS ruby DEPENDENCIES - github-pages + jekyll-redirect-from BUNDLED WITH - 1.15.1 + 1.15.3 diff --git a/_ba/cheatsheets/index.md b/_ba/cheatsheets/index.md new file mode 100644 index 0000000000..0a7e59790f --- /dev/null +++ b/_ba/cheatsheets/index.md @@ -0,0 +1,88 @@ +--- +layout: cheatsheet +title: Scalacheat + +partof: cheatsheet + +by: Brendan O'Connor +about: Zahvaljujući Brendan O'Connoru ovaj cheatsheet teži da bude kratki pregled sintakse Scale. Licenca pripada Brendan O'Connor-u, pod CC-BY-SA 3.0 licencom. + +language: ba +--- + +###### Doprinio {{ page.by }} +{{ page.about }} + +| varijable | | +| `var x = 5` | varijabla. | +| Dobro `val x = 5`
Loše `x=6` | konstanta. | +| `var x: Double = 5` | eksplicitni tip. | +| funkcije | | +| Dobro `def f(x: Int) = { x*x }`
Loše `def f(x: Int) { x*x }` | definicija funkcije.
skrivena greška: bez `=` ovo je procedura koja vraća `Unit`; uzrokuje zabunu. | +| Dobro `def f(x: Any) = println(x)`
Loše `def f(x) = println(x)` | definicija funkcije.
sintaksna greška: potrebni su tipovi za svaki argument. | +| `type R = Double` | pseudonim za tip. | +| `def f(x: R)` ili
`def f(x: => R)` | poziv-po-vrijednosti.
poziv-po-imenu (lijeni parameteri). | +| `(x:R) => x*x` | anonimna funkcija. | +| `(1 to 5).map(_*2)` ili
`(1 to 5).reduceLeft( _+_ )` | anonimna funkcija: donja crta odgovara argumentu po poziciji. | +| `(1 to 5).map( x => x*x )` | anonimna funkcija: da bi koristili argument više od jednom, morate mu dati ime. | +| Dobro `(1 to 5).map(2*)`
Loše `(1 to 5).map(*2)` | anonimna funkcija: vezana infiksna metoda. Koristite `2*_` zbog jasnoće. | +| `(1 to 5).map { x => val y=x*2; println(y); y }` | anonimna funkcija: blokovski stil vraća vrijednost zadnjeg izraza. | +| `(1 to 5) filter {_%2 == 0} map {_*2}` | anonimne funkcije: pipeline stil (može i sa oblim zagradama). | +| `def compose(g:R=>R, h:R=>R) = (x:R) => g(h(x))`
`val f = compose({_*2}, {_-1})` | anonimne funkcije: da bi proslijedili više blokova, potrebne su dodatne zagrade. | +| `val zscore = (mean:R, sd:R) => (x:R) => (x-mean)/sd` | curry-jevanje, očita sintaksa. | +| `def zscore(mean:R, sd:R) = (x:R) => (x-mean)/sd` | curry-jevanje, očita sintaksa. | +| `def zscore(mean:R, sd:R)(x:R) = (x-mean)/sd` | curry-jevanje, sintaksni šećer (kratica). Ali onda: | +| `val normer = zscore(7, 0.4) _` | je potrebna prateća donja crta za parcijalnu primjenu, samo kod šećer (skraćene) verzije. | +| `def mapmake[T](g:T=>T)(seq: List[T]) = seq.map(g)` | generički tip. | +| `5.+(3); 5 + 3`
`(1 to 5) map (_*2)` | infiksni šećer. | +| `def sum(args: Int*) = args.reduceLeft(_+_)` | varirajući broj argumenata (varargs). | +| paketi | | +| `import scala.collection._` | džoker (wildcard) import. | +| `import scala.collection.Vector`
`import scala.collection.{Vector, Sequence}` | selektivni import. | +| `import scala.collection.{Vector => Vec28}` | preimenujući import. | +| `import java.util.{Date => _, _}` | import svega iz `java.util` paketa osim `Date`. | +| `package pkg` _na početku fajla_
`package pkg { ... }` | deklaracija paketa. | +| strukture podataka | | +| `(1,2,3)` | torka (tuple) literal (`Tuple3`). | +| `var (x,y,z) = (1,2,3)` | destrukturirajuće vezivanje: otpakivanje torke podudaranjem uzoraka (pattern matching). | +| Loše`var x,y,z = (1,2,3)` | skrivena greška: svim varijablama dodijeljena cijela torka. | +| `var xs = List(1,2,3)` | lista (nepromjenjiva). | +| `xs(2)` | indeksiranje zagradama ([slajdovi](http://www.slideshare.net/Odersky/fosdem-2009-1013261/27)). | +| `1 :: List(2,3)` | cons. | +| `1 to 5` _isto kao_ `1 until 6`
`1 to 10 by 2` | šećer za raspon (range). | +| `()` _(prazne zagrade)_ | jedina instanca Unit tipa (slično kao u C/Java void). | +| kontrolne strukture | | +| `if (check) happy else sad` | uslov. | +| `if (check) happy` _isto kao_
`if (check) happy else ()` | sintaksni šećer za uslov. | +| `while (x < 5) { println(x); x += 1}` | while petlja. | +| `do { println(x); x += 1} while (x < 5)` | do while petlja. | +| `import scala.util.control.Breaks._`
`breakable {`
` for (x <- xs) {`
` if (Math.random < 0.1) break`
` }`
`}`| break ([slajdovi](http://www.slideshare.net/Odersky/fosdem-2009-1013261/21)). | +| `for (x <- xs if x%2 == 0) yield x*10` _isto kao_
`xs.filter(_%2 == 0).map(_*10)` | for komprehensija: filter/map. | +| `for ((x,y) <- xs zip ys) yield x*y` _isto kao_
`(xs zip ys) map { case (x,y) => x*y }` | for komprehensija: destrukturirajuće vezivanje. | +| `for (x <- xs; y <- ys) yield x*y` _isto kao_
`xs flatMap {x => ys map {y => x*y}}` | for komprehensija: međuproizvod (vektorski proizvod). | +| `for (x <- xs; y <- ys) {`
`println("%d/%d = %.1f".format(x, y, x/y.toFloat))`
`}` | for komprehensija: imperativ-asto.
[sprintf-stil.](http://java.sun.com/javase/6/docs/api/java/util/Formatter.html#syntax) | +| `for (i <- 1 to 5) {`
`println(i)`
`}` | for komprehensija: iteracija uključujući gornju granicu. | +| `for (i <- 1 until 5) {`
`println(i)`
`}` | for komprehensija: iteracija ne uključujući gornju granicu. | +| podudaranje uzoraka (pattern matching) | | +| Dobro `(xs zip ys) map { case (x,y) => x*y }`
Loše `(xs zip ys) map( (x,y) => x*y )` | slučaj korištenja u argumentima funkcije. | +| Loše
`val v42 = 42`
`Some(3) match {`
` case Some(v42) => println("42")`
` case _ => println("Not 42")`
`}` | "v42" interpretira se kao ime koje odgovara bilo kojoj vrijednosti Int, i "42" se prikazuje. | +| Dobro
`val v42 = 42`
`Some(3) match {`
`` case Some(`v42`) => println("42")``
`case _ => println("Not 42")`
`}` | "\`v42\`" s kosim apostrofima interpretira se kao postojeća val `v42`, i "Not 42" se prikazuje. | +| Dobro
`val UppercaseVal = 42`
`Some(3) match {`
` case Some(UppercaseVal) => println("42")`
` case _ => println("Not 42")`
`}` | `UppercaseVal` tretira se kao postojeća val, a ne kao nova vrijednost uzorka, zato što počinje velikim slovom. Stoga, vrijednost u `UppercaseVal` se poredi sa `3`, i "Not 42" se prikazuje. | +| objektna orijentisanost | | +| `class C(x: R)` _isto kao_
`class C(private val x: R)`
`var c = new C(4)` | parameteri konstruktora - privatni. | +| `class C(val x: R)`
`var c = new C(4)`
`c.x` | parameteri konstruktora - javni. | +| `class C(var x: R) {`
`assert(x > 0, "positive please")`
`var y = x`
`val readonly = 5`
`private var secret = 1`
`def this = this(42)`
`}`|
konstruktor je tijelo klase.
deklaracija javnog člana.
deklaracija dostupnog ali nepromjenjivog člana
deklaracija privatnog člana.
alternativni konstruktor.| +| `new{ ... }` | anonimna klasa. | +| `abstract class D { ... }` | definicija apstraktne klase (ne može se kreirati). | +| `class C extends D { ... }` | definicija nasljedne klase. | +| `class D(var x: R)`
`class C(x: R) extends D(x)` | nasljeđivanje i parameteri konstruktora (lista želja: automatsko prosljeđivanje parametara...). +| `object O extends D { ... }` | definicija singletona (kao modul). | +| `trait T { ... }`
`class C extends T { ... }`
`class C extends D with T { ... }` | trejtovi.
interfejs-s-implementacijom. Bez parametara konstruktora. [Miksabilan]({{ site.baseurl }}/tutorials/tour/mixin-class-composition.html). +| `trait T1; trait T2`
`class C extends T1 with T2`
`class C extends D with T1 with T2` | više trejtova. | +| `class C extends D { override def f = ...}` | moraju se deklarisati prebrisane metode. | +| `new java.io.File("f")` | kreiranje objekta. | +| Loše `new List[Int]`
Dobro `List(1,2,3)` | greška tipa: apstraktni tip.
umjesto toga, konvencija: fabrika istoimenog tipa. | +| `classOf[String]` | literal za klasu. | +| `x.isInstanceOf[String]` | provjera tipa (runtime). | +| `x.asInstanceOf[String]` | kastovanje tipa (runtime). | +| `x: String` | askripcija (compile time). | diff --git a/_ba/tour/abstract-types.md b/_ba/tour/abstract-types.md new file mode 100644 index 0000000000..97d531910c --- /dev/null +++ b/_ba/tour/abstract-types.md @@ -0,0 +1,85 @@ +--- +layout: tour +title: Apstraktni tipovi + +discourse: false + +partof: scala-tour + +num: 22 + +language: ba + +next-page: compound-types +previous-page: inner-classes +--- + +U Scali, klase su parameterizovane vrijednostima (parameteri konstruktora) i tipovima (ako su [generičke](generic-classes.html)). +Zbog dosljednosti, ne samo da je moguće imati vrijednosti kao članove objekta već i tipove. +Nadalje, obje forme članova mogu biti konkretne ili apstraktne. +Slijedi primjer koji sadrži obje forme: apstraktnu vrijednost i apstraktni tip kao članove [klase](traits.html) `Buffer`. + + trait Buffer { + type T + val element: T + } + +*Apstraktni tipovi* su tipovi čiji identitet nije precizno definisan. +U gornjem primjeru, poznato je samo da svaki objekat klase `Buffer` ima tip-član `T`, +ali definicija klase `Buffer` ne kazuje kojem konkretno tipu odgovara `T`. +Kao i definicije vrijednosti, možemo redefinisati (override) definicije tipova u podklasama. +Ovo nam omogućuje da otkrijemo više informacija o apstraktnom tipu sužavanjem granica tipa (koje opisuju moguće konkretne instance apstraktnog tipa). + +U sljedećem programu izvodimo klasu `SeqBuffer` koja omogućuje čuvanje samo sekvenci u baferu kazivanjem da tip `T` +mora biti podtip `Seq[U]` za neki novi apstraktni tip `U`: + + abstract class SeqBuffer extends Buffer { + type U + type T <: Seq[U] + def length = element.length + } + +Trejtovi (trait) ili [klase](classes.html) s apstraktnim tip-članovima se često koriste u kombinaciji s instanciranjem anonimnih klasa. +Radi ilustracije, pogledaćemo program koji radi s sekvencijalnim baferom koji sadrži listu integera: + + 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) + } + +Povratni tip metode `newIntSeqBuf` odnosi se na specijalizaciju trejta `Buffer` u kom je tip `U` sada jednak `Int`u. +Imamo sličan alijas tip u anonimnoj instanci klase u tijelu metode `newIntSeqBuf`. +Ovdje kreiramo novu instancu `IntSeqBuffer` u kojoj se tip `T` odnosi na `List[Int]`. + +Imajte na umu da je često moguće pretvoriti apstraktni tip-član u tipski parametar klase i obrnuto. +Slijedi verzija gornjeg koda koji koristi tipske parametre: + + 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) + } + +Primijetite da moramo koristiti [anotacije za varijansu](variances.html) ovdje; +inače ne bismo mogli sakriti konkretni tip sekvencijalne implementacije objekta vraćenog iz metode `newIntSeqBuf`. +Nadalje, postoje slučajevi u kojima nije moguće zamijeniti apstraktne tipove tip parametrima. diff --git a/_ba/tour/annotations.md b/_ba/tour/annotations.md new file mode 100644 index 0000000000..27da913fd4 --- /dev/null +++ b/_ba/tour/annotations.md @@ -0,0 +1,134 @@ +--- +layout: tour +title: Anotacije + +discourse: false + +partof: scala-tour + +num: 31 + +language: ba + +next-page: default-parameter-values +previous-page: case-classes +--- + +Anotacije pridružuju meta-informacije definicijama. + +Jednostavna anotacija ima formu `@C` ili `@C(a1, .., an)`. Ovdje je `C` konstruktor klase `C`, koja mora naslijediti klasu `scala.Annotation`. +Svi argumenti konstruktora `a1, .., an` moraju biti konstante (npr. izrazi ili numeričke primitive, stringovi, primitive klasa, +Java enumeracije i jednodimenzionalni nizovi (`Array`) navedenih). + +Anotacijska klauza (ili više njih) primjenjuje se na prvu definiciju ili deklaraciju koja slijedi nakon nje. +Redoslijed anotacijskih klauza nije bitan. + +Značenje anotacijskih klauza je _implementacijski-nezavisno_. Na Java platformi, sljedeće Scala anotacije imaju standardno značenje. + +| Scala | Java | +| ------ | ------ | +| [`scala.SerialVersionUID`](https://www.scala-lang.org/api/current/scala/SerialVersionUID.html) | [`serialVersionUID`](http://java.sun.com/j2se/1.5.0/docs/api/java/io/Serializable.html#navbar_bottom) (polje) | +| [`scala.deprecated`](https://www.scala-lang.org/api/current/scala/deprecated.html) | [`java.lang.Deprecated`](http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Deprecated.html) | +| [`scala.inline`](https://www.scala-lang.org/api/current/scala/inline.html) (od 2.6.0) | nema ekvivalent | +| [`scala.native`](https://www.scala-lang.org/api/current/scala/native.html) (od 2.6.0) | [`native`](http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_ključna riječs.html) (ključna riječ) | +| [`scala.remote`](https://www.scala-lang.org/api/current/scala/remote.html) | [`java.rmi.Remote`](http://java.sun.com/j2se/1.5.0/docs/api/java/rmi/Remote.html) | +| [`scala.throws`](https://www.scala-lang.org/api/current/scala/throws.html) | [`throws`](http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html) (ključna riječ) | +| [`scala.transient`](https://www.scala-lang.org/api/current/scala/transient.html) | [`transient`](http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html) (ključna riječ) | +| [`scala.unchecked`](https://www.scala-lang.org/api/current/scala/unchecked.html) (od 2.4.0) | nema ekvivalent | +| [`scala.volatile`](https://www.scala-lang.org/api/current/scala/volatile.html) | [`volatile`](http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html) (ključna riječ) | +| [`scala.beans.BeanProperty`](https://www.scala-lang.org/api/current/scala/beans/BeanProperty.html) | [`Dizajn patern`](http://docs.oracle.com/javase/tutorial/javabeans/writing/properties.html) | + +U sljedećem primjeru dodajemo `throws` anotaciju definiciji metode `read` da bi uhvatili izuzetak (exception) u Java main programu. + +> Java kompajler provjerava da li program sadrži rukovatelje (handler) za [provjereni izuzetak (checked exception)](http://docs.oracle.com/javase/specs/jls/se5.0/html/exceptions.html) +analizom koji se izuzeci mogu dobiti izvršenjem neke metode ili konstruktora. +Za svaki mogući provjereni izuzetak **throws** klauza metodi ili konstruktora _mora_ navesti klasu izuzetka ili neku nadklasu izuzetka. +> Pošto Scala nema provjerene izuzetke, Scala metode _moraju_ imati jednu ili više `throws` anotacija kako bi Java kod mogao uhvatiti iste. + + package examples + import java.io._ + class Reader(fname: String) { + private val in = new BufferedReader(new FileReader(fname)) + @throws(classOf[IOException]) + def read() = in.read() + } + +Sljedeći Java program prikazuje sadržaj fajla s imenom koje je proslijeđeno kao prvi argument `main` metode. + + package test; + import examples.Reader; // Scala klasa !! + 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()); + } + } + } + +Kada bi zakomentarisali `throws` anotaciju u klasi `Reader` dobili bi sljedeću grešku s porukom pri kompajliranju Java main programa: + + Main.java:11: exception java.io.IOException is never thrown in body of + corresponding try statement + } catch (java.io.IOException e) { + ^ + 1 error + +### Java anotacije ### + +**Napomena:** Pobrinite se da koristite `-target:jvm-1.5` opciju sa Java anotacijama. + +Java 1.5 je uvela korisnički definisane metapodatke u formi [anotacija](http://java.sun.com/j2se/1.5.0/docs/guide/language/annotations.html). Ključna sposobnost anotacija je da koriste parove ime-vrijednost za inicijalizaciju svojih elemenata. Naprimjer, ako nam treba anotacija da pratimo izvor neke klase mogli bismo je definisati kao + + @interface Source { + public String URL(); + public String mail(); + } + +I upotrijebiti je kao npr.: + + @Source(URL = "http://coders.com/", + mail = "support@coders.com") + public class MyClass extends HisClass ... + +Primjena anotacije u Scali izgleda kao poziv konstruktora, dok se za instanciranje Javinih anotacija moraju koristiti imenovani argumenti: + + @Source(URL = "http://coders.com/", + mail = "support@coders.com") + class MyScalaClass ... + +Ova sintaksa je ponekad naporna, npr. ako anotacija ima samo jedan element (bez podrazumijevane vrijednosti), pa po konvenciji, +ako se koristi naziv `value` onda se u Javi može koristiti i konstruktor-sintaksa: + + @interface SourceURL { + public String value(); + public String mail() default ""; + } + +I upotrijebiti je kao npr.: + + @SourceURL("http://coders.com/") + public class MyClass extends HisClass ... + +U ovom slučaju, Scala omogućuje istu sintaksu: + + @SourceURL("http://coders.com/") + class MyScalaClass ... + +Element `mail` je specificiran s podrazumijevanom vrijednošću tako da ne moramo eksplicitno navoditi vrijednost za njega. +Međutim, ako trebamo, ne možemo miješati dva Javina stila: + + @SourceURL(value = "http://coders.com/", + mail = "support@coders.com") + public class MyClass extends HisClass ... + +Dok u Scali možemo: + + @SourceURL("http://coders.com/", + mail = "support@coders.com") + class MyScalaClass ... diff --git a/_ba/tour/anonymous-function-syntax.md b/_ba/tour/anonymous-function-syntax.md new file mode 100644 index 0000000000..649fafe17f --- /dev/null +++ b/_ba/tour/anonymous-function-syntax.md @@ -0,0 +1,45 @@ +--- +layout: tour +title: Sintaksa anonimnih funkcija + +discourse: false + +partof: scala-tour + +num: 6 + +language: ba + +next-page: higher-order-functions +previous-page: mixin-class-composition +--- + +Scala omogućuje relativno lahku sintaksu za definisanje anonimnih funkcija. Sljedeći izraz kreira funkciju za sljedbenike cijelih brojeva: + + (x: Int) => x + 1 + +Ovo je kratica za definiciju sljedeće anonimne klase: + + new Function1[Int, Int] { + def apply(x: Int): Int = x + 1 + } + +Također je moguće definisati funkciju s više parametara: + + (x: Int, y: Int) => "(" + x + ", " + y + ")" + +ili bez parametara: + + () => { System.getProperty("user.dir") } + +Također postoji vrlo lahka sintaksa za pisanje tipa funkcije. Ovo su tipovi tri gore navedene funkcije: + + Int => Int + (Int, Int) => String + () => String + +Ova sintaksa je kratica za sljedeće tipove: + + Function1[Int, Int] + Function2[Int, Int, String] + Function0[String] diff --git a/_ba/tour/automatic-closures.md b/_ba/tour/automatic-closures.md new file mode 100644 index 0000000000..251b826950 --- /dev/null +++ b/_ba/tour/automatic-closures.md @@ -0,0 +1,83 @@ +--- +layout: tour +title: Automatska konstrukcija tipno zavisnih closura (zatvarajućih funkcija) + +discourse: false + +partof: scala-tour + +num: 30 + +language: ba + +next-page: case-classes +previous-page: operators +--- + +Scala dozvoljava da se argument (ili više njih) metode ne evaluira prije samog poziva metode. +Kada se takva metoda pozove, dati parametar se ne evaluira odmah već se na svakom mjestu u metodi gdje se poziva ubacuje +besparametarska funkcija koja enkapsulira izračunavanje odgovarajućeg parametra (tzv. *poziv-po-imenu* (call-by-name) evaluacija). + +Sljedeći kod demonstrira ovaj mehanizam: + + 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 + } + } + +Funkcija `whileLoop` prima dva parametra, `cond` i `body`. Kada se izvršava tijelo funkcije, stvarni parametri se ne evaluiraju. +Ali svaki put kada se formalni parametri koriste u tijelu `whileLoop`, implicitno kreirana besparametarska funkcija će biti evaluirana. +Stoga, naša metoda `whileLoop` implementira Java-stu while petlju u rekurzivnom stilu. + +Možemo kombinirati upotrebu [infiksnih/postfiksnih operatora](operators.html) s ovim mehanizmom da bi kreirali +komplikovanije naredbe (s lijepom sintaksom). + +Slijedi implementacija loop-unless naredbe: + + 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) + } + +Funkcija `loop` prima samo tijelo i vraća instancu klase `LoopUnlessCond` (koja enkapsulira objekat tijela). +Primijetite da tijelo još uvijek nije evaluirano. +Klasa `LoopUnlessCond` ima metodu `unless` koju možemo koristiti kao *infiksni operator*. +Na ovaj način postižemo prilično prirodnu sintaksu za našu novu petlju: `loop { < stats > } unless ( < cond > )`. + +Ovo je rezultat izvršenja `TargetTest2`: + + i = 10 + i = 9 + i = 8 + i = 7 + i = 6 + i = 5 + i = 4 + i = 3 + i = 2 + i = 1 + diff --git a/_ba/tour/case-classes.md b/_ba/tour/case-classes.md new file mode 100644 index 0000000000..bac4d64e2b --- /dev/null +++ b/_ba/tour/case-classes.md @@ -0,0 +1,93 @@ +--- +layout: tour +title: Case klase + +discourse: false + +partof: scala-tour + +num: 30 + +language: ba + +next-page: annotations +previous-page: automatic-closures +--- + +Scala podržava tzv. _case klase_. +Case klase su obične klase koje eksponiraju svoje parametre konstruktora i +omogućuju rekurzivnu dekompoziciju pomoću [podudaranja uzorka (pattern matching)](pattern-matching.html). + +Slijedi primjer hijerarhije klasa koja se sastoji od apstraktne nadklase `Term` i tri konkretne case klase `Var`, `Fun`, i `App`. + + abstract class Term + case class Var(name: String) extends Term + case class Fun(arg: String, body: Term) extends Term + case class App(f: Term, v: Term) extends Term + +Ova hijerarhija klasa može biti korištena da predstavi pojmove iz [bestipskog lambda računa](https://en.wikipedia.org/wiki/Lambda_calculus). +Da bi se olakšala konstrukcija instanci case klasa, Scala ne zahtijeva `new` primitivu. Može se jednostavno koristiti ime klase kao funkcija. + +Primjer: + + Fun("x", Fun("y", App(Var("x"), Var("y")))) + +Parametri konstruktora case klasa tretiraju se kao javne (public) vrijednosti i može im se pristupiti direktno. + + val x = Var("x") + println(x.name) + +Za svaku case klasu Scala kompajler izgeneriše `equals` metodu koja implementira strukturalnu jednakost i `toString` metodu. Naprimjer: + + val x1 = Var("x") + val x2 = Var("x") + val y1 = Var("y") + println("" + x1 + " == " + x2 + " => " + (x1 == x2)) + println("" + x1 + " == " + y1 + " => " + (x1 == y1)) + +će prikazati + + Var(x) == Var(x) => true + Var(x) == Var(y) => false + +Ima smisla definisati case klasu samo ako će biti korištena sa podudaranjem uzorka za dekompoziciju. +Sljedeći [objekt](singleton-objects.html) definiše funkciju za lijepo ispisivanje naše reprezentacije lambda računa: + + object TermTest extends scala.App { + + def printTerm(term: Term) { + term match { + case Var(n) => + print(n) + case Fun(x, b) => + print("^" + x + ".") + printTerm(b) + case App(f, v) => + print("(") + printTerm(f) + print(" ") + printTerm(v) + print(")") + } + } + + def isIdentityFun(term: Term): Boolean = term match { + case Fun(x, Var(y)) if x == y => true + case _ => false + } + + val id = Fun("x", Var("x")) + val t = Fun("x", Fun("y", App(Var("x"), Var("y")))) + + printTerm(t) + println + println(isIdentityFun(id)) + println(isIdentityFun(t)) + } + +U našem primjeru, funkcija `printTerm` je izražena kao naredba podudaranja uzorka s `match` ključnom riječi +i sastoji se od niza `case Pattern => Body` klauza. +Gornji program također definiše funkciju `isIdentityFun` koja provjerava da li dati pojam odgovara jednostavnoj funkciji identiteta. +Ovaj primjer koristi duboke uzorke i čuvare (guard). +Nakon što se uzorak podudari s datom vrijednošću, čuvar (definisan nakon ključne riječi `if`) se evaluira. +Ako vrati `true`, podudaranje uspijeva; u suprotnom, ne uspijeva i sljedeći uzorak će biti pokušan. diff --git a/_ba/tour/classes.md b/_ba/tour/classes.md new file mode 100644 index 0000000000..2e7f4f973c --- /dev/null +++ b/_ba/tour/classes.md @@ -0,0 +1,61 @@ +--- +layout: tour +title: Klase + +discourse: false + +partof: scala-tour + +num: 3 + +language: ba + +next-page: traits +previous-page: unified-types +--- + +Klase u Scali su statički šabloni koji mogu biti instancirani u više objekata tokom izvršavanja programa (runtime). +Slijedi definicija klase `Point`: + + class Point(xc: Int, yc: Int) { + + var x: Int = xc + var y: Int = yc + + def move(dx: Int, dy: Int) { + x = x + dx + y = y + dy + } + + override def toString(): String = "(" + x + ", " + y + ")"; + } + +Ova klasa definiše dvije varijable: `x` i `y`, i dvije metode: `move` i `toString`. +Metoda `move` prima dva cjelobrojna argumenta ali ne vraća vrijednost (implicitni povratni tip je `Unit`, +koji odgovoara `void`-u u jezicima sličnim Javi). `toString`, za razliku, ne prima nikakve parametre ali vraća `String` vrijednost. +Pošto `toString` prebrisava predefinisanu `toString` metodu, mora biti tagovana s `override`. + +Klase u Scali se parametrizuju parametrima konstruktora. Kod iznad definiše dva parametra konstruktora, `xc` i `yc`; +oba su vidljiva u cijelom tijelu klase. U našem primjeru korišteni su za inicijalizaciju varijabli `x` i `y`. + +Klase se inicijalizaciju pomoću `new` primitive, kao u sljedećem primjeru: + + object Classes { + def main(args: Array[String]) { + val pt = new Point(1, 2) + println(pt) + pt.move(10, 10) + println(pt) + } + } + +Program definiše izvršnu aplikaciju `Classes` u form vrhovnog singlton objekta s `main` metodom. +Metoda `main` kreira novu instancu klase `Point` i sprema je u vrijednost `pt`. +_Imajte u vidu da se vrijednosti definisane primitivom `val` razlikuju +od varijabli definisanih primitivom `var` (vidi klasu `Point` iznad) +u tome da ne dozvoljavaju promjenu vrijednosti; tj. vrijednost je konstanta._ + +Ovo je rezultat programa: + + (1, 2) + (11, 12) diff --git a/_ba/tour/compound-types.md b/_ba/tour/compound-types.md new file mode 100644 index 0000000000..6a682eafa0 --- /dev/null +++ b/_ba/tour/compound-types.md @@ -0,0 +1,54 @@ +--- +layout: tour +title: Složeni tipovi + +discourse: false + +partof: scala-tour + +num: 23 + +language: ba + +next-page: explicitly-typed-self-references +previous-page: abstract-types +--- + +Ponekad je potrebno izraziti da je tip objekta podtip nekoliko drugih tipova. +U Scali ovo može biti izraženo pomoću *složenih tipova*, koji su presjeci tipova objekata. + +Pretpostavimo da imamo dva trejta: `Cloneable` i `Resetable`: + + trait Cloneable extends java.lang.Cloneable { + override def clone(): Cloneable = { + super.clone().asInstanceOf[Cloneable] + } + } + trait Resetable { + def reset: Unit + } + +Pretpostavimo da želimo napisati funkciju `cloneAndReset` koja prima objekt, klonira ga i resetuje originalni objekt: + + def cloneAndReset(obj: ?): Cloneable = { + val cloned = obj.clone() + obj.reset + cloned + } + +Postavlja se pitanje koji bi trebao biti tip parametra `obj`. +Ako je `Cloneable` onda objekt može biti `clone`-iran, ali ne i `reset`-ovan; +ako je `Resetable` onda se može `reset`, ali ne i `clone`. +Da bi izbjegli kastovanje tipa u ovoj situaciji, možemo navesti tip `obj` da bude oboje `Cloneable` i `Resetable`. +Ovaj složeni tip u Scali se piše kao: `Cloneable with Resetable`. + +Ovo je ažurirana funkcija: + + def cloneAndReset(obj: Cloneable with Resetable): Cloneable = { + //... + } + +Složeni tipovi mogu se sastojati od više tipova i mogu imati jednu rafinaciju koja može biti korištena da suzi potpis postojećih članova objekta. +General forma je: `A with B with C ... { refinement }` + +Primjer za upotrebu rafinacije dat je na stranici o [apstraktnim tipovima](abstract-types.html). diff --git a/_ba/tour/currying.md b/_ba/tour/currying.md new file mode 100644 index 0000000000..43f0a35893 --- /dev/null +++ b/_ba/tour/currying.md @@ -0,0 +1,43 @@ +--- +layout: tour +title: Curry-jevanje + +discourse: false + +partof: scala-tour + +num: 9 + +language: ba + +next-page: pattern-matching +previous-page: nested-functions +--- + +Metode mogu definisati više lista parametara. +Kada je metoda pozvana s manje listi parametara nego što ima, +onda će to vratiti funkciju koja prima preostale liste parametara kao argumente. + +Primjer: + + 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))) + } + +_Napomena: metoda `modN` je parcijalno primijenjena u dva poziva `filter`; tj. samo prvi argument je ustvari primijenjen. +Izraz `modN(2)` vraća funkciju tipa `Int => Boolean` i zato je mogući kandidat za drugi argument funkcije `filter`._ + +Rezultat gornjeg programa: + + List(2,4,6,8) + List(3,6) diff --git a/_ba/tour/default-parameter-values.md b/_ba/tour/default-parameter-values.md new file mode 100644 index 0000000000..6e1d3cf2bd --- /dev/null +++ b/_ba/tour/default-parameter-values.md @@ -0,0 +1,76 @@ +--- +layout: tour +title: Podrazumijevane vrijednosti parametara + +discourse: false + +partof: scala-tour + +num: 32 + +language: ba + +next-page: named-parameters +previous-page: annotations +--- + +Scala omogućuje davanje podrazumijevanih vrijednosti parametrima koje dozvoljavaju korisniku metode da izostavi te parametre. + +U Javi, postoje mnoge preopterećene (overloaded) metode koje samo služe da bi obezbijedile podrazumijevane vrijednosti za neke parametre velike metode. +Ovo je posebno tačno kod konstruktora: + + public class HashMap { + public HashMap(Map m); + /** Kreiraj novu HashMap s podrazumijevanim kapacitetom (16) + * i loadFactor-om (0.75) + */ + public HashMap(); + + /** Kreiraj novu HashMap s podrazumijevanim loadFactor-om (0.75) */ + public HashMap(int initialCapacity); + public HashMap(int initialCapacity, float loadFactor); + } + +Ovdje postoje samo dva konstruktora ustvari; jedan koji prima drugu mapu, i jedan koji prima kapacitet i faktor opterećenja. +Treći i četvrti konstruktor su tu samo da dozvole korisnicima HashMap-e da kreiraju instance s vjerovatno-dobrim podrazumijevanim parametrima +za kapacitet i faktor opterećenja. + +Problematičnije je to da su podrazumijevane vrijednosti i u Javadoc-u *i* u kodu. +Održavanje ovih vrijednosti sinhronizovanim se lahko zaboravi. +Tipičan način zaobilaženja ovog problema je dodavanje javnih konstanti čije vrijednosti se pojavljuju u Javadoc: + + public class HashMap { + public static final int DEFAULT_CAPACITY = 16; + public static final float DEFAULT_LOAD_FACTOR = 0.75; + + public HashMap(Map m); + /** Kreiraj novu HashMap s podrazumijevanim kapacitetom (16) + * i loadFactor-om (0.75) + */ + public HashMap(); + /** Kreiraj novu HashMap s podrazumijevanim loadFactor-om (0.75) */ + public HashMap(int initialCapacity); + public HashMap(int initialCapacity, float loadFactor); + } + +Ovaj pristup umanjuje ponavljanje koda, ali je manje ekspresivan. + +Scala ima direktnu podršku za ovaj problem: + + class HashMap[K,V](initialCapacity:Int = 16, loadFactor:Float = 0.75f) { + } + + // Koristi podrazumijevane vrijednosti + val m1 = new HashMap[String,Int] + + // initialCapacity 20, podrazumijevani loadFactor + val m2 = new HashMap[String,Int](20) + + // prosljeđivanje obje vrijednosti + val m3 = new HashMap[String,Int](20, 0.8f) + + // prosljeđivanje samo loadFactor preko + // imenovanih parametara + val m4 = new HashMap[String,Int](loadFactor = 0.8f) + +Ovako možemo iskoristiti prednost *bilo koje* podrazumijevane vrijednosti korištenjem [imenovanih parametara]({{ site.baseurl }}/tutorials/tour/named-parameters.html). diff --git a/_ba/tour/explicitly-typed-self-references.md b/_ba/tour/explicitly-typed-self-references.md new file mode 100644 index 0000000000..12cfffcff3 --- /dev/null +++ b/_ba/tour/explicitly-typed-self-references.md @@ -0,0 +1,127 @@ +--- +layout: tour +title: Eksplicitno tipizirane samo-reference + +discourse: false + +partof: scala-tour + +num: 24 + +language: ba + +next-page: implicit-parameters +previous-page: compound-types +--- + +Kada se razvija proširiv softver ponekad je zgodno deklarisati tip vrijednosti `this` eksplicitno. +Za motivaciju, izvešćemo malu proširivu reprezentaciju strukture grafa u Scali. + +Slijedi definicija koja opisuje grafove: + + 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 + } + +Grafovi se sastoje od liste čvorova i ivica gdje su oba tipa ostavljena apstraktnim. +Upotreba [apstraktnih tipova](abstract-types.html) dozvoljava da implementacije trejta `Graph` obezbijede svoje konkretne klase za čvorove i ivice. +Nadalje, tu je metoda `addNode` za dodavanje novih čvorova u graf. Čvorovi se povezuju metodom `connectWith`. + +Moguća implementacija klase `Graph` data je u sljedećoj klasi: + + 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` je specijalizacija klase `Graph`, predstavlja parcijalnu implementaciju. +Implementacija je parcijalna jer želimo da proširimo klasu `DirectedGraph` dalje. +Zato ova klasa ostavlja implementacijske detalje otvorenim pa su tipovi čvora i ivice ostavljeni apstraktnim. +Klasa `DirectedGraph` otkriva neke dodatne detalje o implementaciji tipa čvora ograničavanjem ga klasom `EdgeImpl`. +Nadalje, imamo neke preliminarne implementacije ivica i čvorova u klasama `EdgeImpl` i `NodeImpl`. +Pošto je potrebno kreirati nove čvorove i ivice u našoj parcijalnoj implementaciji grafa, +moramo dodati i tvorničke (factory) metode `newNode` i `newEdge`. +Metode `addNode` i `connectWith` su definisane pomoću ovih tvorničkih metoda. +Pažljiviji pogled na implementaciju metode `connectWith` otkriva da za kreiranje ivice, +moramo proslijediti samo-referencu (self-reference) `this` tvorničkoj metodi `newEdge`. +Ali, `this` ima tip `NodeImpl`, tako da nije kompatibilan s tipom `Node` kojeg zahtijeva odgovarajuća tvornička metoda. +Kao posljedica, gornji program nije dobro formiran i Scala kompajler će javiti grešku. + +U Scali je moguće vezati klasu za neki drugi tip (koji će biti implementiran kasnije) +davanjem drugog tipa samo-referenci `this`. +Ovaj mehanizam možemo iskoristiti u gornjem kodu. +Eksplicitna samo-referenca je specificirana u tijelu klase `DirectedGraph`. + +Ovo je popravljeni program: + + abstract class DirectedGraph extends Graph { + ... + class NodeImpl extends NodeIntf { + self: Node => + def connectWith(node: Node): Edge = { + val edge = newEdge(this, node) // now legal + edges = edge :: edges + edge + } + } + ... + } + +U novoj definiciji klase `NodeImpl`, `this` (self) ima tip `Node`. Pošto je tip `Node` apstraktan i još uvijek ne znamo da li je `NodeImpl` +podtip od `Node`, sistem tipova Scale nam neće dozvoliti instanciranje ove klase. +Kako god, eksplicitnom anotacijom iskazali smo da podklasa `NodeImpl` +mora navesti podtip tipa `Node` da bi se mogla instancirati. + +Ovo je konkretna specijalizacija klase `DirectedGraph` u kojoj su svi apstraktni članovi klase sada konkretni: + + 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) + } + +Primijetite da u ovoj klasi možemo instancirati `NodeImpl` jer znamo da je `NodeImpl` +podtip tipa `Node` (koja je samo pseudonim za `NodeImpl`). + +Primjer korištenja klase `ConcreteDirectedGraph`: + + 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/_ba/tour/extractor-objects.md b/_ba/tour/extractor-objects.md new file mode 100644 index 0000000000..3b701d13ea --- /dev/null +++ b/_ba/tour/extractor-objects.md @@ -0,0 +1,57 @@ +--- +layout: tour +title: Ekstraktor objekti + +discourse: false + +partof: scala-tour + +num: 15 + +language: ba + +next-page: sequence-comprehensions +previous-page: regular-expression-patterns +--- + +U Scali, uzorci (patterns) mogu biti definisani nezavisno od case klasa. +Za ovo se koristi metoda `unapply` koja vraća tzv. ekstraktor. +Ekstraktor se može smatrati kao specijalna metoda koja ima obrnuti efekt od primjene (apply) nekog objekta na neke ulazne parametre. +Svrha mu je da 'ekstraktuje' ulazne parametre koji su bili prisutni prije 'apply' operacije. +Naprimjer, sljedeći kod definiše ekstraktor [objekt](singleton-objects.html) Twice. + + 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) } // prints 21 + } + +Navedene su dvije sintaksne konvencije: + +Uzorak `case Twice(n)` će pozvati `Twice.unapply`, koja se koristi da ekstraktuje bilo koji paran broj; +povratna vrijednost od `unapply` signalizira da li se argument podudario s uzorkom ili ne, +i bilo koje pod-vrijednosti mogu biti korištene za daljnje uzorkovanje (matching). +Ovdje je pod-vrijednost `z/2`. + +Metoda `apply` nije obavezna za uzorkovanje. Ona se jedino koristi da imitira konstructor. +`val x = Twice(21)` se proširuje na `val x = Twice.apply(21)`. + +Povratni tip od `unapply` se bira na sljedeći način: + +* Ako je samo test, vraća `Boolean`. Naprimjer `case even()` +* Ako vraća jednu pod-vrijednost tipa, vraća `Option[T]` +* Ako vraća više pod-vrijednosti `T1,...,Tn`, groupiše ih u opcionu torku `Option[(T1,...,Tn)]`. + +Ponekad, broj pod-vrijednosti nije fiksan i želimo da vratimo listu. +Iz ovog razloga, također možete definisati uzorke pomoću `unapplySeq`. +Zadnja pod-vrijednost tipa `Tn` mora biti `Seq[S]`. +Ovaj mehanizam se koristi naprimjer za uzorak `case List(x1, ..., xn)`. + +Ekstraktori čine kod lakšim za održavanje. +Za više detalja, pročitajte dokument +["Matching Objects with Patterns"](https://infoscience.epfl.ch/record/98468/files/MatchingObjectsWithPatterns-TR.pdf) +(u sekciji 4) od Emir, Odersky i Williams (Januar 2007). diff --git a/_ba/tour/generic-classes.md b/_ba/tour/generic-classes.md new file mode 100644 index 0000000000..9399d391bd --- /dev/null +++ b/_ba/tour/generic-classes.md @@ -0,0 +1,52 @@ +--- +layout: tour +title: Generičke klase + +discourse: false + +partof: scala-tour + +num: 17 + +language: ba + +next-page: variances +previous-page: sequence-comprehensions +--- + +Kao u Javi 5 ([JDK 1.5](http://java.sun.com/j2se/1.5/)), Scala ima ugrađenu podršku za klase parametrizovane tipovima. +Takve klase su vrlo korisne za implementiranje kolekcija. +Ovo je primjer koji to demonstrira: + + 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 imperativne (promjenjive) stekove elemenata proizvoljnog tipa `T`. +Parametri tipova obezbjeđuju sigurnost da se samo legalni elementi (samo tipa `T`) guraju na stek. +Slično, s tipskim parametrima možemo izraziti da metoda `top` vraća samo elemente zadanog tipa. + +Ovo su neki primjeri korištenja: + + object GenericsTest extends App { + val stack = new Stack[Int] + stack.push(1) + stack.push('a') + println(stack.top) + stack.pop() + println(stack.top) + } + +Izlaz ovog programa je: + + 97 + 1 + +_Napomena: nasljeđivanje generičkih tipova je *invarijantno*. +Ovo znači da ako imamo stek karaktera, koji ima tip `Stack[Char]` onda on ne može biti korišten kao stek cijelih brojeva tipa `Stack[Int]`. +Ovo bi bilo netačno (unsound) jer bi onda mogli stavljati i integere na stek karaktera. +Zaključimo, `Stack[T]` je podtip `Stack[S]` ako i samo ako je `S = T`. +Pošto ovo može biti prilično ograničavajuće, Scala ima i [anotacije tipskih parametara](variances.html) za kontrolisanje ponašanja podtipova generičkih tipova._ diff --git a/_ba/tour/higher-order-functions.md b/_ba/tour/higher-order-functions.md new file mode 100644 index 0000000000..2eef4bc5a1 --- /dev/null +++ b/_ba/tour/higher-order-functions.md @@ -0,0 +1,43 @@ +--- +layout: tour +title: Funkcije višeg reda + +discourse: false + +partof: scala-tour + +num: 7 + +language: ba + +next-page: nested-functions +previous-page: anonymous-function-syntax +--- + +Scala dozvoljava definisanje funkcija višeg reda. +To su funkcije koje _primaju druge funkcije kao parametre_, ili čiji je _rezultat funkcija_. +Ovo je funkcija `apply` koja uzima drugu funkciju `f` i vrijednost `v` i primjenjuje funkciju `f` na `v`: + + def apply(f: Int => String, v: Int) = f(v) + +_Napomena: metode se automatski pretvoraju u funkcije ako kontekst zahtijeva korištenje this._ + +Ovo je još jedan primjer: + + 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)) + } + +Izvršenjem se dobije izlaz: + + [7] + +U ovom primjeru, metoda `decorator.layout` je automatski pretvorena u vrijednost tipa `Int => String` koju zahtijeva metoda `apply`. +Primijetite da je metoda `decorator.layout` _polimorfna metoda_ (tj. apstrahuje neke tipove u svom potpisu) +i Scala kompajler mora prvo instancirati tipove metode. diff --git a/_ba/tour/implicit-conversions.md b/_ba/tour/implicit-conversions.md new file mode 100644 index 0000000000..39212f99a0 --- /dev/null +++ b/_ba/tour/implicit-conversions.md @@ -0,0 +1,51 @@ +--- +layout: tour +title: Implicitne konverzije + +discourse: false + +partof: scala-tour + +num: 26 + +language: ba + +next-page: polymorphic-methods +previous-page: implicit-parameters +--- + +Implicitna konverzija iz tipa `S` u tip `T` je definisana kao implicitna vrijednost koja ima tip `S => T` (funkcija), +ili kao implicitna metoda koja može pretvoriti u očekivani tip `T`. + +Implicitne konverzije se primjenjuju u dvije situacije: + +* Ako je izraz `e` tipa `S`, i `S` ne odgovara očekivanom tipu `T`. +* U selekciji `e.m` gdje je `e` tipa `T`, ako selektor `m` nije član tipa `T`. + +U prvom slučaju, traži se konverzija `c` koja je primjenjiva na `e` i čiji rezultat odgovara `T`. +U drugom slučaju, traži se konverzija `c` koja je primjenjiva na `e` i čiji rezultat sadrži član pod imenom `m`. + +Sljedeća operacija nad dvije liste xs i ys tipa `List[Int]` je legalna: + + xs <= ys + +pod pretpostavkom da su implicitne metode `list2ordered` i `int2ordered` definisane i dostupne (in scope): + + 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] { /* .. */ } + +Implicitno importovani objekt `scala.Predef` deklariše nekoliko predefinisanih tipova (npr. `Pair`) i metoda (npr. `assert`) ali i nekoliko implicitnih konverzija. + +Naprimjer, kada se pozivaju Javine metode koje očekuju `java.lang.Integer`, možete proslijediti `scala.Int`. +Možete, zato što `Predef` uključuje slj. implicitnu konverziju: + + implicit def int2Integer(x: Int) = + java.lang.Integer.valueOf(x) + +Da bi definisali vlastite implicitne konverzije, morate importovati `scala.language.implicitConversions` +(ili uključiti kompajler s flegom `-language:implicitConversions`). +Ova osobina mora biti korištena eksplicitno jer ima potencijalne zamke ako se koristi neselektivno. diff --git a/_ba/tour/implicit-parameters.md b/_ba/tour/implicit-parameters.md new file mode 100644 index 0000000000..12439f74d1 --- /dev/null +++ b/_ba/tour/implicit-parameters.md @@ -0,0 +1,55 @@ +--- +layout: tour +title: Implicitni parametri + +discourse: false + +partof: scala-tour + +num: 25 + +language: ba + +next-page: implicit-conversions +previous-page: explicitly-typed-self-references +--- + +Metoda s _implicitnim parametrima_ može biti primijenjena na argumente kao i normalna metoda. +U ovom slučaju, implicitna labela nema nikakav efekt. +Međutim, ako takvoj metodi nedostaju argumenti za implicitne parametre, ti argumenti će biti proslijeđeni automatski. + +Argumenti koji se mogu proslijediti kao implicitni parametri spadaju u dvije kategorije: + +* Prva, kvalifikovani su svi identifikatori x koji su dostupni pri pozivu metode bez prefiksa i predstavljaju implicitnu definiciju ili implicitni parameter. +* Druga, kvalifikovani su također svi članovi prijateljskih objekata (modula) tipova implicitnih parametara. + +U sljedećem primjeru definisaćemo metodu `sum` koja izračunava sumu liste elemenata koristeći `add` i `unit` operacije monoida. +Molimo primijetite da implicitne vrijednosti ne mogu biti top-level, već moraju biti članovi templejta. + + abstract class SemiGroup[A] { + def add(x: A, y: A): A + } + abstract class Monoid[A] extends SemiGroup[A] { + def unit: A + } + object ImplicitTest extends App { + 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 + } + def sum[A](xs: List[A])(implicit m: Monoid[A]): A = + if (xs.isEmpty) m.unit + else m.add(xs.head, sum(xs.tail)) + + println(sum(List(1, 2, 3))) + println(sum(List("a", "b", "c"))) + } + +Ovo je izlaz navedenog Scala programa: + + 6 + abc diff --git a/_ba/tour/inner-classes.md b/_ba/tour/inner-classes.md new file mode 100644 index 0000000000..a860deec8c --- /dev/null +++ b/_ba/tour/inner-classes.md @@ -0,0 +1,102 @@ +--- +layout: tour +title: Unutarnje klase + +discourse: false + +partof: scala-tour + +num: 21 + +language: ba + +next-page: abstract-types +previous-page: lower-type-bounds +--- + +U Scali je moguće da klase imaju druge klase kao članove. +Nasuprot jezicima sličnim Javi, gdje su unutarnje klase članovi vanjske klase, +u Scali takve unutarnje klase su vezane za vanjski objekt. +Radi ilustracije razlike, prikazaćemo implementaciju klase grafa: + + 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 + } + } + +U našem programu, grafovi su predstavljeni listom čvorova. +Čvorovi su objekti unutarnje klase `Node`. +Svaki čvor ima listu susjeda, koji se smještaju u listu `connectedNodes`. +Sada kad možemo kreirati graf s nekim čvorovima i povezati čvorove inkrementalno: + + 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) + } + +Sada obogaćujemo gornji primjer s tipovima s eksplicitno napisanim tipovima: + + 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) + } + +Ovaj kod jasno pokazuje da tip čvora ima prefiks instance vanjskog objekta (`g` u našem primjeru). +Ako sada imamo dva grafa, Scalin sistem tipova neće dozvoliti miješanje čvorova definisanih u različitim grafovima, +jer čvorovi različitih grafova imaju različit tip. +Ovo je primjer netačnog programa: + + object IllegalGraphTest extends App { + val g: Graph = new Graph + val n1: g.Node = g.newNode + val n2: g.Node = g.newNode + n1.connectTo(n2) // može + val h: Graph = new Graph + val n3: h.Node = h.newNode + n1.connectTo(n3) // ne može! + } + +Primijetite da bi u Javi zadnja linija prethodnog primjera bila tačna. +Za čvorove oba grafa, Java bi dodijelila isti tip `Graph.Node`; npr. `Node` bi imala prefiks klase `Graph`. +U Scali takav tip je također moguće izraziti, piše se kao `Graph#Node`. +Ako želimo povezati čvorove različitih grafova, moramo promijeniti definiciju naše inicijalne implementacije grafa: + + 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 + } + } + +> Primijetite da ovaj program ne dozvoljava da dodamo čvor u dva različita grafa. +Ako bi htjeli ukloniti i ovo ograničenje, moramo promijeniti tipski parametar `nodes` u `Graph#Node`. diff --git a/_ba/tour/local-type-inference.md b/_ba/tour/local-type-inference.md new file mode 100644 index 0000000000..aa2c46665a --- /dev/null +++ b/_ba/tour/local-type-inference.md @@ -0,0 +1,62 @@ +--- +layout: tour +title: Lokalno zaključivanje tipova (type inference) + +discourse: false + +partof: scala-tour + +num: 28 + +language: ba + +next-page: operators +previous-page: polymorphic-methods +--- +Scala ima ugrađen mehanizam zaključivanja tipova koji dozvoljava programeru da izostavi određene anotacije tipova. +Često nije potrebno specificirati tip varijable u Scali, +jer kompajler može sam zaključiti tip iz inicijalizacijskog izraza varijable. +Povratni tipovi metoda također mogu biti izostavljeni jer oni odgovaraju tipu tijela (zadnji izraz u tijelu), koje kompajler sam zaključi. + +Slijedi jedan primjer: + + object InferenceTest1 extends App { + val x = 1 + 2 * 3 // tip x-a je Int + val y = x.toString() // tip y-a je String + def succ(x: Int) = x + 1 // metoda succ vraća Int + } + +Za rekurzivne metode, kompajler nije u mogućnosti da zaključi tip rezultata. +Ovo je program koji se ne može kompajlirati iz ovog razloga: + + object InferenceTest2 { + def fac(n: Int) = if (n == 0) 1 else n * fac(n - 1) + } + +Također nije obavezno specificirati tipske parametre kada se pozivaju [polimorfne metode](polymorphic-methods.html) +ili kada se [generičke klase](generic-classes.html) instanciraju. +Scala kompajler će zaključiti nedostajuće tipske parametre iz konteksta i iz tipova stvarnih parametara metoda/konstruktora. + +Ovo je primjer koji to ilustrira: + + case class MyPair[A, B](x: A, y: B); + object InferenceTest3 extends App { + def id[T](x: T) = x + val p = MyPair(1, "scala") // tip: MyPair[Int, String] + val q = id(1) // tip: Int + } + +Zadnje dvije linije ovog programa su ekvivalentne sljedećem kodu gdje su svi zaključeni tipovi eksplicitno napisani: + + val x: MyPair[Int, String] = MyPair[Int, String](1, "scala") + val y: Int = id[Int](1) + +U nekim situacijama može biti vrlo opasno osloniti se na Scalin mehanizam zaključivanja tipova: + + object InferenceTest4 { + var obj = null + obj = new Object() + } + +Ovaj program se ne može kompajlirati jer je zaključeni tip varijable `obj` tip `Null`. +Pošto je jedina vrijednost tog tipa `null`, nemoguće je dodijeliti ovoj varijabli neku drugu vrijednost. diff --git a/_ba/tour/lower-type-bounds.md b/_ba/tour/lower-type-bounds.md new file mode 100644 index 0000000000..91714c6c0c --- /dev/null +++ b/_ba/tour/lower-type-bounds.md @@ -0,0 +1,62 @@ +--- +layout: tour +title: Donja granica tipa + +discourse: false + +partof: scala-tour + +num: 20 + +language: ba + +next-page: inner-classes +previous-page: upper-type-bounds +--- + +Dok [gornja granica tipa](upper-type-bounds.html) limitira tip na podtip nekog drugog tipa, +*donja granica tipa* limitira tip da bude nadtip nekog drugog tipa. +Izraz `T >: A` izražava tipski parametar `T` ili apstraktni tip `T` koji je nadtip tipa `A`. + +Kroz slj. primjer vidjećemo zašto je ovo korisno: + + 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) + } + +Gornji program implementira povezanu listu s operacijom nadovezivanja (na početak liste). +Nažalost, ovaj tip je invarijantan u tipskom parametru `T`, klase `ListNode`; +tj. `ListNode[String]` nije podtip `ListNode[Any]`. +Pomoću [anotacija varijansi](variances.html) možemo izraziti navedeno: + + case class ListNode[+T](h: T, t: ListNode[T]) { ... } + +Nažalost, ovaj program se ne može kompajlirati, jer anotacija za kovarijansu je jedino moguća ako se varijabla tipa koristi na kovarijantnoj poziciji (u ovom slučaju kao povratni tip). +Pošto je varijabla tipa `T` tipski parametar metode `prepend`, ovo pravilo je prekršeno. +Pomoću *donje granice tipa*, možemo implementirati operaciju nadovezivanja gdje se `T` pojavljuje samo u kovarijantnoj poziciji. + +Ovo je odgovarajući kod: + + 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) + } + +_Napomena:_ nova `prepend` metoda ima manje restriktivan tip. +Ona dozvoljava, naprimjer, da se nadoveže objekt nadtipa na postojeću listu. +Rezultujuća lista biće lista ovog nadtipa. + +Ovo je kod koji to ilustrira: + + 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/_ba/tour/mixin-class-composition.md b/_ba/tour/mixin-class-composition.md new file mode 100644 index 0000000000..379d464211 --- /dev/null +++ b/_ba/tour/mixin-class-composition.md @@ -0,0 +1,59 @@ +--- +layout: tour +title: Kompozicija mixin klasa + +discourse: false + +partof: scala-tour + +num: 5 + +language: ba + +next-page: anonymous-function-syntax +previous-page: traits +--- + +Nasuprot jezicima koji podržavaju samo _jednostruko nasljeđivanje_, Scala ima generalniji pojam ponovne upotrebe klasa. +Scala omogućuje ponovno korištenje _novih definicija članova klase_ (tj. razlika u odnosu na nadklasu) u definiciji nove klase. +Ovo se izražava _kompozicijom mixin-klasa_. +Razmotrimo apstrakciju za iteratore. + + abstract class AbsIterator { + type T + def hasNext: Boolean + def next: T + } + +Dalje, razmotrimo mixin klasu koja nasljeđuje `AbsIterator` s metodom `foreach` koja primjenjuje datu funkciju na svaki element iteratora. +Da bi definisali klasu koja može biti korištena kao mixin koristimo ključnu riječ `trait` (en. osobina, svojstvo). + + trait RichIterator extends AbsIterator { + def foreach(f: T => Unit) { while (hasNext) f(next) } + } + +Ovo je konkretna klasa iteratora, koja vraća sukcesivne karaktere datog stringa: + + 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 } + } + +Željeli bismo iskombinirati funkcionalnosti `StringIterator`-a i `RichIterator`-a u jednoj klasi. +S jednostrukim nasljeđivanjem i interfejsima ovo je nemoguće, jer obje klase sadrže implementacije članova. +Scala nam pomaže s _kompozicijom mixin-klasa_. +Ona dozvoljava programerima da ponovo iskoriste razliku definicija klasa, tj., sve nove definicije koje nisu naslijeđene. +Ovaj mehanizam omogućuje kombiniranje `StringIterator`-a s `RichIterator`-om, kao u sljedećem test programu koji ispisuje kolonu svih karaktera datog stringa. + + object StringIteratorTest { + def main(args: Array[String]) { + class Iter extends StringIterator(args(0)) with RichIterator + val iter = new Iter + iter foreach println + } + } + +Klasa `Iter` u funkciji `main` je konstruisana mixin kompozicijom roditelja `StringIterator` i `RichIterator` ključnom riječju `with`. +Prvi roditelj se zove _nadklasa_ `Iter`-a, a drugi (i svaki sljedeći, ako postoji) roditelj se zove _mixin_. diff --git a/_ba/tour/named-parameters.md b/_ba/tour/named-parameters.md new file mode 100644 index 0000000000..0690d22a48 --- /dev/null +++ b/_ba/tour/named-parameters.md @@ -0,0 +1,39 @@ +--- +layout: tour +title: Imenovani parametri + +discourse: false + +partof: scala-tour + +num: 33 + +language: ba + +previous-page: default-parameter-values +--- + +Kada se pozivaju metode i funkcije, možete koristiti imena varijabli eksplicitno pri pozivu: + + def printName(first: String, last: String) = { + println(first + " " + last) + } + + printName("John", "Smith") + // ispisuje "John Smith" + printName(first = "John", last = "Smith") + // ispisuje "John Smith" + printName(last = "Smith", first = "John") + // ispisuje "John Smith" + +Primijetite da kada koristite imenovane parametre pri pozivu, redoslijed nije bitan, dok god su svi parametri imenovani. +Ova sposobnost Scale radi vrlo dobro u paru sa [podrazumijevanim parametrima]({{ site.baseurl }}/tutorials/tour/default-parameter-values.html): + + def printName(first: String = "John", last: String = "Smith") = { + println(first + " " + last) + } + + printName(last = "Jones") + // ispisuje "John Jones" + +Pošto parametre možete navesti u bilo kom redoslijedu, možete koristiti podrazumijevane vrijednosti za parametre koji su zadnji u listi parametara. diff --git a/_ba/tour/nested-functions.md b/_ba/tour/nested-functions.md new file mode 100644 index 0000000000..b9a7b81755 --- /dev/null +++ b/_ba/tour/nested-functions.md @@ -0,0 +1,35 @@ +--- +layout: tour +title: Ugniježdene funkcije + +discourse: false + +partof: scala-tour + +num: 8 + +language: ba + +next-page: currying +previous-page: higher-order-functions +--- + +U Scali je moguće ugnježdavati definicije funkcija. +Sljedeći objekt sadrži funkciju `filter` za dobijanje vrijednosti iz liste cijelih brojeva koji su manji od vrijednosti praga (threshold): + + 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)) + } + +_Napomena: ugniježdena funkcija `process` koristi i varijablu `threshold` definisanu u vanjskom području (scope) kao parametar funkcije `filter`._ + +Izlaz ovog programa je: + + List(1,2,3,4) diff --git a/_ba/tour/operators.md b/_ba/tour/operators.md new file mode 100644 index 0000000000..82f4b04110 --- /dev/null +++ b/_ba/tour/operators.md @@ -0,0 +1,38 @@ +--- +layout: tour +title: Operatori + +discourse: false + +partof: scala-tour + +num: 29 + +language: ba + +next-page: automatic-closures +previous-page: local-type-inference +--- + +Bilo koja metoda koja prima samo jedan parametar može biti korištena kao *infiksni operator* u Scali. +Slijedi definicija klase `MyBool` koja definiše tri metode `and`, `or`, i `negate`. + + 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 = new MyBool(!x) + } + +Sada je moguće koristiti `and` i `or` kao infiksne operatore: + + def not(x: MyBool) = x negate; // tačka-zarez je obavezna ovdje + def xor(x: MyBool, y: MyBool) = (x or y) and not(x and y) + +Prva linija navedenog koda pokazuje da je također moguće koristiti besparametarske metode kao postfiksne operatore. +Druga linija definiše funkciju `xor` koristeći `and` i `or` metode kao i novu `not` funkciju. +U ovom primjeru korištenje _infiksnih operatora_ pomaže da definiciju `xor`-a učinimo čitljivijom. + +Ovo je primjer sintakse tradicionalnih objektno orijenitsanih programskih jezika: + + def not(x: MyBool) = x.negate; // tačka-zarez je obavezna ovdje + def xor(x: MyBool, y: MyBool) = x.or(y).and(x.and(y).negate) diff --git a/_ba/tour/pattern-matching.md b/_ba/tour/pattern-matching.md new file mode 100644 index 0000000000..e9364697e4 --- /dev/null +++ b/_ba/tour/pattern-matching.md @@ -0,0 +1,50 @@ +--- +layout: tour +title: Podudaranje uzoraka (pattern matching) + +discourse: false + +partof: scala-tour + +num: 11 + + +language: ba + +next-page: singleton-objects +previous-page: currying +--- + +Scala ima ugrađen mehanizam generalnog podudaranja uzoraka. +On omogućuje da se podudaraju uzorci bilo koje vrste podataka politikom "prvo podudaranje". +Slijedi mali primjer koji pokazuje kako podudarati vrijednost cijelog broja: + + object MatchTest1 extends App { + def matchTest(x: Int): String = x match { + case 1 => "one" + case 2 => "two" + case _ => "many" + } + println(matchTest(3)) + } + +Blok s `case` izrazima definiše funkciju koja mapira cijele brojeve u stringove. +Ključna riječ `match` omogućuje pogodan način za primjenu funkcije (kao pattern matching funkcija iznad) na objekt. + +Ovo je drugi primjer koja podudara vrijednost s uzorcima različitih tipova: + + 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")) + } + +Prvi `case` se podudara ako je `x` cijeli broj `1`. +Drugi `case` se podudara ako je `x` jednak stringu `"two"`. +Treći slučaj se sastoji od tipskog uzorka; podudara se sa bilo kojim integerom i povezuje vrijednost selektora `x` s varijablom `y` tipa integer. + +Scalin mehanizam podudaranja uzoraka je najkorisniji za algebarske tipove koji su izraženi kroz [case klase](case-classes.html). +Scala također dozvoljava definisanje uzoraka nezavisno od case klasa, koristeći `unapply` metode u [ekstraktor objektima](extractor-objects.html). diff --git a/_ba/tour/polymorphic-methods.md b/_ba/tour/polymorphic-methods.md new file mode 100644 index 0000000000..81fbf42b3c --- /dev/null +++ b/_ba/tour/polymorphic-methods.md @@ -0,0 +1,36 @@ +--- +layout: tour +title: Polimorfne metode + +discourse: false + +partof: scala-tour + +num: 27 + + +language: ba + +next-page: local-type-inference +previous-page: implicit-conversions +--- + +Metode u Scali mogu biti parametrizovane i s vrijednostima i s tipovima. +Kao i na nivou klase, parameteri vrijednosti su ograđeni parom zagrada, dok su tipski parameteri deklarisani u paru uglatih zagrada. + +Slijedi primjer: + + 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` je parametrizovana tipom `T` i vrijednostima parametara `x: T` i `n: Int`. +Pri prvom pozivu `dup`, programer navodi sve zahtijevane parametre, ali kako vidimo u sljedećoj liniji, +programer ne mora eksplicitno navesti tipske parametre. +Scalin sistem tipova može zaključiti takve tipove sam. +Scalin kompajler ovo postiže gledanjem tipova vrijednosti datih parametara i konteksta u kojem je metoda pozvana. diff --git a/_ba/tour/regular-expression-patterns.md b/_ba/tour/regular-expression-patterns.md new file mode 100644 index 0000000000..6d2a68d3ef --- /dev/null +++ b/_ba/tour/regular-expression-patterns.md @@ -0,0 +1,51 @@ +--- +layout: tour +title: Regularni izrazi + +discourse: false + +partof: scala-tour + +num: 14 + + +language: ba + +next-page: extractor-objects +previous-page: singleton-objects +--- + +## Desno-ignorišući uzorci sekvenci ## + +Desno-ignorišući uzorci su korisna opcija za dekompoziciju bilo kojeg podatka koji je ili podtip `Seq[A]` +ili case klasa s ponavljajućim formalnim parametrima (`Node*` u primjeru), naprimjer: + + Elem(prefix: String, label: String, attrs: MetaData, scp: NamespaceBinding, children: Node*) + +U takvim slučajevima, Scala dozvoljava uzorke koji imaju zvjezdicu `_*` na najdesnijoj poziciji, predstavljajući sekvencu bilo koje dužine. +Sljedeći primjer demonstrira uzorak koji se podudara s prefiksom sekvence i povezuje ostatak s varijablom `rest`. + + 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 + } + } + } + +Za razliku od prijašnjih verzija Scale, više nije dozvoljeno imati bilo kakve regularne izraze, iz razloga navedenih ispod. + +### Generalni `RegExp` uzorci privremeno povučeni iz Scale ### + +Pošto smo otkrili problem tačnosti, ova opcija je privremeno povučena iz Scala jezika. +Ako korisnici budu zahtijevali, moguće je da ćemo je ponovo aktivirati u poboljšanoj formi. + +Naše mišljenje je da uzorci za regularne izraze nisu bili toliko korisni za procesiranje XML-a kako smo mislili. +U stvarnim aplikacijama za procesiranje XML-a, XPath se čini kao bolja opcija. +Kada smo otkrili da naš prevod uzoraka regularnih izraza ima greške kod ezoteričnih uzoraka koji su neobični ali nezamjenjivi, +odlučili smo da je vrijeme da pojednostavimo jezik. diff --git a/_ba/tour/sequence-comprehensions.md b/_ba/tour/sequence-comprehensions.md new file mode 100644 index 0000000000..169a0cbe8e --- /dev/null +++ b/_ba/tour/sequence-comprehensions.md @@ -0,0 +1,73 @@ +--- +layout: tour +title: Komprehensije sekvenci + +discourse: false + +partof: scala-tour + +num: 16 + +language: ba + +next-page: generic-classes +previous-page: extractor-objects +--- + +Scala ima skraćenu notaciju za pisanje *komprehensija sekvenci*. +Komprehensije imaju oblik +`for (enumeratori) yield e`, gdje su `enumeratori` lista enumeratora razdvojenih tačka-zarezima. +*Enumerator* je ili generator koji uvodi nove varijable, ili je filter. +Komprehensija evaluira tijelo `e` za svako vezivanje varijable generisano od strane enumeratora i vraća sekvencu ovih vrijednosti. + +Slijedi primjer: + + 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)) + } + +For-izraz u funkciji uvodi novu varijablu `i` tipa `Int` koja se u svakoj iteraciji vezuje za vrijednost iz liste `List(from, from + 1, ..., to - 1)`. +Čuvar (guard) `if i % 2 == 0` izbacuje sve neparne brojeve tako da se tijelo (koje se sastoji samo od izraza `i`) evaluira samo za parne brojeve. +Stoga, cijeli for-izraz vraća listu parnih brojeva. + +Program ispisuje sljedeće: + + List(0, 2, 4, 6, 8, 10, 12, 14, 16, 18) + +Slijedi komplikovaniji primjer koji izračunava sve parove brojeva između `0` i `n-1` čija je suma jednaka zadanoj vrijednosti `v`: + + object ComprehensionTest2 extends App { + def foo(n: Int, v: Int) = + for (i <- 0 until n; + j <- i until n if i + j == v) yield + Pair(i, j); + foo(20, 32) foreach { + case (i, j) => + println("(" + i + ", " + j + ")") + } + } + +Ovaj primjer pokazuje da komprehensije nisu ograničene samo na liste. +Prethodni program koristi iteratore (a ne liste). +Svaki tip podatka koji podržava operacije `withFilter`, `map`, i `flatMap` (s odgovarajućim tipovima) može biti korišten u komprehensijama. + +Ovo je izlaz programa: + + (13, 19) + (14, 18) + (15, 17) + (16, 16) + +Također postoji poseban oblik za komprehensije sekvenci koje vraćaju `Unit`. +Ovdje se vrijednosti koje se uzimaju iz liste generatora i filtera koriste za popratne pojave (side-effects). +Programer mora izostaviti ključnu riječ `yield` da bi koristio takve komprehensije. +Slijedi program koji je ekvivalentan prethodnom ali koristi specijalnu for komprehensiju koja vraća `Unit`: + + object ComprehensionTest3 extends App { + for (i <- Iterator.range(0, 20); + j <- Iterator.range(i, 20) if i + j == 32) + println("(" + i + ", " + j + ")") + } + diff --git a/_ba/tour/singleton-objects.md b/_ba/tour/singleton-objects.md new file mode 100644 index 0000000000..9874cd887b --- /dev/null +++ b/_ba/tour/singleton-objects.md @@ -0,0 +1,90 @@ +--- +layout: tour +title: Singlton objekti + +discourse: false + +partof: scala-tour + +num: 12 + + +language: ba + +next-page: regular-expression-patterns +previous-page: pattern-matching +--- + +Metode i vrijednosti koje ne pripadaju individualnim instancama [klase](classes.html) pripadaju *singlton objektima*, +označenim ključnom riječju `object` umjesto `class`. + + package test + + object Blah { + def sum(l: List[Int]): Int = l.sum + } + +Metoda `sum` je dostupna globalno, i može se pozvati, ili importovati, kao `test.Blah.sum`. + +Singlton objekt je ustvari kratica za definisanje jednokratne klase, koja ne može biti direktno instancirana, +i ima `val` član `object`, s istim imenom. +Kao i `val`, singlton objekti mogu biti definisani kao članovi [trejta](traits.html) ili klase, iako je ovo netipično. + +Singlton objekt može naslijediti klase i trejtove. +Ustvari, [case klasa](case-classes.html) bez [tipskih parametara](generic-classes.html) +će podrazumijevano kreirati singlton objekt s istim imenom, +i implementiranim [`Function*`](http://www.scala-lang.org/api/current/scala/Function1.html) trejtom. + +## Kompanjoni (prijatelji) ## + +Većina singlton objekata nisu samostalni, već su povezani s istoimenom klasom. +“Singlton objekt istog imena” case klase, pomenut ranije, je jedan primjer ovoga. +U ovom slučaju, singlton objekt se zove *kompanjon objekt* klase, a klasa se zove *kompanjon klasa* objekta. + +[Scaladoc](https://wiki.scala-lang.org/display/SW/Introduction) ima posebnu podršku za prebacivanje između klase i njenog kompanjona: +ako krug s velikim “C” ili “O” ima savijenu ivicu (kao papir), možete kliknuti na krug da pređete na kompanjon. + +Klasa i njen kompanjon objekt, ako ga ima, moraju biti definisani u istom izvornom fajlu: + + 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)) + } + +Često vidimo typeclass (jedan od dizajn paterna) instance kao [implicitne vrijednosti](implicit-parameters.html), kao navedeni `ipord`, +definisane u kompanjonu. +Ovo je pogodno jer se i članovi kompanjona uključuju u implicitnu pretragu za potrebnim vrijednostima. + +## Napomene Java programerima ## + +`static` nije ključna riječ u Scali. +Umjesto nje, svi članovi koji bi u Javi bili statički, uključujući i klase, trebaju ići u neki singlton objekt. +Pristupa im se istom sintaksom, importovanim posebno ili grupno. + +Java programeri nekada definišu statičke članove privatnim kao pomoćne vrijednosti/funkcije. +Iz ovoga proizilazi čest šablon kojim se importuju svi članovi kompanjon objekta u klasu: + + class X { + import X._ + + def blah = foo + } + + object X { + private def foo = 42 + } + +U kontekstu ključne riječi `private`, klasa i njen kompanjon su prijatelji. +`object X` može pristupiti privatnim članovima od `class X`, i obrnuto. +Da bi član bio *zaista* privatan, koristite `private[this]`. + +Za pogodno korištenje u Javi, metode, uključujući `var` i `val` vrijednosti, definisane direktno u singlton objektu +također imaju statičke metode u kompanjon klasi, zvane *statički prosljeđivači*. +Drugi članovi su dostupni kroz `X$.MODULE$` statička polja za `object X`. + +Ako prebacite sve u kompanjon objekt i klasa ostane prazna koju ne želite instancirati, samo obrišite klasu. +Statički prosljeđivači će biti kreirani svakako. diff --git a/_ba/tour/tour-of-scala.md b/_ba/tour/tour-of-scala.md new file mode 100644 index 0000000000..2557e3956f --- /dev/null +++ b/_ba/tour/tour-of-scala.md @@ -0,0 +1,79 @@ +--- +layout: tour +title: Uvod + +discourse: false + +partof: scala-tour + +num: 1 + +language: ba + +next-page: unified-types +--- + +Scala je moderan programski jezik koji spaja više paradigmi, +dizajniran da izrazi česte programske šablone kroz precizan, elegantan i tipski bezbjedan način. +Scala elegantno objedinjuje mogućnosti objektno orijentisanih i funkcionalnih jezika. + +## Scala je objektno orijentisana ## +Scala je čisto objektno orijentisan jezik u smislu da je [svaka vrijednost objekt](unified-types.html). +Tipovi i ponašanja objekata se opisuju kroz [klase](classes.html) i [trejtove](traits.html). +Klase se proširuju nasljeđivanjem i fleksibilnim mehanizmom [kompozicije mixina](mixin-class-composition.html) +kao čistom zamjenom za višestruko nasljeđivanje. + +## Scala je funkcionalna ## +Scala je također funkcionalni jezik u smislu da je [svaka funkcija vrijednost](unified-types.html). +Scala ima [lahku sintaksu](anonymous-function-syntax.html) za definisanje anonimnih funkcija, +i podržava [funkcije višeg reda](higher-order-functions.html), omogućuje [ugnježdavanje funkcija](nested-functions.html), +i podržava [curry-jevanje](currying.html). +Scaline [case klase](case-classes.html) i njen mehanizam [podudaranja uzoraka](pattern-matching.html) modeluju algebarske tipove +koji se koriste u dosta funkcionalnih programskih jezika. +[Singlton objekti](singleton-objects.html) omogućuju pogodan način za grupisanje funkcija koje nisu članovi klase. + +Nadalje, Scalin mehanizam podudaranja uzoraka (pattern-matching) prirodno podržava [procesiranje XML podataka](xml-processing.html) +pomoću [desno-ignorišućih uzoraka sekvenci](regular-expression-patterns.html), +i generalnim proširivanjem s [ekstraktor objektima](extractor-objects.html). +U ovom kontekstu, [komprehensije sekvenci](sequence-comprehensions.html) su korisne za izražavanje upita (query). +Ove mogućnosti čine Scalu idealnom za razvijanje aplikacija kao što su web servisi. + +## Scala je statički tipizirana (statically typed) ## +Scala je opremljena ekspresivnim sistemom tipova koji primorava da se apstrakcije koriste na bezbjedan i smislen način. +Konkretno, sistem tipova podržava sljedeće: + +* [generičke klase](generic-classes.html) +* [anotacije varijanse](variances.html) +* [gornje](upper-type-bounds.html) i [donje](lower-type-bounds.html) granice tipa, +* [unutarnje klase](inner-classes.html) i [apstraktne tipove](abstract-types.html) kao članove objekta +* [složene tipove](compound-types.html) +* [eksplicitno tipizirane samo-reference](explicitly-typed-self-references.html) +* implicitne [parametre](implicit-parameters.html) i [konverzije](implicit-conversions.html) +* [polimorfne metode](polymorphic-methods.html) + +Mehanizam za [lokalno zaključivanje tipova](local-type-inference.html) se brine da korisnik ne mora pisati tipove varijabli +više nego što je potrebno. +U kombinaciji, ove mogućnosti su jaka podloga za bezbjedno ponovno iskorištenje programskih apstrakcija +i za tipski bezbjedno proširenje softvera. + +## Scala je proširiva ## + +U praksi, razvijanje domenski specifičnih aplikacija često zahtijeva i domenski specifične ekstenzije jezika. +Scala omogućuje jedinstvenu kombinaciju mehanizama jezika koji olakšavaju elegantno dodavanje novih +jezičkih konstrukcija u formi biblioteka: + +* bilo koja metoda se može koristiti kao [infiksni ili postfiksni operator](operators.html) +* [closure se prave automatski zavisno od očekivanog tipa](automatic-closures.html) (ciljno tipiziranje). + +Zajedničkom upotrebom obje mogućnosti olakšava definisanje novih izraza bez proširenja sintakse samog Scala jezika i bez +korištenja olakšica u vidu macro-a ili meta-programiranja. + +Scala je dizajnirana za interoperabilnost s popularnim Java 2 Runtime Environment (JRE). +Konkretno, interakcija s popularnim objektno orijentisanim Java programskim jezikom je prirodna. +Novije mogućnosti Jave kao [anotacije](annotations.html) i Javini generički tipovi imaju direktnu analogiju u Scali. +Scaline mogućnosti bez analogija u Javi, kao što su [podrazumijevani](default-parameter-values.html) i [imenovani parametri](named-parameters.html), +se kompajliraju što približnije Javi. +Scala ima isti kompilacijski model (posebno kompajliranje, dinamičko učitavanje klasa) +kao Java i time omogućuje pristupanje hiljadama postojećih visoko kvalitetnih biblioteka. + +Molimo nastavite sa sljedećom stranicom za više informacija. diff --git a/_ba/tour/traits.md b/_ba/tour/traits.md new file mode 100644 index 0000000000..fa286e2714 --- /dev/null +++ b/_ba/tour/traits.md @@ -0,0 +1,55 @@ +--- +layout: tour +title: Trejtovi + +discourse: false + +partof: scala-tour + +num: 4 + +language: ba + +next-page: mixin-class-composition +previous-page: classes +--- + +Slično Javinim interfejsima, trejtovi se koriste za definisanje tipova objekata navođenjem potpisa podržanih metoda. +Kao u Javi 8, Scala dozvoljava trejtovima da budu parcijalno implementirani; +tj. moguće je definisati podrazumijevane implementacije nekih metoda. +Nasuprot klasama, trejtovi ne mogu imati parametre konstruktora. +Slijedi primjer: + + trait Similarity { + def isSimilar(x: Any): Boolean + def isNotSimilar(x: Any): Boolean = !isSimilar(x) + } + +Ovaj trejt se sastoji od dvije metode, `isSimilar` i `isNotSimilar`. +Dok metoda `isSimilar` nema konkretnu implementaciju (u Java terminologiji, apstraktna je), +metoda `isNotSimilar` definiše konkretnu implementaciju. +Klase koje integrišu ovaj trejt moraju obezbijediti samo implementaciju za `isSimilar`. +Ponašanje metode `isNotSimilar` se direktno nasljeđuje iz trejta. +Trejtovi se obično integrišu u [klase](classes.html) (ili druge trejtove) [kompozicijom mixin klasa](mixin-class-composition.html): + + 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) + println(p1.isNotSimilar(p2)) + println(p1.isNotSimilar(p3)) + println(p1.isNotSimilar(2)) + } + +Ovo je izlaz programa: + + false + true + true diff --git a/_ba/tour/unified-types.md b/_ba/tour/unified-types.md new file mode 100644 index 0000000000..d5dc8f7a9a --- /dev/null +++ b/_ba/tour/unified-types.md @@ -0,0 +1,61 @@ +--- +layout: tour +title: Sjedinjeni tipovi + +discourse: false + +partof: scala-tour + +num: 2 + +language: ba + +next-page: classes +previous-page: tour-of-scala +--- + +Nasuprot Javi, sve vrijednosti u Scali su objekti (uključujući brojeve i funkcije). +Pošto je Scala bazirana na klasama, sve vrijednosti su instance neke klase. +Dijagram ispod prikazuje hijerarhiju Scala klasa. + +![Scala Type Hierarchy]({{ site.baseurl }}/resources/images/classhierarchy.img_assist_custom.png) + +## Hijerarhija klasa u Scali ## + +Nadklasa svih klasa, `scala.Any`, ima dvije direktne podklase, `scala.AnyVal` i `scala.AnyRef`, koje predstavljaju dva različita svijeta klasa: +klase za vrijednosti i klase za reference. +Sve klase za vrijednosti su predefinisane; one odgovaraju primitivnim tipovima u jezicima kao Java. +Sve ostale klase definišu referencne tipove. +Korisnički definisane klase definišu referencne tipove po defaultu; tj. uvijek (indirektno) nasljeđuju `scala.AnyRef`. +Svaka korisnični definisana klasa u Scali implicitno nasljeđuje trejt `scala.ScalaObject`. +Klase iz infrastrukture na kojoj se izvršava Scala (tj. JRE) ne nasljeđuju `scala.ScalaObject`. +Ako se Scala koristi u kontekstu JRE, onda `scala.AnyRef` odgovara klasi `java.lang.Object`. +Primijetite da gornji dijagram također prikazuje i implicitne konverzije (isprekidana crta) između vrijednosnih (value) klasa. +Slijedi primjer koji pokazuje da su i brojevi, i karakteri, boolean vrijednosti, i funkcije samo objekti kao i svaki drugi: + + object UnifiedTypes extends App { + val set = new scala.collection.mutable.LinkedHashSet[Any] + set += "Ovo je string" // dodaj string + set += 732 // dodaj number + set += 'c' // dodaj character + set += true // dodaj boolean + set += main _ // dodaj main funkciju + val iter: Iterator[Any] = set.iterator + while (iter.hasNext) { + println(iter.next.toString()) + } + } + +Ovaj program deklariše aplikaciju `UnifiedTypes` kao top-level [singlton objekt](singleton-objects.html) koji nasljeđuje `App`. +Aplikacija definiše lokalnu varijablu `set` koja se odnosi na instancu klase `LinkedHashSet[Any]`. +Program dodaje različite elemente u ovaj skup. +Elementi moraju odgovarati deklarisanom tipu elementa skupa, `Any`. +Na kraju, ispisana je string reprezentacija svih elemenata. + +Ovo je izlaz programa: + + Ovo je string + 732 + c + true + diff --git a/_ba/tour/upper-type-bounds.md b/_ba/tour/upper-type-bounds.md new file mode 100644 index 0000000000..eb8fa5ef1e --- /dev/null +++ b/_ba/tour/upper-type-bounds.md @@ -0,0 +1,41 @@ +--- +layout: tour +title: Gornja granica tipa + +discourse: false + +partof: scala-tour + +num: 19 + +language: ba + +next-page: lower-type-bounds +previous-page: variances +--- + +U Scali, [tipski parametri](generic-classes.html) i [apstraktni tipovi](abstract-types.html) mogu biti ograničeni granicom tipa. +Takve granice tipa ograničavaju konkretne vrijednosti tipskih varijabli i ponekad otkrivaju još informacija o članovima takvih tipova. + _Gornja granica tipa_ `T <: A` deklariše da se tipska varijabla `T` odnosi na podtip tipa `A`. +Slijedi primjer koji se oslanja na gornju granicu tipa za implementaciju polimorfne metode `findSimilar`: + + trait Similar { + def isSimilar(x: Any): Boolean + } + case class MyInt(x: Int) extends Similar { + def isSimilar(m: Any): Boolean = + m.isInstanceOf[MyInt] && + m.asInstanceOf[MyInt].x == x + } + object UpperBoundTest extends App { + def findSimilar[T <: Similar](e: T, xs: List[T]): Boolean = + if (xs.isEmpty) false + else if (e.isSimilar(xs.head)) true + else findSimilar[T](e, xs.tail) + val list: List[MyInt] = List(MyInt(1), MyInt(2), MyInt(3)) + println(findSimilar[MyInt](MyInt(4), list)) + println(findSimilar[MyInt](MyInt(2), list)) + } + +Bez gornje granice ne bi bilo moguće pozvati metodu `isSimilar` iz metode `findSimilar`. +Korištenje donje granice tipa razmotreno je [ovdje](lower-type-bounds.html). diff --git a/_ba/tour/variances.md b/_ba/tour/variances.md new file mode 100644 index 0000000000..f8bcb83f0d --- /dev/null +++ b/_ba/tour/variances.md @@ -0,0 +1,63 @@ +--- +layout: tour +title: Varijanse + +discourse: false + +partof: scala-tour + +num: 18 + +language: ba + +next-page: upper-type-bounds +previous-page: generic-classes +--- + +Scala podržava anotacije varijanse tipskih parametara [generičkih klasa](generic-classes.html). +Nasuprot Javi 5 ([JDK 1.5](http://java.sun.com/j2se/1.5/)), anotacije varijanse se dodaju pri definiciji same klase, +dok u Javi 5, anotacije varijanse se dodaju na korisničkoj strani, tj. kada se klasa koristi. + +Na stranici o [generičkim klasama](generic-classes.html) dat je primjer promjenjivog steka. +Objasnili smo da je tip definisan klasom `Stack[T]` subjekt invarijantnog nasljeđivanja u odnosu na tipski parametar. +Ovo može ograničiti ponovnu upotrebu apstrakcije klase. +Sada ćemo izvesti funkcionalnu (tj. nepromjenjivu) implementaciju steka koja nema ovo ograničenje. +Molimo primijetite da je ovo komplikovaniji primjer koji kombinuje upotrebu [polimorfnih metoda](polymorphic-methods.html), +[donjih granica tipa](lower-type-bounds.html), i kovarijantnu anotaciju tipskog parametra na netrivijalan način. +Nadalje, koristimo [unutarnje klase](inner-classes.html) da povežemo elemente steka bez eksplicitnih veza. + +```tut +class Stack[+T] { + def push[S >: T](elem: S): Stack[S] = new Stack[S] { + override def top: S = elem + override def pop: Stack[S] = Stack.this + override def toString: String = + elem.toString + " " + Stack.this.toString + } + def top: T = sys.error("no element on stack") + def pop: Stack[T] = sys.error("no element on stack") + override def toString: String = "" +} + +object VariancesTest extends App { + var s: Stack[Any] = new Stack().push("hello") + s = s.push(new Object()) + s = s.push(7) + println(s) +} +``` + +Anotacija `+T` deklariše tip `T` da bude korišten samo na kovarijantnim pozicijama. +Slično, `-T` bi deklarisalo `T` da bude korišten samo na kontravarijantnim pozicijama. +Za kovarijantne tipske parametre dobijamo kovarijantnu podtip relaciju u odnosu na ovaj parametar. +Za naš primjer to znači da je `Stack[T]` podtip od `Stack[S]` ako je `T` podtip `S`. +Suprotno važi za tipske parametre koji su obilježeni s `-`. + +Za primjer sa stekom morali bi koristiti kovarijantni tipski parametar `T` na kontravarijantnoj poziciji pri definiciji metode `push`. +Pošto želimo kovarijantno nasljeđivanje za stekove, koristimo trik kojim apstrahujemo nad tipskim parametrom metode `push`. +Dobijamo polimorfnu metodu u kojoj koristimo element tip `T` kao donju granicu tipske varijable metode `push`. +Ovo ima efekt sinhronizovanja varijanse `T` s njegovom deklaracijom kao kovarijantni tipski parametar. +Sada su stekovi kovarijantni, ali naše rješenje dozvoljava npr. da dodamo string na stek integera. +Rezultat će biti stek tipa `Stack[Any]`; +tako da samo ako je rezultat korišten u kontekstu gdje se zahtijeva stek integera, možemo otkriti grešku. +U suprotnom dobijamo stek s generalnijim tipom elemenata. diff --git a/_books/1-programming-in-scala-3rd.md b/_books/1-programming-in-scala-3rd.md new file mode 100644 index 0000000000..4c04d132c8 --- /dev/null +++ b/_books/1-programming-in-scala-3rd.md @@ -0,0 +1,12 @@ +--- +title: "Programming in Scala, 3rd ed" +link: http://booksites.artima.com/programming_in_scala_3ed +image: /resources/img/books/ProgrammingInScala.gif +status: Updated for Scala 2.12 +authors: ["Martin Odersky", "Lex Spoon", "Bill Benners"] +publisher: +--- + +(First edition [available for free online reading](http://www.artima.com/pins1ed/)) + +Being co-written by the language's designer, Martin Odersky, you will find it provides additional depth and clarity to the diverse features of the language. The book provides both an authoritative reference for Scala and a systematic tutorial covering all the features in the language. Once you are familiar with the basics of Scala you will appreciate having this source of invaluable examples and precise explanations of Scala on hand. The book is available from [Artima](http://booksites.artima.com/programming_in_scala_3ed). Award winning book - [Jolt Productivity award](http://www.drdobbs.com/joltawards/232601431) for Technical Books. diff --git a/_books/2-scala-for-the-impatient.md b/_books/2-scala-for-the-impatient.md new file mode 100644 index 0000000000..1113269218 --- /dev/null +++ b/_books/2-scala-for-the-impatient.md @@ -0,0 +1,22 @@ +--- +title: "Scala for the Impatient" +link: http://www.horstmann.com/scala/index.html +image: /resources/img/books/scala_for_the_impatient.png +status: Available Now +authors: ["Cay S. Horstmann"] +publisher: Addison-Wesley +publisherLink: https://heuk.pearson.com/about-us.html/Addison-Wesley/ +--- + +What you get: + +* A rapid introduction to Scala for programmers who are competent in Java, C#, or C++ +* Blog-length chunks of information that you can digest quickly +* An organization that you'll find useful as a quick reference + +What you don't get: + +* An introduction into programming or object-oriented design +* Religion about the superiority of one paradigm or another +* Cute or academic examples +* Mind-numbing details about syntax minutiae diff --git a/_books/3-programming-scala.md b/_books/3-programming-scala.md new file mode 100644 index 0000000000..eb814ff1fb --- /dev/null +++ b/_books/3-programming-scala.md @@ -0,0 +1,11 @@ +--- +title: "Programming Scala" +link: http://shop.oreilly.com/product/0636920033073.do +image: /resources/img/books/ProgrammingScala-final-border.gif +status: Updated for Scala 2.11 +authors: ["Alex Payne", "Dean Wampler"] +publisher: O’Reilly +publisherLink: http://www.oreilly.com/ +--- + +Both are industry experts, Alex Payne being the lead API programmer at Twitter, a social networking service based on Scala. O’Reilly, the publisher, writes: "Learn how to be more productive with Scala, a new multi-paradigm language for the Java Virtual Machine (JVM) that integrates features of both object-oriented and functional programming. With this book, you'll discover why Scala is ideal for highly scalable, component-based applications that support concurrency and distribution. You'll also learn how to leverage the wealth of Java class libraries to meet the practical needs of enterprise and Internet projects more easily." \ No newline at end of file diff --git a/_books/4-functional-programming-in-scala.md b/_books/4-functional-programming-in-scala.md new file mode 100644 index 0000000000..9a27404679 --- /dev/null +++ b/_books/4-functional-programming-in-scala.md @@ -0,0 +1,11 @@ +--- +title: "Functional Programming in Scala" +link: https://www.manning.com/books/functional-programming-in-scala +image: /resources/img/books/FPiS_93x116.png +status: Available now +authors: ["Paul Chiusano", "Rúnar Bjarnason"] +publisher: Manning +publisherLink: http://www.manning.com/ +--- + +"Functional programming (FP) is a style of software development emphasizing functions that don't depend on program state... Functional Programming in Scala is a serious tutorial for programmers looking to learn FP and apply it to the everyday business of coding. The book guides readers from basic techniques to advanced topics in a logical, concise, and clear progression. In it, you'll find concrete examples and exercises that open up the world of functional programming." \ No newline at end of file diff --git a/_books/5-scala-in-depth.md b/_books/5-scala-in-depth.md new file mode 100644 index 0000000000..6262e3a4ee --- /dev/null +++ b/_books/5-scala-in-depth.md @@ -0,0 +1,11 @@ +--- +title: "Scala in Depth" +link: http://www.manning.com/suereth +image: /resources/img/books/icon_Scala_in_Depth_93x116.png +status: Available now +authors: ["Joshua D. Suereth"] +publisher: Manning +publisherLink: http://www.manning.com/ +--- + +"While information about the Scala language is abundant, skilled practitioners, great examples, and insight into the best practices of the community are harder to find. Scala in Depth bridges that gap, preparing you to adopt Scala successfully for real world projects. Scala in Depth is a unique new book designed to help you integrate Scala effectively into your development process. By presenting the emerging best practices and designs from the Scala community, it guides you though dozens of powerful techniques example by example. There's no heavy-handed theory here-just lots of crisp, practical guides for coding in Scala." \ No newline at end of file diff --git a/_books/6-scala-puzzlers.md b/_books/6-scala-puzzlers.md new file mode 100644 index 0000000000..a14d32b183 --- /dev/null +++ b/_books/6-scala-puzzlers.md @@ -0,0 +1,11 @@ +--- +title: "Scala Puzzlers" +link: http://www.artima.com/shop/scala_puzzlers +image: /resources/img/books/icon_Scala_in_Depth_93x116.png +status: Available now +authors: ["Andrew Phillips", "Nermin Šerifović"] +publisher: Artima Press +publisherLink: http://www.artima.com/index.jsp +--- + +"Getting code to do what we want it to do is perhaps the essence of our purpose as developers. So there are few things more intriguing or important than code that we think we understand, but that behaves rather contrary to our expectations. Scala Puzzlers is a collection of such examples in Scala. It is not only an entertaining and instructive way of understanding this highly expressive language better. It will also help you recognize many counter-intuitive traps and pitfalls and prevent them from inflicting further production bug hunt stress on Scala developers." \ No newline at end of file diff --git a/_config.yml b/_config.yml index 166a936f20..da1e6599c1 100644 --- a/_config.yml +++ b/_config.yml @@ -15,11 +15,72 @@ keywords: - Document - Guide -scala-version: 2.12.0 +scala-version: 2.12.2 + +collections: + style: + output: true + overviews: + output: true + tour: + output: true + permalink: /:collection/:path.html + tutorials: + output: true + permalink: /:collection/:path.html + sips: + output: true + permalink: /:collection/:path.html + books: + output: false + ja: # Japanese translations + output: true + permalink: /:collection/:path.html + zh-cn: # Chinese (Simplified) translations + output: true + permalink: /:collection/:path.html + ru: # Russian translations + output: true + permalink: /:collection/:path.html + es: # Spanish translations + output: true + permalink: /:collection/:path.html + ba: # Bosnian translations + output: true + permalink: /:collection/:path.html + pl: # Polish translations + output: true + permalink: /:collection/:path.html + pt-br: # Brazilian Portuguese translations + output: true + permalink: /:collection/:path.html + ko: # Korean translations + output: true + permalink: /:collection/:path.html + de: # German translations + output: true + permalink: /:collection/:path.html + it: # Italian translations + output: true + permalink: /:collection/:path.html + zh-tw: # Taiwanese translations + output: true + permalink: /:collection/:path.html + fr: # French translations + output: true + permalink: /:collection/:path.html + +defaults: + - + scope: + path: "" + type: "tour" + values: + overview-name: "Tour of Scala" highlighter: rouge permalink: /:categories/:title.html:output_ext baseurl: exclude: ["vendor"] -gems: +plugins: - jekyll-redirect-from diff --git a/_data/doc-nav-header.yml b/_data/doc-nav-header.yml new file mode 100644 index 0000000000..db1d1afeef --- /dev/null +++ b/_data/doc-nav-header.yml @@ -0,0 +1,37 @@ +- title: API + url: "#!" + submenu: + - title: Current + url: https://www.scala-lang.org/api/current/ + - title: Nightly + url: https://www.scala-lang.org/files/archive/nightly/2.12.x/api/2.12.x/ + - title: All Versions + url: "/api/all.html" +- title: Learn + url: "#!" + submenu: + - title: Getting Started + url: "/getting-started.html" + - title: Tour of Scala + url: "/tour/tour-of-scala.html" + - title: Scala for Java Programmers + url: "/tutorials/scala-for-java-programmers.html" +- title: Reference + url: "#!" + submenu: + - title: "Guides & Overviews" + url: "/overviews/index.html" + - title: Books + url: "/books.html" + - title: Scala FAQs + url: "/tutorials/FAQ/index.html" + - title: Language Spec + url: http://scala-lang.org/files/archive/spec/2.12/ +- title: Style Guide + url: "/style/index.html" +- title: Cheatsheet + url: "/cheatsheets/index.html" +- title: Glossary + url: "/glossary/index.html" +- title: SIPs + url: "/sips/index.html" diff --git a/_data/docnames.yml b/_data/docnames.yml new file mode 100644 index 0000000000..7a8ddf97b9 --- /dev/null +++ b/_data/docnames.yml @@ -0,0 +1,8 @@ +scala-tour: + name: "Tour of Scala" + +futures: + name: "Futures" + +cheatsheet: + name: "Scala Cheatsheet" diff --git a/_data/footer.yml b/_data/footer.yml new file mode 100644 index 0000000000..3ec77a2fac --- /dev/null +++ b/_data/footer.yml @@ -0,0 +1,56 @@ +- title: Documentation + class: documentation + links: + - title: Getting Started + url: "/getting-started.html" + - title: API + url: "https://www.scala-lang.org/api/current/index.html" + - title: Overviews/Guides + url: "/overviews" + - title: Tutorials + url: "tutorials/" + - title: Language Specification + url: "http://scala-lang.org/files/archive/spec/2.12/" +- title: Download + class: download + links: + - title: Current Version + url: "http://scala-lang.org/download/" + - title: All versions + url: "http://scala-lang.org/download/all.html" +- title: Community + class: community + links: + - title: Community + url: "http://scala-lang.org/community/" + - title: Mailing Lists + url: "http://scala-lang.org/community/index.html#mailing-lists" + - title: Chat Rooms & More + url: "http://scala-lang.org/community/index.html#chat-rooms" + - title: Libraries and Tools + url: "http://scala-lang.org/community/index.html#community-libraries-and-tools" + - title: "The Scala Center" + url: "http://scala.epfl.ch/" +- title: Contribute + class: contribute + links: + - title: How to help + url: "http://scala-lang.org/contribute/" + - title: Report an Issue + url: "http://scala-lang.org/contribute/bug-reporting-guide.html" +- title: Scala + class: scala + links: + - title: Blog + url: "http://scala-lang.org/blog/" + - title: Code of Conduct + url: "http://scala-lang.org/conduct/" + - title: License + url: "http://scala-lang.org/license/" +- title: Social + class: social + links: + - title: GitHub + url: "https://github.com/scala/scala" + - title: Twitter + url: "https://twitter.com/scala_lang" diff --git a/_data/nav-header.yml b/_data/nav-header.yml new file mode 100644 index 0000000000..712048ed02 --- /dev/null +++ b/_data/nav-header.yml @@ -0,0 +1,12 @@ +- title: Documentation + url: / +- title: Download + url: https://www.scala-lang.org/download/ +- title: Community + url: https://www.scala-lang.org/community/ +- title: Libraries + url: https://index.scala-lang.org +- title: Contribute + url: https://www.scala-lang.org/contribute/ +- title: Blog + url: https://www.scala-lang.org/blog/ diff --git a/_data/overviews.yml b/_data/overviews.yml new file mode 100644 index 0000000000..f7ac783f48 --- /dev/null +++ b/_data/overviews.yml @@ -0,0 +1,219 @@ + +- category: Core Scala + description: "Guides and overviews covering central libraries in the Scala standard library, core language features, and more." + overviews: + - title: Collections + by: Martin Odersky + icon: sitemap + url: "collections/introduction.html" + description: "Scala's Collection Library." + subdocs: + - title: Introduction + url: "collections/introduction.html" + - title: Mutable and Immutable Collections + url: "collections/overview.html" + - title: Trait Traversable + url: "collections/trait-traversable.html" + - title: Trait Iterable + url: "collections/trait-iterable.html" + - title: The sequence traits Seq, IndexedSeq, and LinearSeq + - title: Concrete Immutable Collection Classes + url: "collections/concrete-immutable-collection-classes.html" + - title: Concrete Mutable Collection Classes + url: "collections/concrete-mutable-collection-classes.html" + - title: Arrays + url: "collections/arrays.html" + - title: Strings + url: "collections/strings.html" + - title: Performance Characteristics + url: "collections/performance-characteristics.html" + - title: Equality + url: "collections/equality.html" + - title: Views + url: "collections/views.html" + - title: Iterators + url: "collections/iterators.html" + - title: Creating Collections From Scratch + url: "collections/creating-collections-from-scratch.html" + - title: Conversions Between Java and Scala Collections + url: "collections/conversions-between-java-and-scala-collections.html" + - title: The Architecture of Scala Collections + icon: building + url: "core/architecture-of-scala-collections.html" + by: Martin Odersky and Lex Spoon + description: "These pages describe the architecture of the Scala collections framework in detail. Compared to the Collections API you will find out more about the internal workings of the framework. You will also learn how this architecture helps you define your own collections in a few lines of code, while reusing the overwhelming part of collection functionality from the framework." + - title: String Interpolation + icon: usd + url: "core/string-interpolation.html" + description: > + String Interpolation allows users to embed variable references directly in processed string literals. Here’s an example: +
val name = "James"
+          println(s"Hello, $name")  // Hello, James
+ In the above, the literal s"Hello, $name" is a processed string literal. This means that the compiler does some additional work to this literal. A processed string literal is denoted by a set of characters preceding the ". String interpolation was introduced by SIP-11, which contains all details of the implementation. + - title: Implicit Classes + by: Josh Suereth + description: "Scala 2.10 introduced a new feature called implicit classes. An implicit class is a class marked with the implicit keyword. This keyword makes the class’ primary constructor available for implicit conversions when the class is in scope." + url: "core/implicit-classes.html" + - title: Value Classes and Universal Traits + by: Mark Harrah + description: "Value classes are a new mechanism in Scala to avoid allocating runtime objects. This is accomplished through the definition of new AnyVal subclasses." + icon: diamond + url: "core/value-classes.html" + - title: Binary Compatibility of Scala Releases + description: "When two versions of Scala are binary compatible, it is safe to compile your project on one Scala version and link against another Scala version at run time. Safe run-time linkage (only!) means that the JVM does not throw a (subclass of) LinkageError when executing your program in the mixed scenario, assuming that none arise when compiling and running on the same version of Scala. Concretely, this means you may have external dependencies on your run-time classpath that use a different version of Scala than the one you’re compiling with, as long as they’re binary compatible. In other words, separate compilation on different binary compatible versions does not introduce problems compared to compiling and running everything on the same version of Scala." + icon: puzzle-piece + url: "core/binary-compatibility-of-scala-releases.html" + +- category: "Reference/Documentation" + description: "Reference material on core Scala tools like Scaladoc and the Scala REPL." + overviews: + - title: Scaladoc + url: "scaladoc/overview.html" + icon: book + description: "Scala's API documentation generation tool." + subdocs: + - title: Overview + url: "scaladoc/overview.html" + - title: Scaladoc for Library Authors + url: "scaladoc/for-library-authors.html" + - title: Using the Scaladoc Interface + url: "scaladoc/interface.html" + - title: Scala REPL + icon: terminal + url: "repl/overview.html" + description: | + The Scala REPL is a tool (scala) for evaluating expressions in Scala. +

+ The scala command will execute a source script by wrapping it in a template and then compiling and executing the resulting program + +- category: Parallel and Concurrent Programming + description: "Complete guides covering some of Scala's libraries for parallel and concurrent programming." + overviews: + - title: Futures and Promises + by: "Philipp Haller, Aleksandar Prokopec, Heather Miller, Viktor Klang, Roland Kuhn, and Vojin Jovanovic" + icon: tasks + url: "core/futures.html" + description: "Futures provide a way to reason about performing many operations in parallel– in an efficient and non-blocking way. A Future is a placeholder object for a value that may not yet exist. Generally, the value of the Future is supplied concurrently and can subsequently be used. Composing concurrent tasks in this way tends to result in faster, asynchronous, non-blocking parallel code." + - title: Parallel Collections + by: Aleksandar Prokopec and Heather Miller + icon: rocket + url: "parallel-collections/overview.html" + description: "Scala's Parallel Collections Library." + subdocs: + - title: Overview + url: "parallel-collections/overview.html" + - title: Concrete Parallel Collection Classes + url: "parallel-collections/concrete-parallel-collections.html" + - title: Parallel Collection Conversions + url: "parallel-collections/conversions.html" + - title: Concurrent Tries + url: "parallel-collections/ctries.html" + - title: Architecture of the Parallel Collections Library + url: "parallel-collections/architecture.html" + - title: Creating Custom Parallel Collections + url: "parallel-collections/custom-parallel-collections.html" + - title: Configuring Parallel Collections + url: "parallel-collections/configuration.html" + - title: Measuring Performance + url: "parallel-collections/performance.html" + - title: The Scala Actors Migration Guide + by: Vojin Jovanovic and Philipp Haller + icon: truck + url: "core/actors-migration-guide.html" + description: "To ease the migration from Scala Actors to Akka we have provided the Actor Migration Kit (AMK). The AMK consists of an extension to Scala Actors which is enabled by including the scala-actors-migration.jar on a project’s classpath. In addition, Akka 2.1 includes features, such as the ActorDSL singleton, which enable a simpler conversion of code using Scala Actors to Akka. The purpose of this document is to guide users through the migration process and explain how to use the AMK." + - title: The Scala Actors API + by: Philipp Haller and Stephen Tu + icon: users + url: "core/actors.html" + description: "This guide describes the API of the scala.actors package of Scala 2.8/2.9. The organization follows groups of types that logically belong together. The trait hierarchy is taken into account to structure the individual sections. The focus is on the run-time behavior of the various methods that these traits define, thereby complementing the existing Scaladoc-based API documentation." + label-color: "#899295" + label-text: deprecated + +- category: Metaprogramming + description: "Guides and overviews covering the experimental reflection API introduced in Scala 2.10, and Scala's tools for metaprogramming (def macros, macro annotations, and more), also introduced in Scala 2.10" + overviews: + - title: Reflection + by: Heather Miller, Eugene Burmako, and Philipp Haller + icon: binoculars + url: "reflection/overview.html" + description: Scala's runtime/compile-time reflection framework. + label-text: experimental + subdocs: + - title: Overview + url: "reflection/overview.html" + - title: Environment, Universes, and Mirrors + url: "reflection/environment-universes-mirrors.html" + - title: Symbols, Trees, and Types + url: "reflection/symbols-trees-types.html" + - title: Annotations, Names, Scopes, and More + url: "reflection/annotations-names-scopes.html" + - title: TypeTags and Manifests + url: "reflection/typetags-manifests.html" + - title: Thread Safety + url: "reflection/thread-safety.html" + - title: Changes in Scala 2.11 + url: "reflection/changelog211.html" + - title: Macros + by: Eugene Burmako + icon: magic + url: "macros/usecases.html" + description: "Scala's metaprogramming framework." + label-text: experimental + subdocs: + - title: Use Cases + url: "macros/usecases.html" + - title: Blackbox Vs Whitebox + url: "macros/blackbox-whitebox.html" + - title: Def Macros + url: "macros/overview.html" + - title: Quasiquotes + url: "quasiquotes/intro.html" + - title: Macro Bundles + url: "macros/bundles.html" + - title: Implicit Macros + url: "macros/implicits.html" + - title: Extractor Macros + url: "macros/extractors.html" + - title: Type Providers + url: "macros/typeproviders.html" + - title: Macro Annotations + url: "macros/annotations.html" + - title: Macro Paradise + url: "macros/paradise.html" + - title: Roadmap + url: "macros/roadmap.html" + - title: Changes in 2.11 + url: "macros/changelog211.html" + - title: Quasiquotes + by: Denys Shabalin + icon: quote-left + url: "quasiquotes/setup.html" + description: "Quasiquotes are a convenient way to manipulate Scala syntax trees." + label-text: experimental + subdocs: + - title: Dependencies and setup + url: "quasiquotes/setup.html" + - title: Introduction + url: "quasiquotes/intro.html" + - title: Lifting + url: "quasiquotes/lifting.html" + - title: Unlifting + url: "quasiquotes/unlifting.html" + - title: Hygiene + url: "quasiquotes/hygiene.html" + - title: Use cases + url: "quasiquotes/usecases.html" + - title: Syntax summary + url: "quasiquotes/syntax-summary.html" + - title: Expression details + url: "quasiquotes/expression-details.html" + - title: Type details + url: "quasiquotes/type-details.html" + - title: Pattern details + url: "quasiquotes/pattern-details.html" + - title: Definition and import details + url: "quasiquotes/definition-details.html" + - title: Terminology summary + url: "quasiquotes/terminology.html" + - title: Future prospects + url: "quasiquotes/future.html" diff --git a/_data/sip-data.yml b/_data/sip-data.yml new file mode 100644 index 0000000000..6a6b19fe68 --- /dev/null +++ b/_data/sip-data.yml @@ -0,0 +1,27 @@ +under-review: + color: "#b58900" + text: "Under Review" + +pending: + color: "#b58900" + text: "Pending" + +dormant: + color: "#839496" + text: "Dormant" + +under-revision: + color: "#2aa198" + text: "Under Revision" + +accepted: + color: "#859900" + text: "Accepted" + +complete: + color: "#859900" + text: "Complete" + +rejected: + color: "#dc322f" + text: "Rejected" diff --git a/_data/translations.yml b/_data/translations.yml new file mode 100644 index 0000000000..afbfdbb6f4 --- /dev/null +++ b/_data/translations.yml @@ -0,0 +1,2 @@ +tour: + languages: [ba, es, ko, pt-br, pl] diff --git a/_de/tutorials/scala-for-java-programmers.md b/_de/tutorials/scala-for-java-programmers.md new file mode 100644 index 0000000000..451cc0a940 --- /dev/null +++ b/_de/tutorials/scala-for-java-programmers.md @@ -0,0 +1,633 @@ +--- +layout: singlepage-overview +title: Ein Scala Tutorial für Java Programmierer + +partof: scala-for-java-programmers + +discourse: false +language: de +--- + +Von Michel Schinz und Philipp Haller. +Deutsche Übersetzung von Christian Krause. + +## Einleitung + +Dieses Tutorial dient einer kurzen Vorstellung der Programmiersprache Scala und deren Compiler. Sie +ist für fortgeschrittene Programmierer gedacht, die sich einen Überblick darüber verschaffen wollen, +wie man mit Scala arbeitet. Grundkenntnisse in Objekt-orientierter Programmierung, insbesondere +Java, werden vorausgesetzt. + +## Das erste Beispiel + +Als erstes folgt eine Implementierung des wohlbekannten *Hallo, Welt!*-Programmes. Obwohl es sehr +einfach ist, eignet es sich sehr gut, Scalas Funktionsweise zu demonstrieren, ohne dass man viel +über die Sprache wissen muss. + + object HalloWelt { + def main(args: Array[String]) { + println("Hallo, Welt!") + } + } + +Die Struktur des Programmes sollte Java Anwendern bekannt vorkommen: es besteht aus einer Methode +namens `main`, welche die Kommandozeilenparameter als Feld (Array) von Zeichenketten (String) +übergeben bekommt. Der Körper dieser Methode besteht aus einem einzelnen Aufruf der vordefinierten +Methode `println`, die die freundliche Begrüßung als Parameter übergeben bekommt. Weiterhin hat die +`main`-Methode keinen Rückgabewert - sie ist also eine Prozedur. Daher ist es auch nicht notwendig, +einen Rückgabetyp zu spezifizieren. + +Was Java-Programmierern allerdings weniger bekannt sein sollte, ist die Deklaration `object +HalloWelt`, welche die Methode `main` enthält. Eine solche Deklaration stellt dar, was gemeinhin als +*Singleton Objekt* bekannt ist: eine Klasse mit nur einer Instanz. Im Beispiel oben werden also mit +dem Schlüsselwort `object` sowohl eine Klasse namens `HalloWelt` als auch die dazugehörige, +gleichnamige Instanz definiert. Diese Instanz wird erst bei ihrer erstmaligen Verwendung erstellt. + +Dem aufmerksamen Leser ist vielleicht aufgefallen, dass die `main`-Methode nicht als `static` +deklariert wurde. Der Grund dafür ist, dass statische Mitglieder (Attribute oder Methoden) in Scala +nicht existieren. Die Mitglieder von Singleton Objekten stellen in Scala dar, was Java und andere +Sprachen mit statischen Mitgliedern erreichen. + +### Das Beispiel kompilieren + +Um das obige Beispiel zu kompilieren, wird `scalac`, der Scala-Compiler verwendet. `scalac` arbeitet +wie die meisten anderen Compiler auch: er akzeptiert Quellcode-Dateien als Parameter, einige weitere +Optionen, und übersetzt den Quellcode in Java-Bytecode. Dieser Bytecode wird in ein oder mehrere +Java-konforme Klassen-Dateien, Dateien mit der Endung `.class`, geschrieben. + +Schreibt man den obigen Quellcode in eine Datei namens `HalloWelt.scala`, kann man diese mit dem +folgenden Befehl kompilieren (das größer-als-Zeichen `>` repräsentiert die Eingabeaufforderung und +sollte nicht mit geschrieben werden): + + > scalac HalloWelt.scala + +Damit werden einige Klassen-Dateien in das aktuelle Verzeichnis geschrieben. Eine davon heißt +`HalloWelt.class` und enthält die Klasse, die direkt mit dem Befehl `scala` ausgeführt werden kann, +was im folgenden Abschnitt erklärt wird. + +### Das Beispiel ausführen + +Sobald kompiliert, kann ein Scala-Programm mit dem Befehl `scala` ausgeführt werden. Die Anwendung +ist dem Befehl `java`, mit dem man Java-Programme ausführt, nachempfunden und akzeptiert dieselben +Optionen. Das obige Beispiel kann demnach mit folgendem Befehl ausgeführt werden, was das erwartete +Resultat ausgibt: + + > scala -classpath . HalloWelt + Hallo, Welt! + +## Interaktion mit Java + +Eine Stärke der Sprache Scala ist, dass man mit ihr sehr leicht mit Java interagieren kann. Alle +Klassen des Paketes `java.lang` stehen beispielsweise automatisch zur Verfügung, während andere +explizit importiert werden müssen. + +Als nächstes folgt ein Beispiel, was diese Interoperabilität demonstriert. Ziel ist es, das aktuelle +Datum zu erhalten und gemäß den Konventionen eines gewissen Landes zu formatieren, zum Beispiel +Frankreich. + +Javas Klassen-Bibliothek enthält viele nützliche Klassen, beispielsweise `Date` und `DateFormat`. +Dank Scala Fähigkeit, nahtlos mit Java zu interoperieren, besteht keine Notwendigkeit, äquivalente +Klassen in der Scala Klassen-Bibliothek zu implementieren - man kann einfach die entsprechenden +Klassen der Java-Pakete importieren: + + import java.util.{Date, Locale} + import java.text.DateFormat + import java.text.DateFormat._ + + object FrenchDate { + def main(args: Array[String]) { + val now = new Date + val df = getDateInstance(LONG, Locale.FRANCE) + println(df format now) + } + } + +Scala Import-Anweisung ähnelt sehr der von Java, obwohl sie viel mächtiger ist. Mehrere Klassen des +gleichen Paketes können gleichzeitig importiert werden, indem sie, wie in der ersten Zeile, in +geschweifte Klammern geschrieben werden. Ein weiterer Unterschied ist, dass, wenn man alle +Mitglieder eines Paketes importieren will, einen Unterstrich (`_`) anstelle des Asterisk (`*`) +verwendet. Der Grund dafür ist, dass der Asterisk ein gültiger Bezeichner in Scala ist, +beispielsweise als Name für Methoden, wie später gezeigt wird. Die Import-Anweisung der dritten +Zeile importiert demnach alle Mitglieder der Klasse `DateFormat`, inklusive der statischen Methode +`getDateInstance` und des statischen Feldes `LONG`. + +Innerhalb der `main`-Methode wird zuerst eine Instanz der Java-Klasse `Date` erzeugt, welche +standardmäßig das aktuelle Datum enthält. Als nächstes wird mithilfe der statischen Methode +`getDateInstance` eine Instanz der Klasse `DateFormat` erstellt. Schließlich wird das aktuelle Datum +gemäß der Regeln der lokalisierten `DateFormat`-Instanz formatiert ausgegeben. Außerdem +veranschaulicht die letzte Zeile eine interessante Fähigkeit Scalas Syntax: Methoden, die nur einen +Parameter haben, können in der Infix-Syntax notiert werden. Dies bedeutet, dass der Ausdruck + + df format now + +eine andere, weniger verbose Variante des folgenden Ausdruckes ist: + + df.format(now) + +Dies scheint nur ein nebensächlicher, syntaktischer Zucker zu sein, hat jedoch bedeutende +Konsequenzen, wie im folgenden Abschnitt gezeigt wird. + +Um diesen Abschnitt abzuschließen, soll bemerkt sein, dass es außerdem direkt in Scala möglich ist, +von Java-Klassen zu erben sowie Java-Schnittstellen zu implementieren. + +## Alles ist ein Objekt + +Scala ist eine pur Objekt-orientierte Sprache, in dem Sinne dass *alles* ein Objekt ist, Zahlen und +Funktionen eingeschlossen. Der Unterschied zu Java ist, dass Java zwischen primitiven Typen, wie +`boolean` und `int`, und den Referenz-Typen unterscheidet und es nicht erlaubt ist, Funktionen wie +Werte zu behandeln. + +### Zahlen sind Objekte + +Zahlen sind Objekte und haben daher Methoden. Tatsächlich besteht ein arithmetischer Ausdruck wie +der folgende + + 1 + 2 * 3 / x + +exklusiv aus Methoden-Aufrufen, da es äquivalent zu folgendem Ausdruck ist, wie in vorhergehenden +Abschnitt gezeigt wurde: + + (1).+(((2).*(3))./(x)) + +Dies bedeutet außerdem, dass `+`, `*`, etc. in Scala gültige Bezeichner sind. + +Die Zahlen umschließenden Klammern der zweiten Variante sind notwendig, weil Scalas lexikalischer +Scanner eine Regel zur längsten Übereinstimmung der Token verwendet. Daher würde der folgende +Ausdruck: + + 1.+(2) + +in die Token `1.`, `+`, und `2` zerlegt werden. Der Grund für diese Zerlegung ist, dass `1.` eine +längere, gültige Übereinstimmung ist, als `1`. Daher würde das Token `1.` als das Literal `1.0` +interpretiert, also als Gleitkommazahl anstatt als Ganzzahl. Den Ausdruck als + + (1).+(2) + +zu schreiben, verhindert also, dass `1.` als Gleitkommazahl interpretiert wird. + +### Funktionen sind Objekte + +Vermutlich überraschender für Java-Programmierer ist, dass auch Funktionen in Scala Objekte sind. +Daher ist es auch möglich, Funktionen als Parameter zu übergeben, als Werte zu speichern, und von +anderen Funktionen zurückgeben zu lassen. Diese Fähigkeit, Funktionen wie Werte zu behandeln, ist +einer der Grundsteine eines sehr interessanten Programmier-Paradigmas, der *funktionalen +Programmierung*. + +Ein sehr einfaches Beispiel, warum es nützlich sein kann, Funktionen wie Werte zu behandeln, ist +eine Timer-Funktion, deren Ziel es ist, eine gewisse Aktion pro Sekunde durchzuführen. Wie übergibt +man die durchzuführende Aktion? Offensichtlich als Funktion. Diese einfache Art der Übergabe einer +Funktion sollte den meisten Programmieren bekannt vorkommen: dieses Prinzip wird häufig bei +Schnittstellen für Rückruf-Funktionen (call-back) verwendet, die ausgeführt werden, wenn ein +bestimmtes Ereignis eintritt. + +Im folgenden Programm akzeptiert die Timer-Funktion `oncePerSecond` eine Rückruf-Funktion als +Parameter. Deren Typ wird `() => Unit` geschrieben und ist der Typ aller Funktionen, die keine +Parameter haben und nichts zurück geben (der Typ `Unit` ist das Äquivalent zu `void`). Die +`main`-Methode des Programmes ruft die Timer-Funktion mit der Rückruf-Funktion auf, die einen Satz +ausgibt. In anderen Worten: das Programm gibt endlos den Satz "Die Zeit vergeht wie im Flug." +einmal pro Sekunde aus. + + object Timer { + def oncePerSecond(callback: () => Unit) { + while (true) { + callback() + Thread sleep 1000 + } + } + + def timeFlies() { + println("Die Zeit vergeht wie im Flug.") + } + + def main(args: Array[String]) { + oncePerSecond(timeFlies) + } + } + +Weiterhin ist zu bemerken, dass, um die Zeichenkette auszugeben, die in Scala vordefinierte Methode +`println` statt der äquivalenten Methode in `System.out` verwendet wird. + +#### Anonyme Funktionen + +Während das obige Programm schon leicht zu verstehen ist, kann es noch verbessert werden. Als erstes +sei zu bemerken, dass die Funktion `timeFlies` nur definiert wurde, um der Funktion `oncePerSecond` +als Parameter übergeben zu werden. Dieser nur einmal verwendeten Funktion einen Namen zu geben, +scheint unnötig und es wäre angenehmer, sie direkt mit der Übergabe zu erstellen. Dies ist in Scala +mit *anonymen Funktionen* möglich, die eine Funktion ohne Namen darstellen. Die überarbeitete +Variante des obigen Timer-Programmes verwendet eine anonyme Funktion anstatt der Funktion +`timeFlies`: + + object TimerAnonymous { + def oncePerSecond(callback: () => Unit) { + while (true) { + callback() + Thread sleep 1000 + } + } + + def main(args: Array[String]) { + oncePerSecond(() => println("Die Zeit vergeht wie im Flug.")) + } + } + +Die anonyme Funktion erkennt man an dem Rechtspfeil `=>`, der die Parameter der Funktion von deren +Körper trennt. In diesem Beispiel ist die Liste der Parameter leer, wie man an den leeren Klammern +erkennen kann. Der Körper der Funktion ist derselbe, wie bei der `timeFlies` Funktion des +vorangegangenen Beispiels. + +## Klassen + +Wie weiter oben zu sehen war, ist Scala eine pur Objekt-orientierte Sprache, und als solche enthält +sie das Konzept von Klassen (der Vollständigkeit halber soll bemerkt sein, dass nicht alle +Objekt-orientierte Sprachen das Konzept von Klassen unterstützen, aber Scala ist keine von denen). +Klassen in Scala werden mit einer ähnlichen Syntax wie Java deklariert. Ein wichtiger Unterschied +ist jedoch, dass Scalas Klassen Argumente haben. Dies soll mit der folgenden Definition von +komplexen Zahlen veranschaulicht werden: + + class Complex(real: Double, imaginary: Double) { + def re() = real + def im() = imaginary + } + +Diese Klasse akzeptiert zwei Argumente, den realen und den imaginären Teil der komplexen Zahl. Sie +müssen beim Erzeugen einer Instanz der Klasse übergeben werden: + + val c = new Complex(1.5, 2.3) + +Weiterhin enthält die Klasse zwei Methoden, `re` und `im`, welche als Zugriffsfunktionen (Getter) +dienen. Außerdem soll bemerkt sein, dass der Rückgabe-Typ dieser Methoden nicht explizit deklariert +ist. Der Compiler schlussfolgert ihn automatisch, indem er ihn aus dem rechten Teil der Methoden +ableitet, dass der Rückgabewert vom Typ `Double` ist. + +Der Compiler ist nicht immer fähig, auf den Rückgabe-Typ zu schließen, und es gibt leider keine +einfache Regel, vorauszusagen, ob er dazu fähig ist oder nicht. In der Praxis stellt das +üblicherweise kein Problem dar, da der Compiler sich beschwert, wenn es ihm nicht möglich ist. +Scala-Anfänger sollten versuchen, Typ-Deklarationen, die leicht vom Kontext abzuleiten sind, +wegzulassen, um zu sehen, ob der Compiler zustimmt. Nach einer gewissen Zeit, bekommt man ein Gefühl +dafür, wann man auf diese Deklarationen verzichten kann und wann man sie explizit angeben sollte. + +### Methoden ohne Argumente + +Ein Problem der obigen Methoden `re` und `im` ist, dass man, um sie zu verwenden, ein leeres +Klammerpaar hinter ihren Namen anhängen muss: + + object ComplexNumbers { + def main(args: Array[String]) { + val c = new Complex(1.2, 3.4) + println("imaginary part: " + c.im()) + } + } + +Besser wäre es jedoch, wenn man den realen und imaginären Teil so abrufen könnte, als wären sie +Felder, also ohne das leere Klammerpaar. Mit Scala ist dies möglich, indem Methoden *ohne Argumente* +definiert werden. Solche Methoden haben keine Klammern nach ihrem Namen, weder bei ihrer Definition +noch bei ihrer Verwendung. Die Klasse für komplexe Zahlen kann demnach folgendermaßen umgeschrieben +werden: + + class Complex(real: Double, imaginary: Double) { + def re = real + def im = imaginary + } + +### Vererbung und Überschreibung + +Alle Klassen in Scala erben von einer Oberklasse. Wird keine Oberklasse angegeben, wie bei der +Klasse `Complex` des vorhergehenden Abschnittes, wird implizit `scala.AnyRef` verwendet. + +Außerdem ist es möglich, von einer Oberklasse vererbte Methoden zu überschreiben. Dabei muss jedoch +explizit das Schlüsselwort `override` angegeben werden, um versehentliche Überschreibungen zu +vermeiden. Als Beispiel soll eine Erweiterung der Klasse `Complex` dienen, die die Methode +`toString` neu definiert: + + class Complex(real: Double, imaginary: Double) { + def re = real + def im = imaginary + + override def toString() = + "" + re + (if (im < 0) "" else "+") + im + "i" + } + +## Container-Klassen und Musterabgleiche + +Eine Datenstruktur, die häufig in Programmen vorkommt, ist der Baum. Beispielsweise repräsentieren +Interpreter und Compiler Programme intern häufig als Bäume, XML-Dokumente sind Bäume und einige +Container basieren auf Bäumen, wie Rot-Schwarz-Bäume. + +Als nächstes wird anhand eines kleinen Programmes für Berechnungen gezeigt, wie solche Bäume in +Scala repräsentiert und manipuliert werden können. Das Ziel dieses Programmes ist, einfache +arithmetische Ausdrücke zu manipulieren, die aus Summen, Ganzzahlen und Variablen bestehen. +Beispiele solcher Ausdrücke sind: `1+2` und `(x+x)+(7+y)`. + +Dafür muss zuerst eine Repräsentation für die Ausdrücke gewählt werden. Die natürlichste ist ein +Baum, dessen Knoten Operationen (Additionen) und dessen Blätter Werte (Konstanten und Variablen) +darstellen. + +In Java würde man solche Bäume am ehesten mithilfe einer abstrakten Oberklasse für den Baum und +konkreten Implementierungen für Knoten und Blätter repräsentieren. In einer funktionalen Sprache +würde man algebraische Datentypen mit dem gleichen Ziel verwenden. Scala unterstützt das Konzept +einer Container-Klasse (case class), die einen gewissen Mittelweg dazwischen darstellen. Der +folgenden Quellcode veranschaulicht deren Anwendung: + + abstract class Tree + case class Sum(l: Tree, r: Tree) extends Tree + case class Var(n: String) extends Tree + case class Const(v: Int) extends Tree + +Die Tatsache, dass die Klassen `Sum`, `Var` und `Const` als Container-Klassen deklariert sind, +bedeutet, dass sie sich in einigen Gesichtspunkten von normalen Klassen unterscheiden: + +- das Schlüsselwort `new` ist nicht mehr notwendig, um Instanzen dieser Klassen zu erzeugen (man + kann also `Const(5)` anstelle von `new Const(5)` schreiben) +- Zugriffsfunktionen werden automatisch anhand der Parameter des Konstruktors erstellt (man kann + den Wert `v` einer Instanz `c` der Klasse `Const` erhalten, indem man `c.v` schreibt) +- der Compiler fügt Container-Klassen automatisch Implementierungen der Methoden `equals` und + `hashCode` hinzu, die auf der *Struktur* der Klassen basieren, anstelle deren Identität +- außerdem wird eine `toString`-Methode bereitgestellt, die einen Wert in Form der Quelle + darstellt (der String-Wert des Baum-Ausdruckes `x+1` ist `Sum(Var(x),Const(1))`) +- Instanzen dieser Klassen können mithilfe von Musterabgleichen zerlegt werden, wie weiter unten + zu sehen ist + +Da jetzt bekannt ist, wie die Datenstruktur der arithmetischen Ausdrücke repräsentiert wird, können +jetzt Operationen definiert werden, um diese zu manipulieren. Der Beginn dessen soll eine Funktion +darstellen, die Ausdrücke in einer bestimmten *Umgebung* auswertet. Das Ziel einer Umgebung ist es, +Variablen Werte zuzuweisen. Beispielsweise wird der Ausdruck `x+1` in der Umgebung, die der Variable +`x` den Wert `5` zuweist, geschrieben als `{ x -> 5 }`, mit dem Resultat `6` ausgewertet. + +Demnach muss ein Weg gefunden werden, solche Umgebungen auszudrücken. Dabei könnte man sich für eine +assoziative Datenstruktur entscheiden, wie eine Hash-Tabelle, man könnte jedoch auch direkt eine +Funktion verwenden. Eine Umgebung ist nicht mehr als eine Funktion, die Werte mit Variablen +assoziiert. Die obige Umgebung `{ x -> 5 }` wird in Scala folgendermaßen notiert: + + { case "x" => 5 } + +Diese Schreibweise definiert eine Funktion, welche bei dem String `"x"` als Argument die Ganzzahl +`5` zurückgibt, und in anderen Fällen mit einer Ausnahme fehlschlägt. + +Vor dem Schreiben der Funktionen zum Auswerten ist es sinnvoll, für die Umgebungen einen eigenen Typ +zu definieren. Man könnte zwar immer `String => Int` verwenden, es wäre jedoch besser einen +dedizierten Namen dafür zu verwenden, der das Programmieren damit einfacher macht und die Lesbarkeit +erhöht. Dies wird in Scala mit der folgenden Schreibweise erreicht: + + type Environment = String => Int + +Von hier an wird `Environment` als Alias für den Typ von Funktionen von `String` nach `Int` +verwendet. + +Nun ist alles für die Definition der Funktion zur Auswertung vorbereitet. Konzeptionell ist die +Definition sehr einfach: der Wert der Summe zweier Ausdrücke ist die Summe der Werte der einzelnen +Ausdrücke, der Wert einer Variablen wird direkt der Umgebung entnommen und der Wert einer Konstante +ist die Konstante selbst. Dies in Scala auszudrücken, ist nicht viel schwieriger: + + def eval(t: Tree, env: Environment): Int = t match { + case Sum(l, r) => eval(l, env) + eval(r, env) + case Var(n) => env(n) + case Const(v) => v + } + +Diese Funktion zum Auswerten von arithmetischen Ausdrücken nutzt einen *Musterabgleich* (pattern +matching) am Baumes `t`. Intuitiv sollte die Bedeutung der einzelnen Fälle klar sein: + +1. Als erstes wird überprüft, ob `t` eine Instanz der Klasse `Sum` ist. Falls dem so ist, wird der +linke Teilbaum der Variablen `l` und der rechte Teilbaum der Variablen `r` zugewiesen. Daraufhin +wird der Ausdruck auf der rechten Seite des Pfeiles ausgewertet, der die auf der linken Seite +gebundenen Variablen `l` und `r` verwendet. + +2. Sollte die erste Überprüfung fehlschlagen, also `t` ist keine `Sum`, wird der nächste Fall +abgehandelt und überprüft, ob `t` eine `Var` ist. Ist dies der Fall, wird analog zum ersten Fall der +Wert an `n` gebunden und der Ausdruck rechts vom Pfeil ausgewertet. + +3. Schlägt auch die zweite Überprüfung fehl, also `t` ist weder `Sum` noch `Val`, wird überprüft, +ob es eine Instanz des Typs `Const` ist. Analog wird bei einem Erfolg wie bei den beiden +vorangegangenen Fällen verfahren. + +4. Schließlich, sollten alle Überprüfungen fehlschlagen, wird eine Ausnahme ausgelöst, die +signalisiert, dass der Musterabgleich nicht erfolgreich war. Dies wird unweigerlich geschehen, +sollten neue Baum-Unterklassen erstellt werden. + +Die prinzipielle Idee eines Musterabgleiches ist, einen Wert anhand einer Reihe von Mustern +abzugleichen und, sobald ein Treffer erzielt wird, Werte zu extrahieren, mit denen darauf +weitergearbeitet werden kann. + +Erfahrene Objekt-orientierte Programmierer werden sich fragen, warum `eval` nicht als Methode der +Klasse `Tree` oder dessen Unterklassen definiert wurde. Dies wäre möglich, da Container-Klassen +Methoden definieren können, wie normale Klassen auch. Die Entscheidung, einen Musterabgleich oder +Methoden zu verwenden, ist Geschmackssache, hat jedoch wichtige Auswirkungen auf die +Erweiterbarkeit: + +- einerseits ist es mit Methoden einfach, neue Arten von Knoten als Unterklassen von `Tree` + hinzuzufügen, andererseits ist die Ergänzung einer neuen Operation zur Manipulation des Baumes + mühsam, da sie die Modifikation aller Unterklassen von `Tree` erfordert +- nutzt man einen Musterabgleich kehrt sich die Situation um: eine neue Art von Knoten erfordert + die Modifikation aller Funktionen die einen Musterabgleich am Baum vollführen, wogegen eine neue + Operation leicht hinzuzufügen ist, indem einfach eine unabhängige Funktion dafür definiert wird + +Einen weiteren Einblick in Musterabgleiche verschafft eine weitere Operation mit arithmetischen +Ausdrücken: partielle Ableitungen. Dafür gelten zur Zeit folgende Regeln: + +1. die Ableitung einer Summe ist die Summe der Ableitungen +2. die Ableitung einer Variablen ist eins, wenn sie die abzuleitende Variable ist, ansonsten `0` +3. die Ableitung einer Konstanten ist `0` + +Auch diese Regeln können fast wörtlich in Scala übersetzt werden: + + def derive(t: Tree, v: String): Tree = t match { + case Sum(l, r) => Sum(derive(l, v), derive(r, v)) + case Var(n) if (v == n) => Const(1) + case _ => Const(0) + } + +Diese Funktion führt zwei neue, mit dem Musterabgleich zusammenhängende Konzepte ein. Der zweite, +sich auf eine Variable beziehende Fall hat eine *Sperre* (guard), einen Ausdruck, der dem +Schlüsselwort `if` folgt. Diese Sperre verhindert eine Übereinstimmung, wenn der Ausdruck falsch +ist. In diesem Fall wird sie genutzt, die Konstante `1` nur zurückzugeben, wenn die Variable die +abzuleitende ist. Die zweite Neuerung ist der *Platzhalter* `_`, der mit allem übereinstimmt, jedoch +ohne einen Namen dafür zu verwenden. + +Die volle Funktionalität von Musterabgleichen wurde mit diesen Beispielen nicht demonstriert, doch +soll dies fürs Erste genügen. Eine Vorführung der beiden Funktionen an realen Beispielen steht immer +noch aus. Zu diesem Zweck soll eine `main`-Methode dienen, die den Ausdruck `(x+x)+(7+y)` als +Beispiel verwendet: zuerst wird der Wert in der Umgebung `{ x -> 5, y -> 7 }` berechnet und darauf +die beiden partiellen Ableitungen gebildet: + + def main(args: Array[String]) { + val exp: Tree = Sum(Sum(Var("x"),Var("x")),Sum(Const(7),Var("y"))) + val env: Environment = { + case "x" => 5 + case "y" => 7 + } + println("Ausdruck: " + exp) + println("Auswertung mit x=5, y=7: " + eval(exp, env)) + println("Ableitung von x:\n " + derive(exp, "x")) + println("Ableitung von y:\n " + derive(exp, "y")) + } + +Führt man das Programm aus, erhält man folgende Ausgabe: + + Ausdruck: Sum(Sum(Var(x),Var(x)),Sum(Const(7),Var(y))) + Auswertung mit x=5, y=7: 24 + Ableitung von x: + Sum(Sum(Const(1),Const(1)),Sum(Const(0),Const(0))) + Ableitung von y: + Sum(Sum(Const(0),Const(0)),Sum(Const(0),Const(1))) + +Beim Anblick dieser Ausgabe ist offensichtlich, dass man die Ergebnisse der Ableitungen noch +vereinfachen sollte. Eine solche Funktion zum Vereinfachen von Ausdrücken, die Musterabgleiche +nutzt, ist ein interessantes, aber gar nicht so einfaches Problem, was als Übung offen steht. + +## Traits + +Neben dem Vererben von Oberklassen ist es in Scala auch möglich von mehreren, sogenannten *Traits* +zu erben. Der beste Weg für einen Java-Programmierer einen Trait zu verstehen, ist sich eine +Schnittstelle vorzustellen, die Implementierungen enthält. Wenn in Scala eine Klasse von einem Trait +erbt, implementiert sie dessen Schnittstelle und erbt dessen Implementierungen. + +Um die Nützlichkeit von Traits zu demonstrieren, werden wir ein klassisches Beispiel implementieren: +Objekte mit einer natürlichen Ordnung oder Rangfolge. Es ist häufig hilfreich, Instanzen einer +Klasse untereinander vergleichen zu können, um sie beispielsweise sortieren zu können. In Java +müssen die Klassen solcher Objekte die Schnittstelle `Comparable` implementieren. In Scala kann dies +mit einer äquivalenten, aber besseren Variante von `Comparable` als Trait bewerkstelligt werden, die +im Folgenden `Ord` genannt wird. + +Wenn Objekte verglichen werden, sind sechs verschiedene Aussagen sinnvoll: kleiner, kleiner gleich, +gleich, ungleich, größer, und größer gleich. Allerdings ist es umständlich, immer alle sechs +Methoden dafür zu implementieren, vor allem in Anbetracht der Tatsache, dass vier dieser sechs durch +die verbliebenen zwei ausgedrückt werden können. Sind beispielsweise die Aussagen für gleich und +kleiner gegeben, kann man die anderen damit ausdrücken. In Scala können diese Beobachtungen mit +dem folgenden Trait zusammengefasst werden: + + trait Ord { + def < (that: Any): Boolean + def <=(that: Any): Boolean = (this < that) || (this == that) + def > (that: Any): Boolean = !(this <= that) + def >=(that: Any): Boolean = !(this < that) + } + +Diese Definition erzeugt sowohl einen neuen Typ namens `Ord`, welcher dieselbe Rolle wie Javas +Schnittstelle `Comparable` spielt, und drei vorgegebenen Funktionen, die auf einer vierten, +abstrakten basieren. Die Methoden für Gleichheit und Ungleichheit erscheinen hier nicht, da sie +bereits in allen Objekten von Scala vorhanden sind. + +Der Typ `Any`, welcher oben verwendet wurde, stellt den Ober-Typ aller Typen in Scala dar. Er kann +als noch allgemeinere Version von Javas `Object` angesehen werden, da er außerdem Ober-Typ der +Basis-Typen wie `Int` und `Float` ist. + +Um Objekte einer Klasse vergleichen zu können, ist es also hinreichend, Gleichheit und die +kleiner-als-Beziehung zu implementieren, und dieses Verhalten gewissermaßen mit der eigentlichen +Klasse zu vermengen (mix in). Als Beispiel soll eine Klasse für Datumsangaben dienen, die Daten +eines gregorianischen Kalenders repräsentiert. Solche Daten bestehen aus Tag, Monat und Jahr, welche +durch Ganzzahlen dargestellt werden: + + class Date(y: Int, m: Int, d: Int) extends Ord { + def year = y + def month = m + def day = d + + override def toString = year + "-" + month + "-" + day + +Der wichtige Teil dieser Definition ist die Deklaration `extends Ord`, welche dem Namen der Klasse +und deren Parametern folgt. Sie sagt aus, dass `Date` vom Trait `Ord` erbt. + +Nun folgt eine Re-Implementierung der Methode `equals`, die von `Object` geerbt wird, so dass die +Daten korrekt nach ihren Feldern verglichen werden. Die vorgegebene Implementierung von `equals` ist +dafür nicht nützlich, da in Java Objekte physisch, also nach deren Adressen im Speicher, verglichen +werden. Daher verwenden wir folgende Definition: + + override def equals(that: Any): Boolean = + that.isInstanceOf[Date] && { + val o = that.asInstanceOf[Date] + o.day == day && o.month == month && o.year == year + } + +Diese Methode verwendet die vordefinierten Methoden `isInstanceOf` und `asInstanceOf`. Erstere +entspricht Javas `instanceof`-Operator und gibt `true` zurück, wenn das zu testende Objekt eine +Instanz des angegebenen Typs ist. Letztere entspricht Javas Operator für Typ-Umwandlungen (cast): +ist das Objekt eine Instanz des angegebenen Typs, kann es als solcher angesehen und gehandhabt +werden, ansonsten wird eine `ClassCastException` ausgelöst. + +Schließlich kann die letzte Methode definiert werden, die für `Ord` notwendig ist, und die +kleiner-als-Beziehung implementiert. Diese nutzt eine andere, vordefinierte Methode, namens `error`, +des Paketes `sys`, welche eine `RuntimeException` mit der angegebenen Nachricht auslöst. + + def <(that: Any): Boolean = { + if (!that.isInstanceOf[Date]) + sys.error("cannot compare " + that + " and a Date") + + val o = that.asInstanceOf[Date] + (year < o.year) || + (year == o.year && (month < o.month || + (month == o.month && day < o.day))) + } + } + +Diese Methode vervollständigt die Definition der `Date`-Klasse. Instanzen dieser Klasse stellen +sowohl Daten als auch vergleichbare Objekte dar. Vielmehr implementiert diese Klasse alle sechs +Methoden, die für das Vergleichen von Objekten notwendig sind: `equals` und `<`, die direkt in der +Definition von `Date` vorkommen, sowie die anderen, in dem Trait `Ord` definierten Methoden. + +Traits sind nützlich in Situationen wie der obigen, den vollen Funktionsumfang hier zu zeigen, würde +allerdings den Rahmen dieses Dokumentes sprengen. + +## Generische Programmierung + +Eine weitere Charakteristik Scalas, die in diesem Tutorial vorgestellt werden soll, behandelt das +Konzept der generischen Programmierung. Java-Programmierer, die die Sprache noch vor der Version 1.5 +kennen, sollten mit den Problemen vertraut sein, die auftreten, wenn generische Programmierung nicht +unterstützt wird. + +Generische Programmierung bedeutet, Quellcode nach Typen zu parametrisieren. Beispielsweise stellt +sich die Frage für einen Programmierer bei der Implementierung einer Bibliothek für verkettete +Listen, welcher Typ für die Elemente verwendet werden soll. Da diese Liste in verschiedenen +Zusammenhängen verwendet werden soll, ist es nicht möglich, einen spezifischen Typ, wie `Int`, zu +verwenden. Diese willkürliche Wahl wäre sehr einschränkend. + +Aufgrund dieser Probleme griff man in Java vor der Einführung der generischen Programmierung zu dem +Mittel, `Object`, den Ober-Typ aller Typen, als Element-Typ zu verwenden. Diese Lösung ist +allerdings auch weit entfernt von Eleganz, da sie sowohl ungeeignet für die Basis-Typen, wie `int` +oder `float`, ist, als auch viele explizite Typ-Umwandlungen für den nutzenden Programmierer +bedeutet. + +Scala ermöglicht es, generische Klassen und Methoden zu definieren, um diesen Problemen aus dem Weg +zu gehen. Für die Demonstration soll ein einfacher, generischer Container als Referenz-Typ dienen, +der leer sein kann, oder auf ein Objekt des generischen Typs zeigt: + + class Reference[T] { + private var contents: T = _ + + def get: T = contents + + def set(value: T) { + contents = value + } + } + +Die Klasse `Reference` ist anhand des Types `T` parametrisiert, der den Element-Typ repräsentiert. +Dieser Typ wird im Körper der Klasse genutzt, wie bei dem Feld `contents`. Dessen Argument wird +durch die Methode `get` abgefragt und mit der Methode `set` verändert. + +Der obige Quellcode führt veränderbare Variablen in Scala ein, welche keiner weiteren Erklärung +erfordern sollten. Schon interessanter ist der initiale Wert dieser Variablen, der mit `_` +gekennzeichnet wurde. Dieser Standardwert ist für numerische Typen `0`, `false` für Wahrheitswerte, +`()` für den Typ `Unit` und `null` für alle anderen Typen. + +Um diese Referenz-Klasse zu verwenden, muss der generische Typ bei der Erzeugung einer Instanz +angegeben werden. Für einen Ganzzahl-Container soll folgendes Beispiel dienen: + + object IntegerReference { + def main(args: Array[String]) { + val cell = new Reference[Int] + cell.set(13) + println("Reference contains the half of " + (cell.get * 2)) + } + } + +Wie in dem Beispiel zu sehen ist, muss der Wert, der von der Methode `get` zurückgegeben wird, nicht +umgewandelt werden, wenn er als Ganzzahl verwendet werden soll. Es wäre außerdem nicht möglich, +einen Wert, der keine Ganzzahl ist, in einem solchen Container zu speichern, da er speziell und +ausschließlich für Ganzzahlen erzeugt worden ist. + +## Zusammenfassung + +Dieses Dokument hat einen kurzen Überblick über die Sprache Scala gegeben und dazu einige einfache +Beispiele verwendet. Interessierte Leser können beispielsweise mit dem Dokument *Scala by Example* +fortfahren, welches fortgeschrittenere Beispiele enthält, und die *Scala Language Specification* +konsultieren, sofern nötig. diff --git a/_es/overviews/core/actors.md b/_es/overviews/core/actors.md new file mode 100644 index 0000000000..8c74bcf92e --- /dev/null +++ b/_es/overviews/core/actors.md @@ -0,0 +1,497 @@ +--- +layout: singlepage-overview +title: API de actores en Scala + +partof: actors + +language: es + +discourse: false +--- + +**Philipp Haller and Stephen Tu** + +**Traducción e interpretación: Miguel Ángel Pastor Olivar** + +## Introducción + +La presente guía describe el API del paquete `scala.actors` de Scala 2.8/2.9. El documento se estructura en diferentes grupos lógicos. La jerarquía de "traits" es tenida en cuenta para llevar a cabo la estructuración de las secciones individuales. La atención se centra en el comportamiento exhibido en tiempo de ejecución por varios de los métodos presentes en los traits anteriores, complementando la documentación existente en el Scaladoc API. + +## Traits de actores: Reactor, ReplyReactor, y Actor + +### The Reactor trait + +`Reactor` es el padre de todos los traits relacionados con los actores. Heredando de este trait podremos definir actores con una funcionalidad básica de envío y recepción de mensajes. + +El comportamiento de un `Reactor` se define mediante la implementación de su método `act`. Este método es ejecutado una vez el `Reactor` haya sido iniciado mediante la invocación del método `start`, retornando el `Reactor`. El método `start`es *idempotente*, lo cual significa que la invocación del mismo sobre un actor que ya ha sido iniciado no surte ningún efecto. + +El trait `Reactor` tiene un parámetro de tipo `Msg` el cual determina el tipo de mensajes que un actor es capaz de recibir. + +La invocación del método `!` de un `Reactor` envía un mensaje al receptor. La operación de envío de un mensaje mediante el operador `!` es asíncrona por lo que el actor que envía el mensaje no se bloquea esperando a que el mensaje sea recibido sino que su ejecución continua de manera inmediata. Por ejemplo, `a ! msg` envia `msg` a `a`. Todos los actores disponen de un *buzón* encargado de regular los mensajes entrantes hasta que son procesados. + +El trait `Reactor` trait también define el método `forward`. Este método es heredado de `OutputChannel` y tiene el mismo efecto que el método `!`. Aquellos traits que hereden de `Reactor`, en particular el trait `ReplyActor`, sobreescriben este método para habilitar lo que comunmente se conocen como *"implicit reply destinations"* (ver a continuación) + +Un `Reactor` recibe mensajes utilizando el método `react`. Este método espera un argumento de tipo `PartialFunction[Msg, Unit]` el cual define cómo los mensajes de tipo `Msg` son tratados una vez llegan al buzón de un actor. En el siguiente ejemplo, el actor espera recibir la cadena "Hello", para posteriomente imprimir un saludo: + + react { + case "Hello" => println("Hi there") + } + +La invocación del método `react` nunca retorna. Por tanto, cualquier código que deba ejecutarse tras la recepción de un mensaje deberá ser incluido dentro de la función parcial pasada al método `react`. Por ejemplo, dos mensajes pueden ser recibidos secuencialmente mediante la anidación de dos llamadas a `react`: + + react { + case Get(from) => + react { + case Put(x) => from ! x + } + } + +El trait `Reactor` también ofrece una serie de estructuras de control que facilitan la programación utilizando el mecanismo de `react`. + +#### Terminación y estados de ejecución + +La ejecución de un `Reactor` finaliza cuando el cuerpo del método `act` ha sido completado. Un `Reactor` también pueden terminarse a si mismo de manera explícita mediante el uso del método `exit`. El tipo de retorno de `exit` es `Nothing`, dado que `exit` siempre dispara una excepción. Esta excepción únicamente se utiliza de manera interna y nunca debería ser capturada. + +Un `Reactor` finalizado pueden ser reiniciado mediante la invocación de su método `restart`. La invocación del método anterior sobre un `Reactor` que no ha terminado su ejecución lanza una excepción de tipo `IllegalStateException`. El reinicio de un actor que ya ha terminado provoca que el método `act` se ejecute nuevamente. + +El tipo `Reactor` define el método `getState`, el cual retorna, como un miembro de la enumeración `Actor.State`, el estado actual de la ejecución del actor. Un actor que todavía no ha sido iniciado se encuentra en el estado `Actor.State.New`. Si el actor se está ejecutando pero no está esperando por ningún mensaje su estado será `Actor.State.Runnable`. En caso de que el actor haya sido suspendido mientras espera por un mensaje estará en el estado `Actor.State.Suspended`. Por último, un actor ya terminado se encontrará en el estado `Actor.State.Terminated`. + +#### Manejo de excepciones + +El miembro `exceptionHandler` permite llevar a cabo la definición de un manejador de excepciones que estará habilitado durante toda la vida del `Reactor`: + + def exceptionHandler: PartialFunction[Exception, Unit] + +Este manejador de excepciones (`exceptionHandler`) retorna una función parcial que se utiliza para gestionar excepciones que no hayan sido tratadas de ninguna otra manera. Siempre que una excepción se propague fuera del método `act` de un `Reactor` el manejador anterior será aplicado a dicha excepción, permitiendo al actor ejecutar código de limpieza antes de que se termine. Nótese que la visibilidad de `exceptionHandler` es `protected`. + +El manejo de excepciones mediante el uso de `exceptionHandler` encaja a la perfección con las estructuras de control utilizadas para programas con el método `react`. Siempre que una excepción es manejada por la función parcial retornada por `excepctionHandler`, la ejecución continua con la "closure" actual: + + loop { + react { + case Msg(data) => + if (cond) // process data + else throw new Exception("cannot process data") + } + } + +Assumiendo que `Reactor` sobreescribe el atributo `exceptionHandler`, tras el lanzamiento de una excepción en el cuerpo del método `react`, y una vez ésta ha sido gestionada, la ejecución continua con la siguiente iteración del bucle. + +### The ReplyReactor trait + +El trait `ReplyReactor` extiende `Reactor[Any]` y sobrescribe y/o añade los siguientes métodos: + +- El método `!` es sobrescrito para obtener una referencia al actor + actual (el emisor). Junto al mensaje actual, la referencia a dicho + emisor es enviada al buzón del actor receptor. Este último dispone de + acceso al emisor del mensaje mediante el uso del método `sender` (véase más abajo). + +- El método `forward` es sobrescrito para obtener una referencia al emisor + del mensaje que actualmente está siendo procesado. Junto con el mensaje + actual, esta referencia es enviada como el emisor del mensaje actual. + Como consuencia de este hecho, `forward` nos permite reenviar mensajes + en nombre de actores diferentes al actual. + +- El método (añadido) `sender` retorna el emisor del mensaje que está siendo + actualmente procesado. Puesto que un mensaje puede haber sido reenviado, + `sender` podría retornar un actor diferente al que realmente envió el mensaje. + +- El método (añadido) `reply` envía una respuesta al emisor del último mensaje. + `reply` también es utilizado para responder a mensajes síncronos o a mensajes + que han sido enviados mediante un "future" (ver más adelante). + +- El método (añadido) `!?` ofrece un *mecanismo síncrono de envío de mensajes*. + La invocación de `!?` provoca que el actor emisor del mensaje se bloquee hasta + que se recibe una respuesta, momento en el cual retorna dicha respuesta. Existen + dos variantes sobrecargadas. La versión con dos parámetros recibe un argumento + adicional que representa el tiempo de espera (medido en milisegundos) y su tipo + de retorno es `Option[Any]` en lugar de `Any`. En caso de que el emisor no + reciba una respuesta en el periodo de espera establecido, el método `!?` retornará + `None`; en otro caso retornará la respuesta recibida recubierta con `Some`. + +- Los métodos (añadidos) `!!` son similares al envío síncrono de mensajes en el sentido de + que el receptor puede enviar una respuesta al emisor del mensaje. Sin embargo, en lugar + de bloquear el actor emisor hasta que una respuesta es recibida, retornan una instancia de + `Future`. Esta última puede ser utilizada para recuperar la respuesta del receptor una + vez se encuentre disponible; asimismo puede ser utilizada para comprobar si la respuesta + está disponible sin la necesidad de bloquear el emisor. Existen dos versiones sobrecargadas. + La versión que acepta dos parámetros recibe un argumento adicional de tipo + `PartialFunction[Any, A]`. Esta función parcial es utilizada para realizar el post-procesado de + la respuesta del receptor. Básicamente, `!!` retorna un "future" que aplicará la anterior + función parcial a la repuesta (una vez recibida). El resultado del "future" es el resultado + de este post-procesado. + +- El método (añadido) `reactWithin` permite llevar a cabo la recepción de mensajes en un periodo + determinado de tiempo. En comparación con el método `react`, recibe un parámetro adicional, + `msec`, el cual representa el periodo de tiempo, expresado en milisegundos, hasta que el patrón `TIMEOUT` + es satisfecho (`TIMEOUT` es un "case object" presente en el paquete `scala.actors`). Ejemplo: + + reactWithin(2000) { + case Answer(text) => // process text + case TIMEOUT => println("no answer within 2 seconds") + } + +- El método `reactWithin` también permite realizar accesos no bloqueantes al buzón. Si + especificamos un tiempo de espera de 0 milisegundos, primeramente el buzón será escaneado + en busca de un mensaje que concuerde. En caso de que no exista ningún mensaje concordante + tras el primer escaneo, el patrón `TIMEOUT` será satisfecho. Por ejemplo, esto nos permite + recibir determinado tipo de mensajes donde unos tienen una prioridad mayor que otros: + + reactWithin(0) { + case HighPriorityMsg => // ... + case TIMEOUT => + react { + case LowPriorityMsg => // ... + } + } + + En el ejemplo anterior, el actor procesa en primer lugar los mensajes `HighPriorityMsg` aunque + exista un mensaje `LowPriorityMsg` más antiguo en el buzón. El actor sólo procesará mensajes + `LowPriorityMsg` en primer lugar en aquella situación donde no exista ningún `HighProrityMsg` + en el buzón. + +Adicionalmente, el tipo `ReplyActor` añade el estado de ejecución `Actor.State.TimedSuspended`. Un actor suspendido, esperando la recepción de un mensaje mediante el uso de `reactWithin` se encuentra en dicho estado. + +### El trait Actor + +El trait `Actor` extiende de `ReplyReactor` añadiendo y/o sobrescribiendo los siguientes miembros: + +- El método (añadido) `receive` se comporta del mismo modo que `react`, con la excepción + de que puede retornar un resultado. Este hecho se ve reflejado en la definición del tipo, + que es polimórfico en el tipo del resultado: `def receive[R](f: PartialFunction[Any, R]): R`. + Sin embargo, la utilización de `receive` hace que el uso del actor + sea más pesado, puesto que el hilo subyacente es bloqueado mientras + el actor está esperando por la respuesta. El hilo bloqueado no está + disponible para ejecutar otros actores hasta que la invocación del + método `receive` haya retornado. + +- El método (añadido) `link` permite a un actor enlazarse y desenlazarse de otro + actor respectivamente. El proceso de enlazado puede utilizarse para monitorizar + y responder a la terminación de un actor. En particular, el proceso de enlazado + afecta al comportamiento mostrado en la ejecución del método `exit` tal y como + se escribe en el la documentación del API del trait `Actor`. + +- El atributo `trapExit` permite responder a la terminación de un actor enlazado, + independientemente de los motivos de su terminación (es decir, carece de importancia + si la terminación del actor es normal o no). Si `trapExit` toma el valor cierto en + un actor, este nunca terminará por culpa de los actores enlazados. En cambio, siempre + y cuando uno de sus actores enlazados finalice, recibirá un mensaje de tipo `Exit`. + `Exit` es una "case class" que presenta dos atributos: `from` referenciando al actor + que termina y `reason` conteniendo los motivos de la terminación. + +#### Terminación y estados de ejecución + +Cuando la ejecución de un actor finaliza, el motivo de dicha terminación puede ser +establecida de manera explícita mediante la invocación de la siguiente variante +del método `exit`: + + def exit(reason: AnyRef): Nothing + +Un actor cuyo estado de terminación es diferente del símbolo `'normal` propaga +los motivos de su terminación a todos aquellos actores que se encuentren enlazados +a él. Si el motivo de la terminación es una excepción no controlada, el motivo de +finalización será una instancia de la "case class" `UncaughtException`. + +El trait `Actor` incluye dos nuevos estados de ejecución. Un actor que se encuentra +esperando la recepción de un mensaje mediante la utilización del método `receive` se +encuentra en el método `Actor.State.Blocked`. Un actor esperado la recepción de un +mensaje mediante la utilización del método `receiveWithin` se encuentra en el estado +`Actor.State.TimeBlocked`. + +## Estructuras de control + +El trait `Reactor` define una serie de estructuras de control que simplifican el mecanismo +de programación con la función sin retorno `react`. Normalmente, una invocación al método +`react` no retorna nunca. Si el actor necesita ejecutar código a continuación de la invocación +anterior, tendrá que pasar, de manera explícita, dicho código al método `react` o utilizar +algunas de las estructuras que encapsulan este comportamiento. + +La estructura de control más basica es `andThen`. Permite registrar una `closure` que será +ejecutada una vez el actor haya terminado la ejecución de todo lo demas. + + actor { + { + react { + case "hello" => // processing "hello" + }: Unit + } andThen { + println("hi there") + } + } + +Por ejemplo, el actor anterior imprime un saludo tras realizar el procesado +del mensaje `hello`. Aunque la invocación del método `react` no retorna, +podemos utilizar `andThen` para registrar el código encargado de imprimir +el saludo a continuación de la ejecución del actor. + +Nótese que existe una *atribución de tipo* a continuación de la invocación +de `react` (`:Unit`). Básicamente, nos permite tratar el resultado de +`react` como si fuese de tipo `Unit`, lo cual es legal, puesto que el resultado +de una expresión siempre se puede eliminar. Es necesario llevar a cabo esta operación +dado que `andThen` no puede ser un miembro del tipo `Unit`, que es el tipo del resultado +retornado por `react`. Tratando el tipo de resultado retornado por `react` como +`Unit` permite llevar a cabo la aplicación de una conversión implícita la cual +hace que el miembro `andThen` esté disponible. + +El API ofrece unas cuantas estructuras de control adicionales: + +- `loop { ... }`. Itera de manera indefinidia, ejecutando el código entre +las llaves en cada una de las iteraciones. La invocación de `react` en el +cuerpo del bucle provoca que el actor se comporte de manera habitual ante +la llegada de un nuevo mensaje. Posteriormente a la recepción del mensaje, +la ejecución continua con la siguiente iteración del bucle actual. + +- `loopWhile (c) { ... }`. Ejecuta el código entre las llaves mientras la +condición `c` tome el valor `true`. La invocación de `react` en el cuerpo +del bucle ocasiona el mismo efecto que en el caso de `loop`. + +- `continue`. Continua con la ejecución de la closure actual. La invocación +de `continue` en el cuerpo de un `loop`o `loopWhile` ocasionará que el actor +termine la iteración en curso y continue con la siguiente. Si la iteración en +curso ha sido registrada utilizando `andThen`, la ejecución continua con la +segunda "closure" pasada como segundo argumento a `andThen`. + +Las estructuras de control pueden ser utilizadas en cualquier parte del cuerpo +del método `act` y en los cuerpos de los métodos que, transitivamente, son +llamados por `act`. Aquellos actores creados utilizando la sintáxis `actor { ... }` +pueden importar las estructuras de control desde el objeto `Actor`. + +#### Futures + +Los traits `RepyActor` y `Actor` soportan operaciones de envío de mensajes +(métodos `!!`) que, de manera inmediata, retornan un *future*. Un *future*, +es una instancia del trait `Future` y actúa como un manejador que puede +ser utilizado para recuperar la respuesta a un mensaje "send-with-future". + +El emisor de un mensaje "send-with-future" puede esperar por la respuesta del +future *aplicando* dicha future. Por ejemplo, el envío de un mensaje mediante +`val fut = a !! msg` permite al emisor esperar por el resultado del future +del siguiente modo: `val res = fut()`. + +Adicionalmente, utilizando el método `isSet`, un `Future` puede ser consultado +de manera no bloqueante para comprobar si el resultado está disponible. + +Un mensaje "send-with-future" no es el único modo de obtener una referencia a +un future. Estos pueden ser creados utilizando el método `future`. En el siguiente +ejemplo, `body` se ejecuta de manera concurrente, retornando un future como +resultado. + + val fut = Future { body } + // ... + fut() // wait for future + +Lo que hace especial a los futures en el contexto de los actores es la posibilidad +de recuperar su resultado utilizando las operaciones estándar de actores de +recepción de mensajes como `receive`, etc. Además, es posible utilizar las operaciones +basadas en eventos `react`y `reactWithin`. Esto permite a un actor esperar por el +resultado de un future sin la necesidad de bloquear el hilo subyacente. + +Las operaciones de recepción basadas en actores están disponibles a través del +atributo `inputChannel` del future. Dado un future de tipo `Future[T]`, el tipo +de `inputChannel` es `InputChannel[T]`. Por ejemplo: + + val fut = a !! msg + // ... + fut.inputChannel.react { + case Response => // ... + } + +## Canales + +Los canales pueden ser utilizados para simplificar el manejo de mensajes +que presentan tipos diferentes pero que son enviados al mismo actor. La +jerarquía de canales se divide en `OutputChannel` e `InputChannel`. + +Los `OutputChannel` pueden ser utilizados para enviar mensajes. Un +`OutputChannel` `out` soporta las siguientes operaciones: + +- `out ! msg`. Envía el mensaje `msg` a `out` de manera asíncrona. Cuando `msg` + es enviado directamente a un actor se incluye un referencia al actor emisor + del mensaje. + +- `out forward msg`. Reenvía el mensaje `msg` a `out` de manera asíncrona. + El actor emisor se determina en el caso en el que `msg` es reenviado a + un actor. + +- `out.receiver`. Retorna el único actor que está recibiendo mensajes que están + siendo enviados al canal `out`. + +- `out.send(msg, from)`. Envía el mensaje `msg` a `out` de manera asíncrona, + proporcionando a `from` como el emisor del mensaje. + +Nótese que el trait `OutputChannel` tiene un parámetro de tipo que especifica el +tipo de los mensajes que pueden ser enviados al canal (utilizando `!`, `forward`, +y `send`). Este parámetro de tipo es contra-variante: + + trait OutputChannel[-Msg] + +Los actores pueden recibir mensajes de un `InputChannel`. Del mismo modo que +`OutputChannel`, el trait `InputChannel` presenta un parámetro de tipo que +especifica el tipo de mensajes que pueden ser recibidos por el canal. En este caso, +el parámetro de tipo es covariante: + + trait InputChannel[+Msg] + +Un `InputChannel[Msg]` `in` soportal las siguientes operaciones. + +- `in.receive { case Pat1 => ... ; case Patn => ... }` (y de manera similar, + `in.receiveWithin`) recibe un mensaje proveniente de `in`. La invocación + del método `receive` en un canal de entrada presenta la misma semántica + que la operación estándar de actores `receive`. La única diferencia es que + la función parcial pasada como argumento tiene tipo `PartialFunction[Msg, R]` + donde `R` es el tipo de retorno de `receive`. + +- `in.react { case Pat1 => ... ; case Patn => ... }` (y de manera similar, + `in.reactWithin`). Recibe un mensaje de `in` utilizando la operación basada en + eventos `react`. Del mismo modo que la operación `react` en actores, el tipo + de retorno es `Nothing`, indicando que las invocaciones de este método nunca + retornan. Al igual que la operación `receive` anterior, la función parcial + que se pasa como argumento presenta un tipo más específico: + + PartialFunction[Msg, Unit] + +### Creando y compartiendo canales + +Los canales son creados utilizando la clase concreta `Channel`. Esta clase extiende +de `InputChannel` y `OutputChannel`. Un canal pueden ser compartido haciendo dicho +canal visible en el ámbito de múltiples actores o enviándolo como mensaje. + +El siguiente ejemplo muestra la compartición mediante publicación en ámbitos: + + actor { + var out: OutputChannel[String] = null + val child = actor { + react { + case "go" => out ! "hello" + } + } + val channel = new Channel[String] + out = channel + child ! "go" + channel.receive { + case msg => println(msg.length) + } + } + +La ejecución de este ejemplo imprime la cadena "5" en la consola. Nótese que el +actor `child` únicamente tiene acceso a `out`, que es un `OutputChannel[String]`. +La referencia al canal, la cual puede ser utilizada para llevar a cabo la recepción +de mensajes, se encuentra oculta. Sin embargo, se deben tomar precauciones y +asegurarse que el canal de salida es inicializado con un canal concreto antes de que +`child` le envíe ningún mensaje. En el ejemplo que nos ocupa, esto es llevado a cabo +mediante el mensaje "go". Cuando se está recibiendo de `channel` utilizando el método +`channel.receive` podemos hacer uso del hecho que `msg` es de tipo `String`, y por +lo tanto tiene un miembro `length`. + +Una alternativa a la compartición de canales es enviarlos a través de mensajes. +El siguiente fragmento de código muestra un sencillo ejemplo de aplicación: + + case class ReplyTo(out: OutputChannel[String]) + + val child = actor { + react { + case ReplyTo(out) => out ! "hello" + } + } + + actor { + val channel = new Channel[String] + child ! ReplyTo(channel) + channel.receive { + case msg => println(msg.length) + } + } + +La "case class" `ReplyTo` es un tipo de mensajes que utilizamos para distribuir +una referencia a un `OutputChannel[String]`. Cuando el actor `child` recibe un +mensaje de tipo `ReplyTo` éste envía una cadena a su canal de salida. El segundo +actor recibe en el canal del mismo modo que anteriormente. + +## Planificadores + +Un `Reactor`(o una instancia de uno de sus subtipos) es ejecutado utilizando un +*planificador*. El trait `Reactor` incluye el miembro `scheduler` el cual retorna el +planificador utilizado para ejecutar sus instancias: + + def scheduler: IScheduler + +La plataforma de ejecución ejecuta los actores enviando tareas al planificador mediante +el uso de los métodos `execute` definidos en el trait `IScheduler`. La mayor parte +del resto de métodos definidos en este trait únicamente adquieren cierto protagonismo +cuando se necesita implementar un nuevo planificador desde cero; algo que no es necesario +en muchas ocasiones. + +Los planificadores por defecto utilizados para ejecutar instancias de `Reactor` y +`Actor` detectan cuando los actores han finalizado su ejecución. En el momento que esto +ocurre, el planificador se termina a si mismo (terminando con cualquier hilo que estuviera +en uso por parte del planificador). Sin embargo, algunos planificadores como el +`SingleThreadedScheduler` (definido en el paquete `scheduler`) necesita ser terminado de +manera explícita mediante la invocación de su método `shutdown`). + +La manera más sencilla de crear un planificador personalizado consisten en extender la clase +`SchedulerAdapter`, implementando el siguiente método abstracto: + + def execute(fun: => Unit): Unit + +Por norma general, una implementación concreata utilizaría un pool de hilos para llevar a cabo +la ejecución del argumento por nombre `fun`. + +## Actores remotos + +Esta sección describe el API de los actores remotos. Su principal interfaz es el objecto +[`RemoteActor`](http://www.scala-lang.org/api/2.9.1/scala/actors/remote/RemoteActor$.html) definido +en el paquete `scala.actors.remote`. Este objeto facilita el conjunto de métodos necesarios para crear +y establecer conexiones a instancias de actores remotos. En los fragmentos de código que se muestran a +continuación se asume que todos los miembros de `RemoteActor` han sido importados; la lista completa +de importaciones utilizadas es la siguiente: + + import scala.actors._ + import scala.actors.Actor._ + import scala.actors.remote._ + import scala.actors.remote.RemoteActor._ + +### Iniciando actores remotos + +Un actore remot es identificado de manera unívoca por un +[`Symbol`](http://www.scala-lang.org/api/2.9.1/scala/Symbol.html). Este símbolo es único para la instancia +de la máquina virual en la que se está ejecutando un actor. Un actor remoto identificado con el nombre +`myActor` puede ser creado del siguiente modo. + + class MyActor extends Actor { + def act() { + alive(9000) + register('myActor, self) + // ... + } + } + +Nótese que el nombre únicamente puede ser registrado con un único actor al mismo tiempo. +Por ejemplo, para registrar el actor *A* como `'myActor` y posteriormente registrar otro +actor *B* como `'myActor`, debería esperar hasta que *A* haya finalizado. Este requisito +aplica a lo largo de todos los puertos, por lo que registrando a *B* en un puerto diferente +no sería suficiente. + +### Connecting to remote actors + +Establecer la conexión con un actor remoto es un proceso simple. Para obtener una referencia remota +a un actor remoto que está ejecutándose en la máquina `myMachine` en el puerto 8000 con el nombre +`'anActor`, tendremos que utilizar `select`del siguiente modo: + + val myRemoteActor = select(Node("myMachine", 8000), 'anActor) + +El actor retornado por `select` es de tipo `AbstractActor`, que proporciona esencialmente el mismo +interfaz que un actor normal, y por lo tanto es compatible con las habituales operaciones de envío +de mensajes: + + myRemoteActor ! "Hello!" + receive { + case response => println("Response: " + response) + } + myRemoteActor !? "What is the meaning of life?" match { + case 42 => println("Success") + case oops => println("Failed: " + oops) + } + val future = myRemoteActor !! "What is the last digit of PI?" + +Nótese que la operación `select` es perezosa; no inicializa ninguna conexión de red. Simplemente crea +una nueva instancia de `AbstractActor` que está preparada para iniciar una nueva conexión de red en el +momento en que sea necesario (por ejemplo cuando el método '!' es invocado). diff --git a/_es/overviews/core/string-interpolation.md b/_es/overviews/core/string-interpolation.md new file mode 100644 index 0000000000..392a0b68ac --- /dev/null +++ b/_es/overviews/core/string-interpolation.md @@ -0,0 +1,132 @@ +--- +layout: singlepage-overview +title: Interpolación de cadenas + +partof: string-interpolation + +language: es + +discourse: false +--- + +**Josh Suereth** + +**Traducción e interpretación: Miguel Ángel Pastor Olivar** + +## Introducción + +Desde la versión 2.10.0, Scala ofrece un nuevo mecanismo para la creación de cadenas a partir de nuestros datos mediante la técnica de interpolación de cadenas. +Este nuevo mecanismo permite a los usuarios incluir referencias a variables de manera directa en cadenas de texto "procesadas". Por ejemplo: + + val name = "James" + println(s"Hello, $name") // Hello, James + +En el ejemplo anterior, el literal `s"Hello, $name"` es una cadena "procesada". Esto significa que el compilador debe realizar un trabajo adicional durante el tratamiento de dicha cadena. Una cadena "procesada" se denota mediante un conjunto de caracteres que preceden al símbolo `"`. La interpolación de cadenas ha sido introducida por [SIP-11](http://docs.scala-lang.org/sips/pending/string-interpolation.html), el cual contiene todos los detalles de implementación. + +## Uso + +Scala ofrece tres métodos de interpolación de manera nativa: `s`, `f` and `raw`. + +### Interpolador `s` + +El uso del prefijo `s` en cualquier cadena permite el uso de variables de manera directa dentro de la propia cadena. Ya hemos visto el ejemplo anterior: + + val name = "James" + println(s"Hello, $name") // Hello, James + +`$name` se anida dentro de la cadena "procesada" de tipo `s`. El interpolador `s` sabe como insertar el valor de la variable `name` en lugar indicado, dando como resultado la cadena `Hello, James`. Mediante el uso del interpolador `s`, cualquier nombre disponible en el ámbito puede ser utilizado dentro de la cadena. + +Las interpolaciones pueden recibir expresiones arbitrarias. Por ejemplo: + + println(s"1 + 1 = ${1 + 1}") + +imprimirá la cadena `1 + 1 = 2`. Cualquier expresión puede ser embebida en `${}` + +### Interpolador `f` + +Prefijando `f` a cualquier cadena permite llevar a cabo la creación de cadenas formateadas, del mismo modo que `printf` es utilizado en otros lenguajes. Cuando utilizamos este interpolador, todas las referencias a variables deben estar seguidas por una cadena de formateo que siga el formato `printf-`, como `%d`. Veamos un ejemplo: + + val height = 1.9d + val name = "James" + println(f"$name%s is $height%2.2f meters tall") // James is 1.90 meters tall + +El interpolador `f` es seguro respecto a tipos. Si pasamos un número real a una cadena de formateo que sólo funciona con números enteros, el compilador emitirá un error. Por ejemplo: + + val height: Double = 1.9d + + scala> f"$height%4d" + :9: error: type mismatch; + found : Double + required: Int + f"$height%4d" + ^ + +El interpolador `f` hace uso de las utilidades de formateo de cadenas disponibles en java. Los formatos permitidos tras el carácter `%` son descritos en [Formatter javadoc](http://docs.oracle.com/javase/1.6.0/docs/api/java/util/Formatter.html#detail). Si el carácter `%` no aparece tras la definición de una variable, `%s` es utilizado por defecto. + +### Interpolador `raw` + +El interpolador `raw` difiere del interpolador `s` en que el primero no realiza el escapado de literales contenidos en la cadena. A continuación se muestra un ejemplo de una cadena procesada: + + scala> s"a\nb" + res0: String = + a + b + +En el ejemplo anterior, el interpolador `s` ha reemplazado los caracteres `\n` con un salto de linea. El interpolador `raw` no llevará a cabo esta acción: + + scala> raw"a\nb" + res1: String = a\nb + +Esta cadena de interpolación es muy útil cuando se desea evitar que expresiones como `\n` se conviertan en un salto de línea. + +Adicionalmente a los interpoladores ofrecidos de serie por Scala, nosotros podremos definir nuestras propias cadenas de interpolación. + +## Uso avanzado + +En Scala, todas las cadenas "procesadas" son simples transformaciones de código. En cualquier punto en el que el compilador encuentra una cadena de texto con la forma: + + id"string content" + +la transforma en la llamada a un método (`id`) sobre una instancia de [StringContext](http://www.scala-lang.org/api/current/index.html#scala.StringContext). Este método también puede estar disponible en un ámbito implícito. Para definiir nuestra propia cadena de interpolación simplemente necesitamos crear una clase implícita que añada un nuevo método a la clase `StringContext`. A continuación se muestra un ejemplo: + + // Note: We extends AnyVal to prevent runtime instantiation. See + // value class guide for more info. + implicit class JsonHelper(val sc: StringContext) extends AnyVal { + def json(args: Any*): JSONObject = sys.error("TODO - IMPLEMENT") + } + + def giveMeSomeJson(x: JSONObject): Unit = ... + + giveMeSomeJson(json"{ name: $name, id: $id }") + +En este ejemplo, estamos intentando crear una cadena JSON mediante el uso de la interpolación de cadenas. La clase implícita `JsonHelper` debe estar disponible en el ámbito donde deseemos utilizar esta sintaxis, y el método `json` necesitaría ser implementado completamente. Sin embargo, el resutlado de dicha cadena de formateo no sería una cadena sino un objeto de tipo `JSONObject` + +Cuando el compilador encuentra la cadena `json"{ name: $name, id: $id }"` reescribe la siguiente expresión: + + new StringContext("{ name: ", ", id: ", " }").json(name, id) + +La clase implícita es utilizada para reescribir el fragmento anterior de la siguiente forma: + + new JsonHelper(new StringContext("{ name: ", ", id: ", " }")).json(name, id) + +De este modo, el método `json` tiene acceso a las diferentes partes de las cadenas así como cada una de las expresiones. Una implementación simple, y con errores, de este método podría ser: + + implicit class JsonHelper(val sc: StringContext) extends AnyVal { + def json(args: Any*): JSONObject = { + val strings = sc.parts.iterator + val expressions = args.iterator + var buf = new StringBuffer(strings.next) + while(strings.hasNext) { + buf append expressions.next + buf append strings.next + } + parseJson(buf) + } + } + +Cada una de las diferentes partes de la cadena "procesada" son expuestas en el atributo `parts` de la clase `StringContext`. Cada uno de los valores de la expresión se pasa en el argumento `args` del método `json`. Este método acepta dichos argumentos y genera una gran cadena que posteriormente convierte en un objecto de tipo JSON. Una implementación más sofisticada podría evitar la generación de la cadena anterior y llevar a cabo de manera directa la construcción del objeto JSON a partir de las cadenas y los valores de la expresión. + + +## Limitaciones + +La interpolación de cadenas no funciona con sentencias "pattern matching". Esta funcionalidad está planificada para su inclusión en la versión 2.11 de Scala. diff --git a/_es/overviews/parallel-collections/architecture.md b/_es/overviews/parallel-collections/architecture.md new file mode 100644 index 0000000000..fa8d146b00 --- /dev/null +++ b/_es/overviews/parallel-collections/architecture.md @@ -0,0 +1,118 @@ +--- +layout: multipage-overview +title: Arquitectura de la librería de colecciones paralelas de Scala + +discourse: false + +partof: parallel-collections +overview-name: Parallel Collections + +num: 5 +language: es +--- + +Del mismo modo que la librería de colecciones secuencial, la versión paralela +ofrece un gran número de operaciones uniformes sobre un amplio abanico de +implementaciones de diversas colecciones. Siguiendo la filosofía de la versión +secuencial, se pretende evitar la duplicación de código mediante el uso de +"plantillas" de colecciones paralelas, las cuales permiten que las operaciones +sean definidas una sola vez, pudiendo ser heredadas por las diferentes implementaciones. + +El uso de este enfoque facilita de manera notable el **mantenimiento** y la **extensibilidad** +de la librería. En el caso del primero -- gracias a que cada operación se implementa una única +vez y es heredada por todas las colecciones, el mantenimiento es más sencillo y robusto; la +corrección de posibles errores se progaga hacia abajo en la jerarquía de clases en lugar de +duplicar las implementaciones. Del mismo modo, los motivos anteriores facilitan que la librería al completo sea +más sencilla de extender -- la mayor parte de las nuevas colecciones podrán heredar la mayoría de sus +operaciones. + +## Core Abstractions + +El anteriormente mencionado trait "template" implementa la mayoría de las operaciones en términos +de dos abstracciones básicas -- `Splitter`s y `Combiner`s + +### Splitters + +El trabajo de un `Splitter`, como su propio nombre indica, consiste en dividir una +colección paralela en una partición no trivial de sus elementos. La idea principal +es dividir dicha colección en partes más pequeñas hasta alcanzar un tamaño en el que +se pueda operar de manera secuencial sobre las mismas. + + trait Splitter[T] extends Iterator[T] { + def split: Seq[Splitter[T]] + } + +Curiosamente, los `Splitter` son implementados como `Iterator`s, por lo que además de +particionar, son utilizados por el framework para recorrer una colección paralela +(dado que heredan los métodos `next`y `hasNext` presentes en `Iterator`). +Este "splitting iterator" presenta una característica única: su método `split` +divide `this` (recordad que un `Splitter` es de tipo `Iterator`) en un conjunto de +`Splitter`s cada uno de los cuales recorre un subconjunto disjunto del total de +elementos presentes en la colección. Del mismo modo que un `Iterator` tradicional, +un `Splitter` es invalidado una vez su método `split` es invocado. + +Generalmente las colecciones son divididas, utilizando `Splitter`s, en subconjuntos +con un tamaño aproximadamente idéntico. En situaciones donde se necesitan un tipo de +particiones más arbitrarias, particularmente en las secuencias paralelas, se utiliza un +`PreciseSplitter`, el cual hereda de `Splitter` y define un meticuloso método de + particionado: `psplit`. + +### Combiners + +Podemos ver los `Combiner`s como una generalización de los `Builder`, provenientes +de las secuencias en Scala. Cada una de las colecciones paralelas proporciona un +`Combiner` independiente, del mismo modo que cada colección secuencial ofrece un +`Builder`. + +Mientras que en las colecciones secuenciales los elementos pueden ser añadidos a un +`Builder`, y una colección puede ser construida mediante la invocación del método +`result`, en el caso de las colecciones paralelas los `Combiner` presentan un método +llamado `combine` que acepta otro `Combiner`como argumento y retona un nuevo `Combiner`, +el cual contiene la unión de ambos. Tras la invocación del método `combine` ambos +`Combiner` son invalidados. + + trait Combiner[Elem, To] extends Builder[Elem, To] { + def combine(other: Combiner[Elem, To]): Combiner[Elem, To] + } + +Los dos parametros de tipo `Elem` y `To` presentes en el fragmento de código anterior +representan el tipo del elemento y de la colección resultante respectivamente. + +_Nota:_ Dados dos `Combiner`s, `c1` y `c2` donde `c1 eq c2` toma el valor `true` +(esto implica que son el mismo `Combiner`), la invocación de `c1.combine(c2)` +simplemente retona el `Combiner` receptor de la llamada, `c1` en el ejemplo que +nos ocupa. + +## Hierarchy + +La librería de colecciones paralelas está inspirada en gran parte en el diseño +de la librería de colecciones secuenciales -- de hecho, "replican" los correspondientes +traits presentes en el framework de colecciones secuenciales, tal y como se muestra +a continuación. + +[]({{ site.baseurl }}/resources/images/parallel-collections-hierarchy.png) + +
Jerarquía de clases de las librerías de colecciones secuenciales y paralelas de Scala
+
+ +El objetivo es, por supuesto, integrar tan estrechamente como sea posible las colecciones +secuenciales y paralelas, permitendo llevar a cabo una sustitución directa entre ambos +tipos de colecciones. + +Con el objetivo de tener una referencia a una colección que podría ser secuencial o +paralela (de modo que sea posible "intercambiar" la colección paralela y la secuencial +mediante la invocación de `par` y `seq` respectivamente), necesitamos un supertipo común a +los tipos de las dos colecciones. Este es el origen de los traits "generales" mostrados +anteriormente: `GenTraversable`, `GenIterable`, `GenSeq`, `GenMap` and `GenSet`, los cuales +no garantizan el orden ni el "one-at-a-time" del recorrido. Los correspondientes traits paralelos +o secuenciales heredan de los anteriores. Por ejemplo, el tipo `ParSeq`y `Seq` son subtipos +de una secuencia más general: `GenSeq`, pero no presentan un relación de herencia entre ellos. + +Para una discusión más detallada de la jerarquía de clases compartida por las colecciones secuenciales y +paralelas referirse al artículo \[[1][1]\] + +## References + +1. [On a Generic Parallel Collection Framework, Aleksandar Prokopec, Phil Bawgell, Tiark Rompf, Martin Odersky, June 2011][1] + +[1]: http://infoscience.epfl.ch/record/165523/files/techrep.pdf "flawed-benchmark" diff --git a/_es/overviews/parallel-collections/concrete-parallel-collections.md b/_es/overviews/parallel-collections/concrete-parallel-collections.md new file mode 100644 index 0000000000..f8c65f1e3b --- /dev/null +++ b/_es/overviews/parallel-collections/concrete-parallel-collections.md @@ -0,0 +1,174 @@ +--- +layout: multipage-overview +title: Clases Concretas de las Colecciones Paralelas + +discourse: false + +partof: parallel-collections +overview-name: Parallel Collections + +num: 2 +language: es +--- + +## Array Paralelo + +Una secuencia [ParArray](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/parallel/mutable/ParArray.html) contiene un conjunto de elementos contiguos lineales. Esto significa que los elementos pueden ser accedidos y actualizados (modificados) eficientemente al modificar la estructura subyacente (un array). El iterar sobre sus elementos es también muy eficiente por esta misma razón. Los Arrays Paralelos son como arrays en el sentido de que su tamaño es constante. + + scala> val pa = scala.collection.parallel.mutable.ParArray.tabulate(1000)(x => 2 * x + 1) + pa: scala.collection.parallel.mutable.ParArray[Int] = ParArray(1, 3, 5, 7, 9, 11, 13,... + + scala> pa reduce (_ + _) + res0: Int = 1000000 + + scala> pa map (x => (x - 1) / 2) + res1: scala.collection.parallel.mutable.ParArray[Int] = ParArray(0, 1, 2, 3, 4, 5, 6, 7,... + +Internamente, para partir el array para que sea procesado de forma paralela se utilizan [splitters]({{ site.baseurl }}/es/overviews/parallel-collections/architecture.html#core_abstractions) (o "partidores"). El Splitter parte y crea dos nuevos splitters con sus índices actualizados. A continuación son utilizados los [combiners]({{ site.baseurl }}/es/overviews/parallel-collections/architecture.html#core_abstractions) (o "combinadores"), que necesitan un poco más de trabajo. Ya que en la mayoría de los métodos transformadores (ej: `flatMap`, `filter`, `takeWhile`, etc.) previamente no es sabido la cantidad de elementos (y por ende, el tamaño del array), cada combiner es esencialmente una variante de un array buffer con un tiempo constante de la operación `+=`. Diferentes procesadores añaden elementos a combiners de arrays separados, que después son combinados al encadenar sus arrays internos. El array subyacente se crea en memoria y se rellenan sus elementos después que el número total de elementos es conocido. Por esta razón, los métodos transformadores son un poco más caros que los métodos de acceso. También, nótese que la asignación de memoria final procede secuencialmente en la JVM, lo que representa un cuello de botella si la operación de mapeo (el método transformador aplicado) es en sí económico (en términos de procesamiento). + +Al invocar el método `seq`, los arrays paralelos son convertidos al tipo de colección `ArraySeq`, que vendría a ser la contraparte secuencial del `ParArray`. Esta conversión es eficiente, y el `ArraySeq` utiliza a bajo nivel el mismo array que había sido obtenido por el array paralelo. + + +## Vector Paralelo + +Un [ParVector](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/parallel/immutable/ParVector.html) es una secuencia inmutable con un tiempo de acceso y modificación logarítimico bajo a constante. + + scala> val pv = scala.collection.parallel.immutable.ParVector.tabulate(1000)(x => x) + pv: scala.collection.parallel.immutable.ParVector[Int] = ParVector(0, 1, 2, 3, 4, 5, 6, 7, 8, 9,... + + scala> pv filter (_ % 2 == 0) + res0: scala.collection.parallel.immutable.ParVector[Int] = ParVector(0, 2, 4, 6, 8, 10, 12, 14, 16, 18,... + +Los vectores inmutables son representados por árboles, por lo que los splitters dividen pasándose subárboles entre ellos. Los combiners concurrentemente mantienen un vector de elementos y son combinados al copiar dichos elementos de forma "retardada". Es por esta razón que los métodos tranformadores son menos escalables que sus contrapartes en arrays paralelos. Una vez que las operaciones de concatenación de vectores estén disponibles en una versión futura de Scala, los combiners podrán usar dichas características y hacer más eficientes los métodos transformadores. + +Un vector paralelo es la contraparte paralela de un [Vector](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Vector.html) secuencial, por lo tanto la conversión entre estas dos estructuras se efectúa en tiempo constante. + +## Rango (Range) Paralelo + +Un [ParRange](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/parallel/immutable/ParRange.html) es una secuencia ordenada separada por intervalos iguales (ej: 1, 2, 3 o 1, 3, 5, 7). Un rango paralelo es creado de forma similar al [Rango](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Range.html) secuencial: + + scala> 1 to 3 par + res0: scala.collection.parallel.immutable.ParRange = ParRange(1, 2, 3) + + scala> 15 to 5 by -2 par + res1: scala.collection.parallel.immutable.ParRange = ParRange(15, 13, 11, 9, 7, 5) + +Tal como los rangos secuenciales no tienen constructores, los rangos paralelos no tienen [combiner]({{ site.baseurl }}/overviews/parallel-collections/architecture.html#core_abstractions)s. Mapear elementos de un rango paralelo produce un vector paralelo. Los rangos secuenciales y paralelos pueden ser convertidos de uno a otro utilizando los métodos `seq` y `par`. + + scala> (1 to 5 par) map ((x) => x * 2) + res2: scala.collection.parallel.immutable.ParSeq[Int] = ParVector(2, 4, 6, 8, 10) + + +## Tablas Hash Paralelas + +Las tablas hash paralelas almacenan sus elementos en un array subyacente y los almacenan en una posición determinada por el código hash del elemento respectivo. Las versiones mutables de los hash sets paralelos ([mutable.ParHashSet](http://www.scala-lang.org/api/{{ site.scala-version}}/scala/collection/parallel/mutable/ParHashSet.html)) y los hash maps paraleos ([mutable.ParHashMap](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/parallel/mutable/ParHashMap.html)) están basados en tablas hash. + + scala> val phs = scala.collection.parallel.mutable.ParHashSet(1 until 2000: _*) + phs: scala.collection.parallel.mutable.ParHashSet[Int] = ParHashSet(18, 327, 736, 1045, 773, 1082,... + + scala> phs map (x => x * x) + res0: scala.collection.parallel.mutable.ParHashSet[Int] = ParHashSet(2181529, 2446096, 99225, 2585664,... + +Los combiners de las tablas hash ordenan los elementos en posiciones de acuerdo a su código hash. Se combinan simplemente concatenando las estructuras que contienen dichos elementos. Una vez que la tabla hash final es construida (es decir, se invoca el método `result` del combiner), el array subyacente es rellenado y los elementos de las diferentes estructuras previas son copiados en paralelo a diferentes segmentos continuos del array de la tabla hash. + +Los "Mapas Hash" (Hash Maps) y los "Conjuntos Hash" (Hash Sets) secuenciales pueden ser convertidos a sus variantes paralelas usando el método `par`. Las tablas hash paralelas internamente necesitan de un mapa de tamaño que mantiene un registro del número de elementos en cada pedazo de la hash table. Lo que esto significa es que la primera vez que una tabla hash secuencial es convertida a una tabla hash paralela, la tabla es recorrida y el mapa de tamaño es creado - es por esta razón que la primera llamada a `par` requiere un tiempo lineal con respecto al tamaño total de la tabla. Cualquier otra modificación que le siga mantienen el estado del mapa de tamaño, por lo que conversiones sucesivas entre `par` y `seq` tienen una complejidad constante. El mantenimiento del tamaño del mapa puede ser habilitado y deshabilitado utilizando el método `useSizeMap` de la tabla hash. Es importante notar que las modificaciones en la tabla hash secuencial son visibles en la tabla hash paralela, y viceversa. + +## Hash Tries Paralelos + +Los Hash Tries paralelos son la contraparte paralela de los hash tries inmutables, que son usados para representar conjuntos y mapas inmutables de forma eficiente. Las clases involucradas son: [immutable.ParHashSet](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/parallel/immutable/ParHashSet.html) +y +[immutable.ParHashMap](http://www.scala-lang.org/api/{{ site.scala-version}}/scala/collection/parallel/immutable/ParHashMap.html). + + scala> val phs = scala.collection.parallel.immutable.ParHashSet(1 until 1000: _*) + phs: scala.collection.parallel.immutable.ParHashSet[Int] = ParSet(645, 892, 69, 809, 629, 365, 138, 760, 101, 479,... + + scala> phs map { x => x * x } sum + res0: Int = 332833500 + +De forma similar a las tablas hash paralelas, los [combiners]({{ site.baseurl }}/overviews/parallel-collections/architecture.html#core_abstractions) de los hash tries paralelos pre-ordenan los elementos en posiciones y construyen el hash trie resultante en paralelo al asignarle distintos grupos de posiciones a diferentes procesadores, los cuales contruyen los sub-tries independientemente. + +Los hash tries paralelos pueden ser convertidos hacia y desde hash tries secuenciales por medio de los métodos `seq` y `par`. + + +## Tries Paralelos Concurrentes + +Un [concurrent.TrieMap](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/concurrent/TrieMap.html) es un mapa thread-safe (seguro ante la utilización de múltiples hilos concurrentes) mientras que [mutable.ParTrieMap](http://www.scala-lang.org/api/{{ site.scala-version}}/scala/collection/parallel/mutable/ParTrieMap.html) es su contraparte paralela. Si bien la mayoría de las estructuras de datos no garantizan una iteración consistente si la estructura es modificada en medio de dicha iteración, los tries concurrentes garantizan que las actualizaciones sean solamente visibles en la próxima iteración. Esto significa que es posible mutar el trie concurrente mientras se está iterando sobre este, como en el siguiente ejemplo, que computa e imprime las raíces cuadradas de los números entre 1 y 99: + + scala> val numbers = scala.collection.parallel.mutable.ParTrieMap((1 until 100) zip (1 until 100): _*) map { case (k, v) => (k.toDouble, v.toDouble) } + numbers: scala.collection.parallel.mutable.ParTrieMap[Double,Double] = ParTrieMap(0.0 -> 0.0, 42.0 -> 42.0, 70.0 -> 70.0, 2.0 -> 2.0,... + + scala> while (numbers.nonEmpty) { + | numbers foreach { case (num, sqrt) => + | val nsqrt = 0.5 * (sqrt + num / sqrt) + | numbers(num) = nsqrt + | if (math.abs(nsqrt - sqrt) < 0.01) { + | println(num, nsqrt) + | numbers.remove(num) + | } + | } + | } + (1.0,1.0) + (2.0,1.4142156862745097) + (7.0,2.64576704419029) + (4.0,2.0000000929222947) + ... + + +Para ofrecer más detalles de lo que sucede bajo la superficie, los [Combiners]({{ site.baseurl }}/overviews/parallel-collections/architecture.html#core_abstractions) son implementados como `TrieMap`s --ya que esta es una estructura de datos concurrente, solo un combiner es construido para todo la invocación al método transformador y compartido por todos los procesadores. + +Al igual que todas las colecciones paralelas mutables, `TrieMap`s y la versión paralela, `ParTrieMap`s obtenidas mediante los métodos `seq` o `par` subyacentemente comparten la misma estructura de almacenamiento, por lo tanto modificaciones en una es visible en la otra. + + +## Características de desmpeño (performance) + +Performance de los tipo secuencia: + +| | head | tail | apply | update| prepend | append | insert | +| -------- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | +| `ParArray` | C | L | C | C | L | L | L | +| `ParVector` | eC | eC | eC | eC | eC | eC | - | +| `ParRange` | C | C | C | - | - | - | - | + +Performance para los sets y maps: + +| | lookup | add | remove | +| -------- | ---- | ---- | ---- | +| **inmutables** | | | | +| `ParHashSet`/`ParHashMap`| eC | eC | eC | +| **mutables** | | | | +| `ParHashSet`/`ParHashMap`| C | C | C | +| `ParTrieMap` | eC | eC | eC | + + +Los valores en las tablas de arriba se explican de la siguiente manera: + +| | | +| --- | ---- | +| **C** | La operación toma un tiempo constante (rápido) | +| **eC** | La opración toma tiempo efectivamente constante, pero esto puede depender de algunas suposiciones como el tamaño máximo de un vector o la distribución de las claves hash. | +| **aC** | La operación requiere un tiempo constante amortizado. Algunas invocaciones de la operación pueden tomar un poco más de tiempo, pero si en promedio muchas operaciones son realizadas solo es requerido tiempo constante. | +| **Log** | La operación requiere de un tiempo proporcional al logaritmo del tamaño de la colección. | +| **L** | La operación es linea, es decir que requiere tiempo proporcional al tamaño de la colección. | +| **-** | La operación no es soportada. | + +La primer tabla considera tipos secuencia --ambos mutables e inmutables-- con las siguientes operaciones: + +| | | +| --- | ---- | +| **head** | Seleccionar el primer elemento de la secuencia. | +| **tail** | Produce una nueva secuencia que contiene todos los elementos menos el primero. | +| **apply** | Indexación. Seleccionar un elemento por posición. | +| **update** | Actualización funcional (con `updated`) para secuencias inmutables, actualización real con efectos laterales para secuencias mutables. | +| **prepend**| Añadir un elemento al principio de la colección. Para secuencias inmutables, esto produce una nueva secuencia. Para secuencias mutables, modifica la secuencia existente. | +| **append** | Añadir un elemento al final de la secuencia. Para secuencias inmutables, produce una nueva secuencia. Para secuencias mutables modifica la secuencia existente. | +| **insert** | Inserta un elemento a una posición arbitraria. Solamente es posible en secuencias mutables. | + +La segunda tabla trata sets y maps, tanto mutables como inmutables, con las siguientes operaciones: + +| | | +| --- | ---- | +| **lookup** | Comprueba si un elemento es contenido en un set, o selecciona un valor asociado con una clave en un map. | +| **add** | Añade un nuevo elemento a un set o un par clave/valor a un map. | +| **remove** | Removing an element from a set or a key from a map. | +| **remove** | Elimina un elemento de un set o una clave de un map. | +| **min** | El menor elemento de un set o la menor clave de un mapa. | diff --git a/_es/overviews/parallel-collections/configuration.md b/_es/overviews/parallel-collections/configuration.md new file mode 100644 index 0000000000..6e921bc9a5 --- /dev/null +++ b/_es/overviews/parallel-collections/configuration.md @@ -0,0 +1,83 @@ +--- +layout: multipage-overview +title: Configurando las colecciones paralelas + +discourse: false + +partof: parallel-collections +overview-name: Parallel Collections + +num: 7 +language: es +--- + +## "Task support" + +Las colecciones paralelas son modulares respecto al modo en que las operaciones +son planificadas. Cada colección paralela es planificada con un objeto "task support" +el cual es responsable de la planificación y el balanceo de las tareas a los +distintos procesadores. + +El objeto "task support" mantiene internamente un referencia a un pool de hilos y decide +cómo y cuando las tareas son divididas en tareas más pequeñas. Para conocer más en detalle +cómo funciona internamente diríjase al informe técnico \[[1][1]\]. + +En la actualidad las colecciones paralelas disponen de unas cuantas implementaciones de +"task support". El `ForkJoinTaskSupport` utiliza internamente un fork-join pool y es utilizado +por defecto en JVM 1.6 o superiores. `ThreadPoolTaskSupport`, menos eficiente, es utilizado como +mecanismo de reserva para JVM 1.5 y máquinas virtuales que no soporten los fork join pools. El +`ExecutionContextTaskSupport` utiliza el contexto de ejecución por defecto que viene definido +en `scala.concurrent`, y reutiliza el thread pool utilizado en dicho paquete (podrá ser un fork +join pool o un thread pool executor dependiendo de la versión de la JVM). El "task support" basado +en el contexto de ejecución es establecido en cada una de las colecciones paralelas por defecto, de modo +que dichas colecciones reutilizan el mismo fork-join pool del mismo modo que el API de las "futures". + +A continuación se muestra cómo se puede modificar el objeto "task support" de una colección paralela: + + scala> import scala.collection.parallel._ + import scala.collection.parallel._ + + scala> val pc = mutable.ParArray(1, 2, 3) + pc: scala.collection.parallel.mutable.ParArray[Int] = ParArray(1, 2, 3) + + scala> pc.tasksupport = new ForkJoinTaskSupport(new scala.concurrent.forkjoin.ForkJoinPool(2)) + pc.tasksupport: scala.collection.parallel.TaskSupport = scala.collection.parallel.ForkJoinTaskSupport@4a5d484a + + scala> pc map { _ + 1 } + res0: scala.collection.parallel.mutable.ParArray[Int] = ParArray(2, 3, 4) + +El fragmento de código anterior determina que la colección paralela utilice un fork-join pool con un nivel 2 de +paralelismo. Para indicar que la colección utilice un thread pool executor tendremos que hacerlo del siguiente modo: + + scala> pc.tasksupport = new ThreadPoolTaskSupport() + pc.tasksupport: scala.collection.parallel.TaskSupport = scala.collection.parallel.ThreadPoolTaskSupport@1d914a39 + + scala> pc map { _ + 1 } + res1: scala.collection.parallel.mutable.ParArray[Int] = ParArray(2, 3, 4) + +Cuando una colección paralela es serializada, el atributo que almacena la referencia +al objeto "task support" es omitido en el proceso de serialización. Cuando una colección +paralela es deserializada, dicho atributo toma el valor por defecto -- el objeto "task support" +basado en el contexto de ejecución. + +Para llevar a cabo una implementación personalizada de un nuevo objeto "task support" necesitamos +extender del trait `TaskSupport` e implementar los siguientes métodos: + + def execute[R, Tp](task: Task[R, Tp]): () => R + + def executeAndWaitResult[R, Tp](task: Task[R, Tp]): R + + def parallelismLevel: Int + +El método `execute` planifica una tarea asíncrona y retorna una "future" sobre la que +esperar el resultado de la computación. El método `executeAndWait` lleva a cabo el mismo +trabajo, pero retorna única y exclusivamente una vez la tarea haya finalizado. `parallelismLevel` +simplemente retorna el número de núcleos que el objeto "task support" utiliza para planificar +las diferentes tareas. + + +## Referencias + +1. [On a Generic Parallel Collection Framework, June 2011][1] + + [1]: http://infoscience.epfl.ch/record/165523/files/techrep.pdf "parallel-collections" diff --git a/_es/overviews/parallel-collections/conversions.md b/_es/overviews/parallel-collections/conversions.md new file mode 100644 index 0000000000..8461a2f9aa --- /dev/null +++ b/_es/overviews/parallel-collections/conversions.md @@ -0,0 +1,76 @@ +--- +layout: multipage-overview +title: Conversiones en colecciones paralelas +discourse: false + +partof: parallel-collections +overview-name: Parallel Collections + +num: 3 +language: es +--- + +## Conversiones entre colecciones secuenciales y paralelas + +Cada una de las colecciones secuenciales puede convertirse es su versión +paralela mediante la utilización del método `par`. Determinadas colecciones +secuenciales disponen de una versión homóloga paralela. Para estas colecciones el +proceso de conversión es eficiente -- ocurre en tiempo constante dado que ambas +versiones utilizan la misma estructura de datos interna. Una excepción al caso +anterior es el caso de los hash maps y hash sets mutables, donde el proceso de +conversión es un poco más costoso la primera vez que el método `par` es llamado, +aunque las posteriores invocaciones de dicho método ofrecerán un tiempo de ejecución +constante. Nótese que en el caso de las colecciones mutables, los cambios en la +colección secuencial son visibles en su homóloga paralela en el caso de que compartan +la estructura de datos subyacente. + +| Secuencial | Paralelo | +| ------------- | -------------- | +| **mutable** | | +| `Array` | `ParArray` | +| `HashMap` | `ParHashMap` | +| `HashSet` | `ParHashSet` | +| `TrieMap` | `ParTrieMap` | +| **inmutable** | | +| `Vector` | `ParVector` | +| `Range` | `ParRange` | +| `HashMap` | `ParHashMap` | +| `HashSet` | `ParHashSet` | + +Otro tipo de colecciones, como las listas, colas o `streams`, son inherentemente +secuenciales en el sentido de que los elementos deben ser accedidos uno tras otro. +La versión paralela de estas estructuras se obtiene mediante la copia de los elementos +en una colección paralela. Por ejemplo, una lista funcional es convertida en una +secuencia paralela inmutable; un vector paralelo. + +Cada colección paralela puede ser convertida a su variante secuencial mediante el uso +del método `seq`. La conversión de una colección paralela a su homóloga secuencial es +siempre un proceso eficiente -- tiempo constante. La invocación del método `seq` sobre +una colección paralela mutable retorna una colección secuencial cuya representación interna +es la misma que la de la versión paralela, por lo que posibles actualizaciones en una de las +colecciones serán visibles en la otra. + +## Conversiones entre diferentes tipo de colecciones + +Ortogonal a la conversión entre colecciones secuenciales y paralelas, las colecciones +pueden convertirse entre diferentes tipos. Por ejemplo, la llamada al método `toSeq` +convierte un conjunto secuencial en una secuencia secuencial, mientras que si invocamos +dicho método sobre un conjunto paralelo obtendremos una secuencia paralela. La regla +general is que si existe una versión paralela de `X`, el método `toX` convierte la colección +en una colección `ParX` + +A continuación se muestra un resumen de todos los métodos de conversión: + +| método | Tipo de Retorno| +| -------------- | -------------- | +| `toArray` | `Array` | +| `toList` | `List` | +| `toIndexedSeq` | `IndexedSeq` | +| `toStream` | `Stream` | +| `toIterator` | `Iterator` | +| `toBuffer` | `Buffer` | +| `toTraversable`| `GenTraverable`| +| `toIterable` | `ParIterable` | +| `toSeq` | `ParSeq` | +| `toSet` | `ParSet` | +| `toMap` | `ParMap` | diff --git a/_es/overviews/parallel-collections/ctries.md b/_es/overviews/parallel-collections/ctries.md new file mode 100644 index 0000000000..c45a112520 --- /dev/null +++ b/_es/overviews/parallel-collections/ctries.md @@ -0,0 +1,168 @@ +--- +layout: multipage-overview +title: Tries Concurrentes + +discourse: false + +partof: parallel-collections +overview-name: Parallel Collections + +num: 4 +language: es +--- + +La mayoría de las estructuras de datos no garantizan un recorrido consistente +si la estructura es modificada durante el recorrido de la misma. De hecho, +esto también sucede en la mayor parte de las colecciones mutables. Los "tries" +concurrentes presentan una característica especial, permitiendo la modificación +de los mismos mientras están siendo recorridos. Las modificaciones solo son visibles +en los recorridos posteriores a las mismas. Ésto aplica tanto a los "tries" secuenciales +como a los paralelos. La única diferencia entre ambos es que el primero de ellos +recorre todos los elementos de la estructura de manera secuencial mientras que +el segundo lo hace en paralelo. + +Esta propiedad nos permite escribir determinados algoritmos de un modo mucho más +sencillo. Por lo general, son algoritmos que procesan un conjunto de elementos de manera +iterativa y diferentes elementos necesitan distinto número de iteraciones para ser +procesados. + +El siguiente ejemplo calcula la raíz cuadrada de un conjunto de números. Cada iteración +actualiza, de manera iterativa, el valor de la raíz cuadrada. Aquellos números cuyas +raíces convergen son eliminados del mapa. + + case class Entry(num: Double) { + var sqrt = num + } + + val length = 50000 + + // prepare the list + val entries = (1 until length) map { num => Entry(num.toDouble) } + val results = ParTrieMap() + for (e <- entries) results += ((e.num, e)) + + // compute square roots + while (results.nonEmpty) { + for ((num, e) <- results) { + val nsqrt = 0.5 * (e.sqrt + e.num / e.sqrt) + if (math.abs(nsqrt - e.sqrt) < 0.01) { + results.remove(num) + } else e.sqrt = nsqrt + } + } + +Fíjese que en el anterior método de cálculo de la raíz cuadrada (método Babylonian) +(\[[3][3]\]) algunos números pueden converger mucho más rápidamente que otros. Por esta razón, +queremos eliminar dichos números de la variable `results` de manera que solo aquellos +elementos sobre los que realmente necesitamos trabajar son recorridos. + +Otro ejemplo es el algoritmo de búsqueda en anchura, el cual iterativamente expande el "nodo cabecera" +hasta que encuentra un camino hacia el objetivo o no existen más nodos a expandir. Definamos +un nodo en mapa 2D como una tupla de enteros (`Int`s). Definamos un `map` como un array de +booleanos de dos dimensiones el cual determina si un determinado slot está ocupado o no. Posteriormente, +declaramos dos "concurrent tries maps" -- `open` contiene todos los nodos que deben ser expandidos +("nodos cabecera") mientras que `close` continene todos los nodos que ya han sido expandidos. Comenzamos +la búsqueda desde las esquinas del mapa en busca de un camino hasta el centro del mismo -- +e inicializamos el mapa `open` con los nodos apropiados. Iterativamamente, y en paralelo, +expandimos todos los nodos presentes en el mapa `open` hasta que agotamos todos los elementos +que necesitan ser expandidos. Cada vez que un nodo es expandido, se elimina del mapa `open` y se +añade en el mapa `closed`. Una vez finalizado el proceso, se muestra el camino desde el nodo +destino hasta el nodo inicial. + + val length = 1000 + + // define the Node type + type Node = (Int, Int); + type Parent = (Int, Int); + + // operations on the Node type + def up(n: Node) = (n._1, n._2 - 1); + def down(n: Node) = (n._1, n._2 + 1); + def left(n: Node) = (n._1 - 1, n._2); + def right(n: Node) = (n._1 + 1, n._2); + + // create a map and a target + val target = (length / 2, length / 2); + val map = Array.tabulate(length, length)((x, y) => (x % 3) != 0 || (y % 3) != 0 || (x, y) == target) + def onMap(n: Node) = n._1 >= 0 && n._1 < length && n._2 >= 0 && n._2 < length + + // open list - the nodefront + // closed list - nodes already processed + val open = ParTrieMap[Node, Parent]() + val closed = ParTrieMap[Node, Parent]() + + // add a couple of starting positions + open((0, 0)) = null + open((length - 1, length - 1)) = null + open((0, length - 1)) = null + open((length - 1, 0)) = null + + // greedy bfs path search + while (open.nonEmpty && !open.contains(target)) { + for ((node, parent) <- open) { + def expand(next: Node) { + if (onMap(next) && map(next._1)(next._2) && !closed.contains(next) && !open.contains(next)) { + open(next) = node + } + } + expand(up(node)) + expand(down(node)) + expand(left(node)) + expand(right(node)) + closed(node) = parent + open.remove(node) + } + } + + // print path + var pathnode = open(target) + while (closed.contains(pathnode)) { + print(pathnode + "->") + pathnode = closed(pathnode) + } + println() + + +Los "tries" concurrentes también soportan una operación atómica, no bloqueante y de +tiempo constante conocida como `snapshot`. Esta operación genera un nuevo `trie` +concurrente en el que se incluyen todos los elementos es un instante determinado de +tiempo, lo que en efecto captura el estado del "trie" en un punto específico. +Esta operación simplemente crea una nueva raíz para el "trie" concurrente. Posteriores +actualizaciones reconstruyen, de manera perezosa, la parte del "trie" concurrente que se +ha visto afectada por la actualización, manteniendo intacto el resto de la estructura. +En primer lugar, esto implica que la propia operación de `snapshot` no es costosa en si misma +puesto que no necesita copiar los elementos. En segundo lugar, dado que la optimización +"copy-and-write" solo copia determinadas partes del "trie" concurrente, las sucesivas +actualizaciones escalan horizontalmente. El método `readOnlySnapshot` es ligeramente +más efeciente que el método `snapshot`, pero retorna un mapa en modo solo lectura que no +puede ser modificado. Este tipo de estructura de datos soporta una operación atómica y en tiempo +constante llamada `clear` la cual está basada en el anterior mecanismo de `snapshot`. + +Si desea conocer en más detalle cómo funcionan los "tries" concurrentes y el mecanismo de +snapshot diríjase a \[[1][1]\] y \[[2][2]\]. + +Los iteradores para los "tries" concurrentes están basados en snapshots. Anteriormente a la creación +del iterador se obtiene un snapshot del "trie" concurrente, de modo que el iterador solo recorrerá +los elementos presentes en el "trie" en el momento de creación del snapshot. Naturalmente, +estos iteradores utilizan un snapshot de solo lectura. + +La operación `size` también está basada en el mecanismo de snapshot. En una sencilla implementación, +la llamada al método `size` simplemente crearía un iterador (i.e., un snapshot) y recorrería los +elementos presentes en el mismo realizando la cuenta. Cada una de las llamadas a `size` requeriría +tiempo lineal en relación al número de elementos. Sin embargo, los "tries" concurrentes han sido +optimizados para cachear los tamaños de sus diferentes partes, reduciendo por tanto la complejidad +del método a un tiempo logarítmico amortizado. En realidad, esto significa que tras la primera +llamada al método `size`, las sucesivas llamadas requerirán un esfuerzo mínimo, típicamente recalcular +el tamaño para aquellas ramas del "trie" que hayan sido modificadas desde la última llamada al método +`size`. Adicionalmente, el cálculo del tamaño para los "tries" concurrentes y paralelos se lleva a cabo +en paralelo. + +## Referencias + +1. [Cache-Aware Lock-Free Concurrent Hash Tries][1] +2. [Concurrent Tries with Efficient Non-Blocking Snapshots][2] +3. [Methods of computing square roots][3] + + [1]: http://infoscience.epfl.ch/record/166908/files/ctries-techreport.pdf "Ctries-techreport" + [2]: http://lampwww.epfl.ch/~prokopec/ctries-snapshot.pdf "Ctries-snapshot" + [3]: http://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method "babylonian-method" diff --git a/_es/overviews/parallel-collections/custom-parallel-collections.md b/_es/overviews/parallel-collections/custom-parallel-collections.md new file mode 100644 index 0000000000..802d9f5286 --- /dev/null +++ b/_es/overviews/parallel-collections/custom-parallel-collections.md @@ -0,0 +1,327 @@ +--- +layout: multipage-overview +title: Creating Custom Parallel Collections + +discourse: false + +partof: parallel-collections +overview-name: Parallel Collections + +num: 6 +language: es +--- + +## Parallel collections without combiners + +Just as it is possible to define custom sequential collections without +defining their builders, it is possible to define parallel collections without +defining their combiners. The consequence of not having a combiner is that +transformer methods (e.g. `map`, `flatMap`, `collect`, `filter`, ...) will by +default return a standard collection type which is nearest in the hierarchy. +For example, ranges do not have builders, so mapping elements of a range +creates a vector. + +In the following example we define a parallel string collection. Since strings +are logically immutable sequences, we have parallel strings inherit +`immutable.ParSeq[Char]`: + + class ParString(val str: String) + extends immutable.ParSeq[Char] { + +Next, we define methods found in every immutable sequence: + + def apply(i: Int) = str.charAt(i) + + def length = str.length + +We have to also define the sequential counterpart of this parallel collection. +In this case, we return the `WrappedString` class: + + def seq = new collection.immutable.WrappedString(str) + +Finally, we have to define a splitter for our parallel string collection. We +name the splitter `ParStringSplitter` and have it inherit a sequence splitter, +that is, `SeqSplitter[Char]`: + + def splitter = new ParStringSplitter(str, 0, str.length) + + class ParStringSplitter(private var s: String, private var i: Int, private val ntl: Int) + extends SeqSplitter[Char] { + + final def hasNext = i < ntl + + final def next = { + val r = s.charAt(i) + i += 1 + r + } + +Above, `ntl` represents the total length of the string, `i` is the current +position and `s` is the string itself. + +Parallel collection iterators or splitters require a few more methods in +addition to `next` and `hasNext` found in sequential collection iterators. +First of all, they have a method called `remaining` which returns the number +of elements this splitter has yet to traverse. Next, they have a method called +`dup` which duplicates the current splitter. + + def remaining = ntl - i + + def dup = new ParStringSplitter(s, i, ntl) + +Finally, methods `split` and `psplit` are used to create splitters which +traverse subsets of the elements of the current splitter. Method `split` has +the contract that it returns a sequence of splitters which traverse disjoint, +non-overlapping subsets of elements that the current splitter traverses, none +of which is empty. If the current splitter has 1 or less elements, then +`split` just returns a sequence of this splitter. Method `psplit` has to +return a sequence of splitters which traverse exactly as many elements as +specified by the `sizes` parameter. If the `sizes` parameter specifies less +elements than the current splitter, then an additional splitter with the rest +of the elements is appended at the end. If the `sizes` parameter requires more +elements than there are remaining in the current splitter, it will append an +empty splitter for each size. Finally, calling either `split` or `psplit` +invalidates the current splitter. + + def split = { + val rem = remaining + if (rem >= 2) psplit(rem / 2, rem - rem / 2) + else Seq(this) + } + + def psplit(sizes: Int*): Seq[ParStringSplitter] = { + val splitted = new ArrayBuffer[ParStringSplitter] + for (sz <- sizes) { + val next = (i + sz) min ntl + splitted += new ParStringSplitter(s, i, next) + i = next + } + if (remaining > 0) splitted += new ParStringSplitter(s, i, ntl) + splitted + } + } + } + +Above, `split` is implemented in terms of `psplit`, which is often the case +with parallel sequences. Implementing a splitter for parallel maps, sets or +iterables is often easier, since it does not require `psplit`. + +Thus, we obtain a parallel string class. The only downside is that calling transformer methods +such as `filter` will not produce a parallel string, but a parallel vector instead, which +may be suboptimal - producing a string again from the vector after filtering may be costly. + + +## Parallel collections with combiners + +Lets say we want to `filter` the characters of the parallel string, to get rid +of commas for example. As noted above, calling `filter` produces a parallel +vector and we want to obtain a parallel string (since some interface in the +API might require a sequential string). + +To avoid this, we have to write a combiner for the parallel string collection. +We will also inherit the `ParSeqLike` trait this time to ensure that return +type of `filter` is more specific - a `ParString` instead of a `ParSeq[Char]`. +The `ParSeqLike` has a third type parameter which specifies the type of the +sequential counterpart of the parallel collection (unlike sequential `*Like` +traits which have only two type parameters). + + class ParString(val str: String) + extends immutable.ParSeq[Char] + with ParSeqLike[Char, ParString, collection.immutable.WrappedString] + +All the methods remain the same as before, but we add an additional protected method `newCombiner` which +is internally used by `filter`. + + protected[this] override def newCombiner: Combiner[Char, ParString] = new ParStringCombiner + +Next we define the `ParStringCombiner` class. Combiners are subtypes of +builders and they introduce an additional method called `combine`, which takes +another combiner as an argument and returns a new combiner which contains the +elements of both the current and the argument combiner. The current and the +argument combiner are invalidated after calling `combine`. If the argument is +the same object as the current combiner, then `combine` just returns the +current combiner. This method is expected to be efficient, having logarithmic +running time with respect to the number of elements in the worst case, since +it is called multiple times during a parallel computation. + +Our `ParStringCombiner` will internally maintain a sequence of string +builders. It will implement `+=` by adding an element to the last string +builder in the sequence, and `combine` by concatenating the lists of string +builders of the current and the argument combiner. The `result` method, which +is called at the end of the parallel computation, will produce a parallel +string by appending all the string builders together. This way, elements are +copied only once at the end instead of being copied every time `combine` is +called. Ideally, we would like to parallelize this process and copy them in +parallel (this is being done for parallel arrays), but without tapping into +the internal representation of strings this is the best we can do-- we have to +live with this sequential bottleneck. + + private class ParStringCombiner extends Combiner[Char, ParString] { + var sz = 0 + val chunks = new ArrayBuffer[StringBuilder] += new StringBuilder + var lastc = chunks.last + + def size: Int = sz + + def +=(elem: Char): this.type = { + lastc += elem + sz += 1 + this + } + + def clear = { + chunks.clear + chunks += new StringBuilder + lastc = chunks.last + sz = 0 + } + + def result: ParString = { + val rsb = new StringBuilder + for (sb <- chunks) rsb.append(sb) + new ParString(rsb.toString) + } + + def combine[U <: Char, NewTo >: ParString](other: Combiner[U, NewTo]) = if (other eq this) this else { + val that = other.asInstanceOf[ParStringCombiner] + sz += that.sz + chunks ++= that.chunks + lastc = chunks.last + this + } + } + + +## How do I implement my combiner in general? + +There are no predefined recipes-- it depends on the data-structure at +hand, and usually requires a bit of ingenuity on the implementer's +part. However there are a few approaches usually taken: + +1. Concatenation and merge. Some data-structures have efficient +implementations (usually logarithmic) of these operations. +If the collection at hand is backed by such a data-structure, +its combiner can be the collection itself. Finger trees, +ropes and various heaps are particularly suitable for such an approach. + +2. Two-phase evaluation. An approach taken in parallel arrays and +parallel hash tables, it assumes the elements can be efficiently +partially sorted into concatenable buckets from which the final +data-structure can be constructed in parallel. In the first phase +different processors populate these buckets independently and +concatenate the buckets together. In the second phase, the data +structure is allocated and different processors populate different +parts of the datastructure in parallel using elements from disjoint +buckets. +Care must be taken that different processors never modify the same +part of the datastructure, otherwise subtle concurrency errors may occur. +This approach is easily applicable to random access sequences, as we +have shown in the previous section. + +3. A concurrent data-structure. While the last two approaches actually +do not require any synchronization primitives in the data-structure +itself, they assume that it can be constructed concurrently in a way +such that two different processors never modify the same memory +location. There exists a large number of concurrent data-structures +that can be modified safely by multiple processors-- concurrent skip lists, +concurrent hash tables, split-ordered lists, concurrent avl trees, to +name a few. +An important consideration in this case is that the concurrent +data-structure has a horizontally scalable insertion method. +For concurrent parallel collections the combiner can be the collection +itself, and a single combiner instance is shared between all the +processors performing a parallel operation. + + +## Integration with the collections framework + +Our `ParString` class is not complete yet. Although we have implemented a +custom combiner which will be used by methods such as `filter`, `partition`, +`takeWhile` or `span`, most transformer methods require an implicit +`CanBuildFrom` evidence (see Scala collections guide for a full explanation). +To make it available and completely integrate `ParString` with the collections +framework, we have to mix an additional trait called `GenericParTemplate` and +define the companion object of `ParString`. + + class ParString(val str: String) + extends immutable.ParSeq[Char] + with GenericParTemplate[Char, ParString] + with ParSeqLike[Char, ParString, collection.immutable.WrappedString] { + + def companion = ParString + +Inside the companion object we provide an implicit evidence for the `CanBuildFrom` parameter. + + object ParString { + implicit def canBuildFrom: CanCombineFrom[ParString, Char, ParString] = + new CanCombinerFrom[ParString, Char, ParString] { + def apply(from: ParString) = newCombiner + def apply() = newCombiner + } + + def newBuilder: Combiner[Char, ParString] = newCombiner + + def newCombiner: Combiner[Char, ParString] = new ParStringCombiner + + def apply(elems: Char*): ParString = { + val cb = newCombiner + cb ++= elems + cb.result + } + } + + + +## Further customizations-- concurrent and other collections + +Implementing a concurrent collection (unlike parallel collections, concurrent +collections are ones that can be concurrently modified, like +`collection.concurrent.TrieMap`) is not always straightforward. Combiners in +particular often require a lot of thought. In most _parallel_ collections +described so far, combiners use a two-step evaluation. In the first step the +elements are added to the combiners by different processors and the combiners +are merged together. In the second step, after all the elements are available, +the resulting collection is constructed. + +Another approach to combiners is to construct the resulting collection as the +elements. This requires the collection to be thread-safe-- a combiner must +allow _concurrent_ element insertion. In this case one combiner is shared by +all the processors. + +To parallelize a concurrent collection, its combiners must override the method +`canBeShared` to return `true`. This will ensure that only one combiner is +created when a parallel operation is invoked. Next, the `+=` method must be +thread-safe. Finally, method `combine` still returns the current combiner if +the current combiner and the argument combiner are the same, and is free to +throw an exception otherwise. + +Splitters are divided into smaller splitters to achieve better load balancing. +By default, information returned by the `remaining` method is used to decide +when to stop dividing the splitter. For some collections, calling the +`remaining` method may be costly and some other means should be used to decide +when to divide the splitter. In this case, one should override the +`shouldSplitFurther` method in the splitter. + +The default implementation divides the splitter if the number of remaining +elements is greater than the collection size divided by eight times the +parallelism level. + + def shouldSplitFurther[S](coll: ParIterable[S], parallelismLevel: Int) = + remaining > thresholdFromSize(coll.size, parallelismLevel) + +Equivalently, a splitter can hold a counter on how many times it was split and +implement `shouldSplitFurther` by returning `true` if the split count is +greater than `3 + log(parallelismLevel)`. This avoids having to call +`remaining`. + +Furthermore, if calling `remaining` is not a cheap operation for a particular +collection (i.e. it requires evaluating the number of elements in the +collection), then the method `isRemainingCheap` in splitters should be +overridden to return `false`. + +Finally, if the `remaining` method in splitters is extremely cumbersome to +implement, you can override the method `isStrictSplitterCollection` in its +collection to return `false`. Such collections will fail to execute some +methods which rely on splitters being strict, i.e. returning a correct value +in the `remaining` method. Importantly, this does not effect methods used in +for-comprehensions. diff --git a/_es/overviews/parallel-collections/overview.md b/_es/overviews/parallel-collections/overview.md new file mode 100644 index 0000000000..b5f13762f2 --- /dev/null +++ b/_es/overviews/parallel-collections/overview.md @@ -0,0 +1,199 @@ +--- +layout: multipage-overview +title: Overview + +discourse: false + +partof: parallel-collections +overview-name: Parallel Collections + +num: 1 +language: es +--- + +**Autores originales: Aleksandar Prokopec, Heather Miller** + +**Traducción y arreglos: Santiago Basulto** + +## Motivación + +En el medio del cambio en los recientes años de los fabricantes de procesadores de arquitecturas simples a arquitecturas multi-nucleo, tanto el ámbito académico, como el industrial coinciden que la _Programación Paralela_ sigue siendo un gran desafío. + +Las Colecciones Paralelizadas (Parallel collections, en inglés) fueron incluidas en la librería del lenguaje Scala en un esfuerzo de facilitar la programación paralela al abstraer a los usuarios de detalles de paralelización de bajo nivel, mientras se provee con una abstracción de alto nivel, simple y familiar. La esperanza era, y sigue siendo, que el paralelismo implícito detrás de una abstracción de colecciones (como lo es el actual framework de colecciones del lenguaje) acercara la ejecución paralela confiable, un poco más al trabajo diario de los desarrolladores. + +La idea es simple: las colecciones son abstracciones de programación ficientemente entendidas y a su vez son frecuentemente usadas. Dada su regularidad, es posible que sean paralelizadas eficiente y transparentemente. Al permitirle al usuario intercambiar colecciones secuenciales por aquellas que son operadas en paralelo, las colecciones paralelizadas de Scala dan un gran paso hacia la posibilidad de que el paralelismo sea introducido cada vez más frecuentemente en nuestro código. + +Veamos el siguiente ejemplo secuencial, donde realizamos una operación monádica en una colección lo suficientemente grande. + + val list = (1 to 10000).toList + list.map(_ + 42) + +Para realizar la misma operación en paralelo, lo único que devemos incluir, es la invocación al método `par` en la colección secuencial `list`. Después de eso, es posible utilizar la misma colección paralelizada de la misma manera que normalmente la usariamos si fuera una colección secuencial. El ejemplo superior puede ser paralelizado al hacer simplemente lo siguiente: + + list.par.map(_ + 42) + +El diseño de la librería de colecciones paralelizadas de Scala está inspirada y fuertemente integrada con la librería estandar de colecciones (secuenciales) del lenguaje (introducida en la versión 2.8). Se provee te una contraparte paralelizada a un número importante de estructuras de datos de la librería de colecciones (secuenciales) de Scala, incluyendo: + +* `ParArray` +* `ParVector` +* `mutable.ParHashMap` +* `mutable.ParHashSet` +* `immutable.ParHashMap` +* `immutable.ParHashSet` +* `ParRange` +* `ParTrieMap` (`collection.concurrent.TrieMap`s are new in 2.10) + +Además de una arquitectura común, la librería de colecciones paralelizadas de Scala también comparte la _extensibilidad_ con la librería de colecciones secuenciales. Es decir, de la misma manera que los usuarios pueden integrar sus propios tipos de tipos de colecciones de la librería normal de colecciones secuenciales, pueden realizarlo con la librería de colecciones paralelizadas, heredando automáticamente todas las operaciones paralelas disponibles en las demás colecciones paralelizadas de la librería estandar. + +## Algunos Ejemplos + +To attempt to illustrate the generality and utility of parallel collections, +we provide a handful of simple example usages, all of which are transparently +executed in parallel. + +De forma de ilustrar la generalidad y utilidad de las colecciones paralelizadas, proveemos un conjunto de ejemplos de uso útiles, todos ellos siendo ejecutados en paralelo de forma totalmente transparente al usuario. + +_Nota:_ Algunos de los siguientes ejemplos operan en colecciones pequeñas, lo cual no es recomendado. Son provistos como ejemplo para ilustrar solamente el propósito. Como una regla heurística general, los incrementos en velocidad de ejecución comienzan a ser notados cuando el tamaño de la colección es lo suficientemente grande, tipicamente algunos cuantos miles de elementos. (Para más información en la relación entre tamaño de una coleccion paralelizada y su performance, por favor véase [appropriate subsection]({{ site.baseurl}}/es/overviews/parallel-collections/performance.html#how_big_should_a_collection_be_to_go_parallel) en la sección [performance]({{ site.baseurl }}/es/overviews/parallel-collections/performance.html) (en inglés). + +#### map + +Usando un `map` paralelizado para transformar una colección de elementos tipo `String` a todos caracteres en mayúscula: + + scala> val apellidos = List("Smith","Jones","Frankenstein","Bach","Jackson","Rodin").par + apellidos: scala.collection.parallel.immutable.ParSeq[String] = ParVector(Smith, Jones, Frankenstein, Bach, Jackson, Rodin) + + scala> apellidos.map(_.toUpperCase) + res0: scala.collection.parallel.immutable.ParSeq[String] = ParVector(SMITH, JONES, FRANKENSTEIN, BACH, JACKSON, RODIN) + +#### fold + +Sumatoria mediante `fold` en un `ParArray`: + + scala> val parArray = (1 to 10000).toArray.par + parArray: scala.collection.parallel.mutable.ParArray[Int] = ParArray(1, 2, 3, ... + + scala> parArray.fold(0)(_ + _) + res0: Int = 50005000 + +#### filtrando + + +Usando un filtrado mediante `filter` paralelizado para seleccionar los apellidos que alfabéticamente preceden la letra "K": + + scala> val apellidos = List("Smith","Jones","Frankenstein","Bach","Jackson","Rodin").par + apellidos: scala.collection.parallel.immutable.ParSeq[String] = ParVector(Smith, Jones, Frankenstein, Bach, Jackson, Rodin) + + scala> apellidos.filter(_.head >= 'J') + res0: scala.collection.parallel.immutable.ParSeq[String] = ParVector(Smith, Jones, Jackson, Rodin) + +## Creación de colecciones paralelizadas + +Las colecciones paralelizadas están pensadas para ser usadas exactamente de la misma manera que las colecciones secuenciales --la única diferencia notoria es cómo _obtener_ una colección paralelizada. + +Generalmente se tienen dos opciones para la creación de colecciones paralelizadas: + +Primero al utilizar la palabra clave `new` y una sentencia de importación apropiada: + + import scala.collection.parallel.immutable.ParVector + val pv = new ParVector[Int] + +Segundo, al _convertir_ desde una colección secuencial: + + val pv = Vector(1,2,3,4,5,6,7,8,9).par + +Lo que es importante desarrollar aquí son estos métodos para la conversión de colecciones. Las colecciones secuenciales pueden ser convertiadas a colecciones paralelizadas mediante la invocación del método `par`, y de la misma manera, las colecciones paralelizadas pueden ser convertidas a colecciones secuenciales mediante el método `seq`. + +_Nota:_ Las colecciones que son inherentemente secuenciales (en el sentido que sus elementos deben ser accedidos uno a uno), como las listas, colas y streams (a veces llamados flujos), son convertidos a sus contrapartes paralelizadas al copiar los todos sus elementos. Un ejemplo es la clase `List` --es convertida a una secuencia paralelizada inmutable común, que es un `ParVector`. Por supuesto, el tener que copiar los elementos para estas colecciones involucran una carga más de trabajo que no se sufre con otros tipos como: `Array`, `Vector`, `HashMap`, etc. + +For more information on conversions on parallel collections, see the +[conversions]({{ site.baseurl }}/overviews/parallel-collections/conversions.html) +and [concrete parallel collection classes]({{ site.baseurl }}/overviews/parallel-collections/concrete-parallel-collections.html) +sections of thise guide. + +Para más información sobre la conversión de colecciones paralelizadas, véase los artículos sobre [conversiones]({{ site.baseurl }}/es/overviews/parallel-collections/conversions.html) y [clases concretas de colecciones paralelizadas]({{ site.baseurl }}/es/overviews/parallel-collections/concrete-parallel-collections.html) de esta misma serie. + +## Entendiendo las colecciones paralelizadas + +A pesar de que las abstracciones de las colecciones paralelizadas se parecen mucho a las colecciones secuenciales normales, es importante notar que su semántica difiere, especialmente con relación a efectos secundarios (o colaterales, según algunas traducciones) y operaciones no asociativas. + +Para entender un poco más esto, primero analizaremos _cómo_ son realizadas las operaciones paralelas. Conceptualmente, el framework de colecciones paralelizadas de Scala paraleliza una operación al "dividir" recursivamente una colección dada, aplicando una operación en cada partición de la colección en paralelo y recombinando todos los resultados que fueron completados en paralelo. + +Esta ejecución concurrente y fuera de orden de las colecciones paralelizadas llevan a dos implicancias que es importante notar: + +1. **Las operaciones con efectos secundarios pueden llegar a resultados no deterministas** +2. **Operaciones no asociativas generan resultados no deterministas** + +### Operaciones con efectos secundarios + +Given the _concurrent_ execution semantics of the parallel collections +framework, operations performed on a collection which cause side-effects +should generally be avoided, in order to maintain determinism. A simple +example is by using an accessor method, like `foreach` to increment a `var` +declared outside of the closure which is passed to `foreach`. + +Dada la ejecución _concurrente_ del framework de colecciones paralelizadas, las operaciones que generen efectos secundarios generalmente deben ser evitadas, de manera de mantener el "determinismo". + +Veamos un ejemplo: + + scala> var sum = 0 + sum: Int = 0 + + scala> val list = (1 to 1000).toList.par + list: scala.collection.parallel.immutable.ParSeq[Int] = ParVector(1, 2, 3,… + + scala> list.foreach(sum += _); sum + res01: Int = 467766 + + scala> var sum = 0 + sum: Int = 0 + + scala> list.foreach(sum += _); sum + res02: Int = 457073 + + scala> var sum = 0 + sum: Int = 0 + + scala> list.foreach(sum += _); sum + res03: Int = 468520 + +Acá podemos ver que cada vez que `sum` es reinicializado a 0, e invocamos el método `foreach` en nuestro objeto `list`, el valor de `sum` resulta ser distinto. La razón de este no-determinismo es una _condición de carrera_ -- lecturas/escrituras concurrentes a la misma variable mutable. + +En el ejemplo anterior, es posible para dos hilos leer el _mismo_ valor de `sum`, demorarse un tiempo realizando la operación que tienen que hacer sobre `sum`, y después volver a escribir ese nuevo valor a `sum`, lo que probablemente resulte en una sobreescritura (y por lo tanto pérdida) de un valor anterior que generó otro hilo. Veamos otro ejemplo: + + HiloA: lee el valor en sum, sum = 0 valor de sum: 0 + HiloB: lee el valor en sum, sum = 0 valor de sum: 0 + HiloA: incrementa el valor de sum a 760, graba sum = 760 valor de sum: 760 + HiloA: incrementa el valor de sum a 12, graba sum = 12 valor de sum: 12 + +Este ejemplo ilustra un escenario donde dos hilos leen el mismo valor, `0`, antes que el otro pueda sumar su parte de la ejecución sobre la colección paralela. En este caso el `HiloA` lee `0` y le suma el valor de su cómputo, `0+760`, y en el caso del `HiloB`, le suma al valor leido `0` su resultado, quedando `0+12`. Después de computar sus respectivas sumas, ambos escriben el valor en `sum`. Ya que el `HiloA` llega a escribir antes que el `HiloB` (por nada en particular, solamente coincidencia que en este caso llegue primero el `HiloA`), su valor se pierde, porque seguidamente llega a escribir el `HiloB` y borra el valor previamente guardado. Esto se llama _condición de carrera_ porque el valor termina resultando una cuestión de suerte, o aleatoria, de quién llega antes o después a escribir el valor final. + +### Operaciones no asociativas + +Dado este funcionamiento "fuera de orden", también se debe ser cuidadoso de realizar solo operaciones asociativas para evitar comportamientos no esperados. Es decir, dada una colección paralelizada `par_col`, uno debe saber que cuando invoca una función de orden superior sobre `par_col`, tal como `par_col.reduce(func)`, el orden en que la función `func` es invocada sobre los elementos de `par_col` puede ser arbitrario (de hecho, es el caso más probable). Un ejemplo simple y pero no tan obvio de una operación no asociativa es es una substracción: + + scala> val list = (1 to 1000).toList.par + list: scala.collection.parallel.immutable.ParSeq[Int] = ParVector(1, 2, 3,… + + scala> list.reduce(_-_) + res01: Int = -228888 + + scala> list.reduce(_-_) + res02: Int = -61000 + + scala> list.reduce(_-_) + res03: Int = -331818 + +En el ejemplo anterior invocamos reduce sobre un `ParVector[Int]` pasándole `_-_`. Lo que hace esto es simplemente tomar dos elementos y resta el primero al segundo. Dado que el framework de colecciones paralelizadas crea varios hilos que realizan `reduce(_-_)` independientemente en varias secciones de la colección, el resultado de correr dos veces el método `reduce(_-_)` en la misma colección puede no ser el mismo. + +_Nota:_ Generalmente se piensa que, al igual que las operaciones no asociativas, las operaciones no conmutativas pasadas a un función de orden superior también generan resultados extraños (no deterministas). En realidad esto no es así, un simple ejemplo es la concatenación de Strings (cadenas de caracteres). -- una operación asociativa, pero no conmutativa: + + scala> val strings = List("abc","def","ghi","jk","lmnop","qrs","tuv","wx","yz").par + strings: scala.collection.parallel.immutable.ParSeq[java.lang.String] = ParVector(abc, def, ghi, jk, lmnop, qrs, tuv, wx, yz) + + scala> val alfabeto = strings.reduce(_++_) + alfabeto: java.lang.String = abcdefghijklmnopqrstuvwxyz + +Lo que implica el "fuera de orden" en las colecciones paralelizadas es solamente que la operación será ejecutada fuera de orden (en un sentido _temporal_, es decir no secuencial, no significa que el resultado va a ser re-"*combinado*" fuera de orden (en un sentido de _espacio_). Al contrario, en general los resultados siempre serán reensamblados en roden, es decir una colección paralelizada que se divide en las siguientes particiones A, B, C, en ese orden, será reensamblada nuevamente en el orden A, B, C. No en otro orden arbitrario como B, C, A. + +Para más información de cómo se dividen y se combinan los diferentes tipos de colecciones paralelizadas véase el artículo sobre [Arquitectura]({{ site.baseurl }}/es/overviews +/parallel-collections/architecture.html) de esta misma serie. diff --git a/_es/overviews/parallel-collections/performance.md b/_es/overviews/parallel-collections/performance.md new file mode 100644 index 0000000000..26d2237b10 --- /dev/null +++ b/_es/overviews/parallel-collections/performance.md @@ -0,0 +1,277 @@ +--- +layout: multipage-overview +title: Midiendo el rendimiento + +discourse: false + +partof: parallel-collections +overview-name: Parallel Collections + +num: 8 +language: es +--- + +## Performance on the JVM + +Algunas veces el modelo de rendimiento de la JVM se complica debido a comentarios +sobre el mismo, y como resultado de los mismos, se tienen concepciones equívocas del mismo. +Por diferentes motivos, determinado código podría ofrecer un rendimiento o escalabilidad +inferior a la esperada. A continuación ofrecemos algunos ejemplos. + +Uno de los principales motivos es que el proceso de compilación de una aplicación que se +ejecuta sobre la JVM no es el mismo que el de un lenguaje compilado de manera estática +(véase \[[2][2]\]). Los compiladores de Java y Scala traducen el código fuente en *bytecode* y +el conjunto de optimizaciones que llevan a cabo es muy reducido. En la mayoría de las JVM +modernas, una vez el bytecode es ejecutado, se convierte en código máquina dependiente de la +arquitectura de la máquina subyacente. Este proceso es conocido como compilación "just-int-time". +Sin embargo, el nivel de optimización del código es muy bajo puesto que dicho proceso deber ser +lo más rápido posible. Con el objetivo de evitar el proceso de recompilación, el llamado +compilador HotSpot optimiza únicamente aquellas partes del código que son ejecutadas de manera +frecuente. Esto supone que los desarrolladores de "benchmarks" deberán ser conscientes que los +programas podrían presentar rendimientos dispares en diferentes ejecuciones. Múltiples ejecuciones +de un mismo fragmento de código (por ejemplo un método) podrían ofrecer rendimientos dispares en función de +si se ha llevado a cabo un proceso de optimización del código entre dichas ejecuciones. Adicionalmente, +la medición de los tiempos de ejecución de un fragmento de código podría incluir el tiempo en el que +el propio compilador JIT lleva a cabo el proceso de optimizacion, falseando los resultados. + +Otro elemento "oculto" que forma parte de la JVM es la gestión automática de la memoria. De vez en cuando, +la ejecución de un programa es detenida para que el recolector de basura entre en funcionamiento. Si el +programa que estamos analizando realiza alguna reserva de memoria (algo que la mayoría de programas hacen), +el recolector de basura podría entrar en acción, posiblemente distorsionando los resultados. Con el objetivo +de disminuir los efectos de la recolección de basura, el programa bajo estudio deberá ser ejecutado en +múltiples ocasiones para disparar numerosas recolecciones de basura. + +Una causa muy común que afecta de manera notable al rendimiento son las conversiones implícitas que se +llevan a cabo cuando se pasa un tipo primitivo a un método que espera un argumento genérico. En tiempo +de ejecución, los tipos primitivos con convertidos en los objetos que los representan, de manera que puedan +ser pasados como argumentos en los métodos que presentan parámetros genéricos. Este proceso implica un conjunto +extra de reservas de memoria y es más lento, ocasionando nueva basura en el heap. + +Cuando nos referimos al rendimiento en colecciones paralelas la contención de la memoria es un problema muy +común, dado que el desarrollador no dispone de un control explícito sobre la asignación de los objetos. +De hecho, debido a los efectos ocasionados por la recolección de basura, la contención puede producirse en +un estado posterior del ciclo de vida de la aplicación, una vez los objetos hayan ido circulando por la +memoria. Estos efectos deberán ser tenidos en cuenta en el momento en que se esté desarrollando un benchmark. + +## Ejemplo de microbenchmarking + +Numerosos enfoques permiten evitar los anteriores efectos durante el periodo de medición. +En primer lugar, el microbenchmark debe ser ejecutado el número de veces necesario que +permita asegurar que el compilador just-in-time ha compilado a código máquina y que +ha optimizado el código resultante. Esto es lo que comunmente se conoce como fase de +calentamiento. + +El microbenchmark debe ser ejecutado en una instancia independiente de la máquina virtual +con el objetivo de reducir la cantidad de ruido proveniente de la recolección de basura +de los objetos alocados por el propio benchmark o de compilaciones just-in-time que no +están relacionadas con el proceso que estamos midiendo. + +Deberá ser ejecutado utilizando la versión servidora de la máquina virtual, la cual lleva a +cabo un conjunto de optimizaciones mucho más agresivas. + +Finalmente, con el objetivo de reducir la posibilidad de que una recolección de basura ocurra +durante la ejecución del benchmark, idealmente, debería producirse un ciclo de recolección de basura antes +de la ejecución del benchmark, retrasando el siguiente ciclo tanto como sea posible. + +El trait `scala.testing.Benchmark` se predefine en la librería estándar de Scala y ha sido diseñado con +el punto anterior en mente. A continuación se muestra un ejemplo del benchmarking de un operación map +sobre un "trie" concurrente: + + import collection.parallel.mutable.ParTrieMap + import collection.parallel.ForkJoinTaskSupport + + object Map extends testing.Benchmark { + val length = sys.props("length").toInt + val par = sys.props("par").toInt + val partrie = ParTrieMap((0 until length) zip (0 until length): _*) + + partrie.tasksupport = new ForkJoinTaskSupport(new scala.concurrent.forkjoin.ForkJoinPool(par)) + + def run = { + partrie map { + kv => kv + } + } + } + +El método `run` encapsula el código del microbenchmark que será ejecutado de +manera repetitiva y cuyos tiempos de ejecución serán medidos. El anterior objeto `Map` extiende +el trait `scala.testing.Benchmark` y parsea los parámetros `par` (nivel de paralelismo) y +`length` (número de elementos en el trie). Ambos parámetros son especificados a través de +propiedades del sistema. + +Tras compilar el programa anterior, podríamos ejecutarlo tal y como se muestra a continuación: + + java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=1 -Dlength=300000 Map 10 + +El flag `server` indica que la máquina virtual debe ser utiliada en modo servidor. `cp` especifica +el classpath e incluye todos los archivos __.class__ en el directorio actual así como el jar de +la librería de Scala. Los argumentos `-Dpar` y `-Dlength` representan el nivel de paralelismo y +el número de elementos respectivamente. Por último, `10` indica el número de veces que el benchmark +debería ser ejecutado en una misma máquina virtual. + +Los tiempos de ejecución obtenidos estableciendo `par` a los valores `1`, `2`, `4` y `8` sobre un +procesador quad-core i7 con hyperthreading habilitado son los siguientes: + + Map$ 126 57 56 57 54 54 54 53 53 53 + Map$ 90 99 28 28 26 26 26 26 26 26 + Map$ 201 17 17 16 15 15 16 14 18 15 + Map$ 182 12 13 17 16 14 14 12 12 12 + +Podemos observar en la tabla anterior que el tiempo de ejecución es mayor durante las +ejecuciones iniciales, reduciéndose a medida que el código va siendo optimizado. Además, +podemos ver que el beneficio del hyperthreading no es demasiado alto en este ejemplo +concreto, puesto que el incremento de `4` a `8` hilos produce un incremento mínimo en +el rendimiento. + +## ¿Cómo de grande debe ser una colección para utilizar la versión paralela? + +Esta es pregunta muy común y la respuesta es algo complicada. + +El tamaño de la colección a partir de la cual la paralelización merece la pena +depende de numerosos factores. Algunos de ellos, aunque no todos, son: + +- Arquitectura de la máquina. Diferentes tipos de CPU ofrecen diferente características + de rendimiento y escalabilidad. Por ejemplo, si la máquina es multicore o presenta + múltiples procesadores comunicados mediante la placa base. + +- Versión y proveedor de la JVM. Diferentes máquinas virtuales llevan a cabo + diferentes optimizaciones sobre el código en tiempo de ejecución. Implementan + diferente gestion de memoria y técnicas de sincronización. Algunas de ellas no + soportan el `ForkJoinPool`, volviendo a `ThreadPoolExecutor`, lo cual provoca + una sobrecarga mucho mayor. + +- Carga de trabajo por elemento. Una función o un predicado para una colección + paralela determina cómo de grande es la carga de trabajo por elemento. Cuanto + menor sea la carga de trabajo, mayor será el número de elementos requeridos para + obtener acelaraciones cuando se está ejecutando en paralelo. + +- Uso de colecciones específicas. Por ejemplo, `ParArray` y + `ParTrieMap` tienen "splitters" que recorren la colección a diferentes + velocidades, lo cual implica que existe más trabajo por elemento en el + propio recorrido. + +- Operación específica. Por ejemplo, `ParVector` es mucho más lenta para los métodos + de transformación (cómo `filter`) que para métodos de acceso (como `foreach`). + +- Efectos colaterales. Cuando se modifica un area de memoria de manera concurrente o + se utiliza la sincronización en el cuerpo de un `foreach`, `map`, etc se puede + producir contención. + +- Gestión de memoria. Cuando se reserva espacio para muchos objectos es posible + que se dispare un ciclo de recolección de basura. Dependiendo de cómo se + distribuyan las referencias de los objetos el ciclo de recolección puede llevar + más o menos tiempo. + +Incluso de manera independiente, no es sencillo razonar sobre el conjunto de situaciones +anteriores y determinar una respuesta precisa sobre cuál debería ser el tamaño de la +colección. Para ilustrar de manera aproximada cuál debería ser el valor de dicho tamaño, +a continuación, se presenta un ejemplo de una sencilla operación de reducción, __sum__ en este caso, +libre de efectos colaterales sobre un vector en un procesador i7 quad-core (hyperthreading +deshabilitado) sobre JDK7 + + import collection.parallel.immutable.ParVector + + object Reduce extends testing.Benchmark { + val length = sys.props("length").toInt + val par = sys.props("par").toInt + val parvector = ParVector((0 until length): _*) + + parvector.tasksupport = new collection.parallel.ForkJoinTaskSupport(new scala.concurrent.forkjoin.ForkJoinPool(par)) + + def run = { + parvector reduce { + (a, b) => a + b + } + } + } + + object ReduceSeq extends testing.Benchmark { + val length = sys.props("length").toInt + val vector = collection.immutable.Vector((0 until length): _*) + + def run = { + vector reduce { + (a, b) => a + b + } + } + } + +La primera ejecución del benchmark utiliza `250000` elementos y obtiene los siguientes resultados para `1`, `2` y `4` hilos: + + java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=1 -Dlength=250000 Reduce 10 10 + Reduce$ 54 24 18 18 18 19 19 18 19 19 + java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=2 -Dlength=250000 Reduce 10 10 + Reduce$ 60 19 17 13 13 13 13 14 12 13 + java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=4 -Dlength=250000 Reduce 10 10 + Reduce$ 62 17 15 14 13 11 11 11 11 9 + +Posteriormente se decrementa en número de elementos hasta `120000` y se utilizan `4` hilos para comparar +el tiempo con la operación de reducción sobre un vector secuencial: + + java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=4 -Dlength=120000 Reduce 10 10 + Reduce$ 54 10 8 8 8 7 8 7 6 5 + java -server -cp .:../../build/pack/lib/scala-library.jar -Dlength=120000 ReduceSeq 10 10 + ReduceSeq$ 31 7 8 8 7 7 7 8 7 8 + +En este caso, `120000` elementos parece estar en torno al umbral. + +En un ejemplo diferente, utilizamos `mutable.ParHashMap` y el método `map` (un método de transformación) +y ejecutamos el siguiente benchmark en el mismo entorno: + + import collection.parallel.mutable.ParHashMap + + object Map extends testing.Benchmark { + val length = sys.props("length").toInt + val par = sys.props("par").toInt + val phm = ParHashMap((0 until length) zip (0 until length): _*) + + phm.tasksupport = new collection.parallel.ForkJoinTaskSupport(new scala.concurrent.forkjoin.ForkJoinPool(par)) + + def run = { + phm map { + kv => kv + } + } + } + + object MapSeq extends testing.Benchmark { + val length = sys.props("length").toInt + val hm = collection.mutable.HashMap((0 until length) zip (0 until length): _*) + + def run = { + hm map { + kv => kv + } + } + } + +Para `120000` elementos obtenemos los siguientes tiempos cuando el número de hilos oscila de `1` a `4`: + + java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=1 -Dlength=120000 Map 10 10 + Map$ 187 108 97 96 96 95 95 95 96 95 + java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=2 -Dlength=120000 Map 10 10 + Map$ 138 68 57 56 57 56 56 55 54 55 + java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=4 -Dlength=120000 Map 10 10 + Map$ 124 54 42 40 38 41 40 40 39 39 + +Ahora, si reducimos el número de elementos a `15000` y comparamos con el hashmap secuencial: + + java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=1 -Dlength=15000 Map 10 10 + Map$ 41 13 10 10 10 9 9 9 10 9 + java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=2 -Dlength=15000 Map 10 10 + Map$ 48 15 9 8 7 7 6 7 8 6 + java -server -cp .:../../build/pack/lib/scala-library.jar -Dlength=15000 MapSeq 10 10 + MapSeq$ 39 9 9 9 8 9 9 9 9 9 + +Para esta colección y esta operacion tiene sentido utilizar la versión paralela cuando existen más +de `15000` elementos (en general, es factible paralelizar hashmaps y hashsets con menos elementos de +los que serían requeridos por arrays o vectores). + +## Referencias + +1. [Anatomy of a flawed microbenchmark, Brian Goetz][1] +2. [Dynamic compilation and performance measurement, Brian Goetz][2] + + [1]: http://www.ibm.com/developerworks/java/library/j-jtp02225/index.html "flawed-benchmark" + [2]: http://www.ibm.com/developerworks/library/j-jtp12214/ "dynamic-compilation" diff --git a/_es/tour/abstract-types.md b/_es/tour/abstract-types.md new file mode 100644 index 0000000000..ac54a11ca7 --- /dev/null +++ b/_es/tour/abstract-types.md @@ -0,0 +1,72 @@ +--- +layout: tour +title: Tipos Abstractos + +discourse: false + +partof: scala-tour + +num: 2 + +language: es + +next-page: annotations +previous-page: tour-of-scala +--- + +En Scala, las cases son parametrizadas con valores (los parámetros de construcción) y con tipos (si las clases son [genéricas](generic-classes.html)). Por razones de consistencia, no es posible tener solo valores como miembros de objetos; tanto los tipos como los valores son miembros de objetos. Además, ambos tipos de miembros pueden ser concretos y abstractos. +A continuación un ejemplo el cual define de forma conjunta una asignación de valor tardía y un tipo abstracto como miembros del [trait](traits.html) `Buffer`. + + trait Buffer { + type T + val element: T + } + +Los *tipos abstractos* son tipos los cuales su identidad no es precisamente conocida. En el ejemplo anterior, lo único que sabemos es que cada objeto de la clase `Buffer` tiene un miembro de tipo `T`, pero la definición de la clase `Buffer` no revela qué tipo concreto se corresponde con el tipo `T`. Tal como las definiciones de valores, es posible sobrescribir las definiciones de tipos en subclases. Esto permite revelar más información acerca de un tipo abstracto al acotar el tipo ligado (el cual describe las posibles instancias concretas del tipo abstracto). + +En el siguiente programa derivamos la clase `SeqBuffer` la cual nos permite almacenar solamente sequencias en el buffer al estipular que el tipo `T` tiene que ser un subtipo de `Seq[U]` para un nuevo tipo abstracto `U`: + + abstract class SeqBuffer extends Buffer { + type U + type T <: Seq[U] + def length = element.length + } + +Traits o [clases](classes.html) con miembros de tipos abstractos son generalmente usados en combinación con instancias de clases anónimas. Para ilustrar este concepto veremos un programa el cual trata con un buffer de sequencia que se remite a una lista de enteros. + + 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) + } + +El tipo retornado por el método `newIntSeqBuf` está ligado a la especialización del trait `Buffer` en el cual el tipo `U` es ahora equivalente a `Int`. Existe un tipo alias similar en la instancia de la clase anónima dentro del cuerpo del método `newIntSeqBuf`. En ese lugar se crea una nueva instancia de `IntSeqBuffer` en la cual el tipo `T` está ligado a `List[Int]`. + +Es necesario notar que generalmente es posible transformar un tipo abstracto en un tipo paramétrico de una clase y viceversa. A continuación se muestra una versión del código anterior el cual solo usa tipos paramétricos. + + 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) + } + +Nótese que es necesario usar [variance annotations](variances.html) aquí; de otra manera no sería posible ocultar el tipo implementado por la secuencia concreta del objeto retornado por `newIntSeqBuf`. Además, existen casos en los cuales no es posible remplazar tipos abstractos con tipos parametrizados. diff --git a/_es/tour/annotations.md b/_es/tour/annotations.md new file mode 100644 index 0000000000..2f9dea008d --- /dev/null +++ b/_es/tour/annotations.md @@ -0,0 +1,126 @@ +--- +layout: tour +title: Anotaciones + +discourse: false + +partof: scala-tour + +num: 3 +language: es + +next-page: classes +previous-page: abstract-types +--- + +Las anotaciones sirven para asociar meta-información con definiciones. + +Una anotación simple tiene la forma `@C` o `@C(a1, .., an)`. Aquí, `C` es un constructor de la clase `C`, que debe extender de la clase `scala.Annotation`. Todos los argumentos de construcción dados `a1, .., an` deben ser expresiones constantes (es decir, expresiones de números literales, strings, clases, enumeraciones de Java y arrays de una dimensión de estos valores). + +Una anotación se aplica a la primer definición o declaración que la sigue. Más de una anotación puede preceder una definición o declaración. El orden en que es dado estas anotaciones no importa. + +El significado de las anotaciones _depende de la implementación_. En la plataforma de Java, las siguientes anotaciones de Scala tienen un significado estandar. + +| Scala | Java | +| ------ | ------ | +| [`scala.SerialVersionUID`](https://www.scala-lang.org/api/current/scala/SerialVersionUID.html) | [`serialVersionUID`](http://java.sun.com/j2se/1.5.0/docs/api/java/io/Serializable.html#navbar_bottom) (campo, variable) | +| [`scala.deprecated`](https://www.scala-lang.org/api/current/scala/deprecated.html) | [`java.lang.Deprecated`](http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Deprecated.html) | +| [`scala.inline`](https://www.scala-lang.org/api/current/scala/inline.html) (desde 2.6.0) | sin equivalente | +| [`scala.native`](https://www.scala-lang.org/api/current/scala/native.html) (desde 2.6.0) | [`native`](http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html) (palabra clave) | +| [`scala.remote`](https://www.scala-lang.org/api/current/scala/remote.html) | [`java.rmi.Remote`](http://java.sun.com/j2se/1.5.0/docs/api/java/rmi/Remote.html) | +| [`scala.throws`](https://www.scala-lang.org/api/current/scala/throws.html) | [`throws`](http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html) (palabra clave) | +| [`scala.transient`](https://www.scala-lang.org/api/current/scala/transient.html) | [`transient`](http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html) (palabra clave) | +| [`scala.unchecked`](https://www.scala-lang.org/api/current/scala/unchecked.html) (desde 2.4.0) | sin equivalente | +| [`scala.volatile`](https://www.scala-lang.org/api/current/scala/volatile.html) | [`volatile`](http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html) (palabra clave) | +| [`scala.beans.BeanProperty`](https://www.scala-lang.org/api/current/scala/beans/BeanProperty.html) | [`Design pattern`](http://docs.oracle.com/javase/tutorial/javabeans/writing/properties.html) | + +En el siguiente ejemplo agregamos la anotación `throws` a la definición del método `read` de manera de capturar la excepción lanzada en el programa principal de Java. + +> El compilador de Java comprueba que un programa contenga manejadores para [excepciones comprobadas](http://docs.oracle.com/javase/specs/jls/se5.0/html/exceptions.html) al analizar cuales de esas excepciones comprobadas pueden llegar a lanzarse en la ejecución de un método o un constructor. Por cada excepción comprobada que sea un posible resultado, la cláusula **throws** debe para ese método o constructor debe ser mencionada en la clase de esa excepción o una de las superclases. +> Ya que Scala no tiene excepciones comprobadas, los métodos en Scala deben ser anotados con una o más anotaciones `throws` para que el código Java pueda capturar las excepciones lanzadas por un método de Scala. + + package examples + import java.io._ + class Reader(fname: String) { + private val in = new BufferedReader(new FileReader(fname)) + @throws(classOf[IOException]) + def read() = in.read() + } + +El siguiente programa de Java imprime en consola los contenidos del archivo cuyo nombre es pasado como primer argumento al método `main`. + + package test; + import examples.Reader; // Scala class !! + 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()); + } + } + } + +Si comentamos la anotación `throws` en la clase `Reader` se produce el siguiente error cuando se intenta compilar el programa principal de Java: + + Main.java:11: exception java.io.IOException is never thrown in body of + corresponding try statement + } catch (java.io.IOException e) { + ^ + 1 error + +### Anotaciones en Java ### + +**Nota:** Asegurate de usar la opción `-target:jvm-1.5` con anotaciones de Java. + +Java 1.5 introdujo metadata definida por el usuario en la forma de [anotaciones](http://java.sun.com/j2se/1.5.0/docs/guide/language/annotations.html). Una característica fundamental de las anotaciones es que se basan en pares nombre-valor específicos para inicializar sus elementos. Por ejemplo, si necesitamos una anotación para rastrear el código de alguna clase debemos definirlo así: + + @interface Source { + public String URL(); + public String mail(); + } + +Y después utilizarlo de la siguiente manera + + @Source(URL = "http://coders.com/", + mail = "support@coders.com") + public class MyClass extends HisClass ... + +Una anotación en Scala se asemeja a una invocación a un constructor. Para instanciar una anotación de Java es necesario usar los argumentos nombrados: + + @Source(URL = "http://coders.com/", + mail = "support@coders.com") + class MyScalaClass ... + +Esta sintaxis es bastante tediosa si la anotación contiene solo un elemento (sin un valor por defecto) por lo tanto, por convención, si el nombre es especificado como `value` puede ser utilizado en Java usando una sintaxis similar a la de los constructores: + + @interface SourceURL { + public String value(); + public String mail() default ""; + } + +Y podemos aplicarlo así: + + @SourceURL("http://coders.com/") + public class MyClass extends HisClass ... + +En este caso, Scala provee la misma posibilidad: + + @SourceURL("http://coders.com/") + class MyScalaClass ... + +El elemento `mail` fue especificado con un valor por defecto (mediante la cláusula `default`) por lo tanto no necesitamos proveer explicitamente un valor para este. De todas maneras, si necesitamos pasarle un valor no podemos mezclar los dos estilos en Java: + + @SourceURL(value = "http://coders.com/", + mail = "support@coders.com") + public class MyClass extends HisClass ... + +Scala provee más flexibilidad en este caso: + + @SourceURL("http://coders.com/", + mail = "support@coders.com") + class MyScalaClass ... diff --git a/_es/tour/anonymous-function-syntax.md b/_es/tour/anonymous-function-syntax.md new file mode 100644 index 0000000000..9801b51b6a --- /dev/null +++ b/_es/tour/anonymous-function-syntax.md @@ -0,0 +1,44 @@ +--- +layout: tour +title: Sintaxis de funciones anónimas + +discourse: false + +partof: scala-tour + +num: 14 +language: es + +next-page: currying +previous-page: nested-functions +--- + +Scala provee una sintaxis relativamente livana para definir funciones anónimas. La siguiente expresión crea una función incrementadora para números enteros: + + (x: Int) => x + 1 + +El código anterior es una forma compacta para la definición de la siguiente clase anónima: + + new Function1[Int, Int] { + def apply(x: Int): Int = x + 1 + } + +También es posible definir funciones con múltiples parámetros: + + (x: Int, y: Int) => "(" + x + ", " + y + ")" + +o sin parámetros: + + () => { System.getProperty("user.dir") } + +Existe también una forma simple para escribir los tipos de las funciones. A continuación se muestran los tipos de las trés funciones escritas anteriormente: + + Int => Int + (Int, Int) => String + () => String + +La sintaxis anterior es la forma sintética de escribir los siguientes tipos: + + Function1[Int, Int] + Function2[Int, Int, String] + Function0[String] diff --git a/_es/tour/automatic-closures.md b/_es/tour/automatic-closures.md new file mode 100644 index 0000000000..a04500bca2 --- /dev/null +++ b/_es/tour/automatic-closures.md @@ -0,0 +1,68 @@ +--- +layout: tour +title: Construcción de closures automáticas + +discourse: false + +partof: scala-tour + +num: 16 +language: es + +next-page: operators +previous-page: currying +--- + +Scala permite pasar funciones sin parámetros como parámetros de un método. Cuando un método así es invocado, los parámetros reales de la función enviada sin parámetros no son evaluados y una función "nularia" (de aridad cero, 0-aria, o sin parámetros) es pasada en su lugar. Esta función encapsula el comportamiento del parámetro correspondiente (comunmente conocido como "llamada por nombre"). + +Para aclarar un poco esto aquí se muestra un ejemplo: + + 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 + } + } + +La función `whileLoop` recibe dos parámetros `cond` y `body`. Cuando la función es llamada, los parámetros reales no son evaluados en ese momento. Pero cuando los parámetros son utilizados en el cuerpo de la función `whileLoop`, las funciones nularias creadas implícitamente serán evaluadas en su lugar. Así, nuestro método `whileLoop` implementa un bucle tipo Java mediante una implementación recursiva. + +Es posible combinar el uso de [operadores de infijo y postfijo (infix/postfix)](operators.html) con este mecanismo para crear declaraciones más complejas (con una sintaxis agradadable). + +Aquí mostramos la implementación de una declaración tipo repetir-a-menos-que (repetir el bucle a no ser que se cumpla X condición): + + 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) + } + +La función `loop` solo acepta el cuerpo de un bucle y retorna una instancia de la clase `LoopUnlessCond` (la cual encapsula el cuerpo del objeto). Es importante notar que en este punto el cuerpo del bucle no ha sido evaluado aún. La clase `LoopUnlessCond` tiene un método `unless` el cual puede ser usado como un *operador de infijo (infix)*. De esta manera podemos lograr una sintaxis muy natural para nuestro nuevo bucle `repetir { a_menos_que ( )`. + +A continuación se expone el resultado de la ejecución de `TargetTest2`: + + i = 10 + i = 9 + i = 8 + i = 7 + i = 6 + i = 5 + i = 4 + i = 3 + i = 2 + i = 1 diff --git a/_es/tour/case-classes.md b/_es/tour/case-classes.md new file mode 100644 index 0000000000..4e4a203b20 --- /dev/null +++ b/_es/tour/case-classes.md @@ -0,0 +1,81 @@ +--- +layout: tour +title: Clases Case + +discourse: false + +partof: scala-tour + +num: 5 +language: es + +next-page: compound-types +previous-page: classes +--- + +Scala da soporte a la noción de _clases caso_ (en inglés _case classes_, desde ahora _clases Case_). Las clases Case son clases regulares las cuales exportan sus parámetros constructores y a su vez proveen una descomposición recursiva de sí mismas a través de [reconocimiento de patrones](pattern-matching.html). + +A continuación se muestra un ejemplo para una jerarquía de clases la cual consiste de una super clase abstracta llamada `Term` y tres clases concretas: `Var`, `Fun` y `App`. + + abstract class Term + case class Var(name: String) extends Term + case class Fun(arg: String, body: Term) extends Term + case class App(f: Term, v: Term) extends Term + +Esta jerarquía de clases puede ser usada para representar términos de [cálculo lambda no tipado](http://www.ezresult.com/article/Lambda_calculus). Para facilitar la construcción de instancias de clases Case, Scala no requiere que se utilice la primitiva `new`. Simplemente es posible utilizar el nombre de la clase como una llamada a una función. + +Aquí un ejemplo: + + Fun("x", Fun("y", App(Var("x"), Var("y")))) + +Los parámetros constructores de las clases Case son tratados como valores públicos y pueden ser accedidos directamente. + + val x = Var("x") + println(x.name) + +Para cada una de las clases Case el compilador de Scala genera el método `equals` el cual implementa la igualdad estructural y un método `toString`. Por ejemplo: + + val x1 = Var("x") + val x2 = Var("x") + val y1 = Var("y") + println("" + x1 + " == " + x2 + " => " + (x1 == x2)) + println("" + x1 + " == " + y1 + " => " + (x1 == y1)) + +imprime + + Var(x) == Var(x) => true + Var(x) == Var(y) => false + +Solo tiene sentido definir una clase Case si el reconocimiento de patrones es usado para descomponer la estructura de los datos de la clase. El siguiente objeto define define una función de impresión `elegante` (en inglés `pretty`) que imprime en pantalla nuestra representación del cálculo lambda: + + object TermTest extends scala.App { + def printTerm(term: Term) { + term match { + case Var(n) => + print(n) + case Fun(x, b) => + print("^" + x + ".") + printTerm(b) + case App(f, v) => + print("(") + printTerm(f) + print(" ") + printTerm(v) + print(")") + } + } + def isIdentityFun(term: Term): Boolean = term match { + case Fun(x, Var(y)) if x == y => true + case _ => false + } + val id = Fun("x", Var("x")) + val t = Fun("x", Fun("y", App(Var("x"), Var("y")))) + printTerm(t) + println + println(isIdentityFun(id)) + println(isIdentityFun(t)) + } + +En nuestro ejemplo, la función `printTerm` es expresada como una sentencia basada en reconocimiento de patrones, la cual comienza con la palabra reservada `match` y consiste en secuencias de sentencias tipo `case PatronBuscado => Código que se ejecuta`. + +El programa de arriba también define una función `isIdentityFun` la cual comprueba si un término dado se corresponde con una función identidad simple. Ese ejemplo utiliza patrones y guardas más avanzados (obsrvese la guarda `if x==y`). Tras reconocer un patrón con un valor dado, la guarda (definida después de la palabra clave `if`) es evaluada. Si retorna `true` (verdadero), el reconocimiento es exitoso; de no ser así, falla y se intenta con el siguiente patrón. diff --git a/_es/tour/classes.md b/_es/tour/classes.md new file mode 100644 index 0000000000..1ed2c731dc --- /dev/null +++ b/_es/tour/classes.md @@ -0,0 +1,48 @@ +--- +layout: tour +title: Clases + +discourse: false + +partof: scala-tour + +num: 4 +language: es + +next-page: case-classes +previous-page: annotations +--- +En Scala, las clases son plantillas estáticas que pueden ser instanciadas por muchos objetos en tiempo de ejecución. +Aquí se presenta una clase la cual define la clase `Point`: + + class Point(xc: Int, yc: Int) { + var x: Int = xc + var y: Int = yc + def move(dx: Int, dy: Int) { + x = x + dx + y = y + dy + } + override def toString(): String = "(" + x + ", " + y + ")"; + } + +Esta clase define dos variables `x` e `y`, y dos métodos: `move` y `toString`. El método `move` recibe dos argumentos de tipo entero, pero no retorna ningún valor (implícitamente se retorna el tipo `Unit`, el cual se corresponde a `void` en lenguajes tipo Java). `toString`, por otro lado, no recibe ningún parámetro pero retorna un valor tipo `String`. Ya que `toString` sobreescribe el método `toString` predefinido en una superclase, tiene que ser anotado con `override`. + +Las clases en Scala son parametrizadas con argumentos constructores (inicializadores). En el código anterior se definen dos argumentos contructores, `xc` y `yc`; ambos son visibles en toda la clase. En nuestro ejemplo son utilizados para inicializar las variables `x` e `y`. + +Para instanciar una clase es necesario usar la primitiva `new`, como se muestra en el siguiente ejemplo: + + object Classes { + def main(args: Array[String]) { + val pt = new Point(1, 2) + println(pt) + pt.move(10, 10) + println(pt) + } + } + +El programa define una aplicación ejecutable a través del método `main` del objeto singleton `Classes`. El método `main` crea un nuevo `Point` y lo almacena en `pt`. _Note que valores definidos con la signatura `val` son distintos de los definidos con `var` (véase la clase `Point` arriba) ya que los primeros (`val`) no permiten reasignaciones; es decir, que el valor es una constante._ + +Aquí se muestra la salida del programa: + + (1, 2) + (11, 12) diff --git a/_es/tour/compound-types.md b/_es/tour/compound-types.md new file mode 100644 index 0000000000..c9f43d3eee --- /dev/null +++ b/_es/tour/compound-types.md @@ -0,0 +1,47 @@ +--- +layout: tour +title: Tipos Compuestos + +discourse: false + +partof: scala-tour + +num: 6 +language: es + +next-page: sequence-comprehensions +previous-page: case-classes +--- + +Algunas veces es necesario expresar que el tipo de un objeto es un subtipo de varios otros tipos. En Scala esto puede ser expresado con la ayuda de *tipos compuestos*, los cuales pueden entenderse como la intersección de otros tipos. + +Suponga que tenemos dos traits `Cloneable` y `Resetable`: + + trait Cloneable extends java.lang.Cloneable { + override def clone(): Cloneable = { + super.clone().asInstanceOf[Cloneable] + } + } + trait Resetable { + def reset: Unit + } + +Ahora suponga que queremos escribir una función `cloneAndReset` la cual recibe un objeto, lo clona y resetea el objeto original: + + def cloneAndReset(obj: ?): Cloneable = { + val cloned = obj.clone() + obj.reset + cloned + } + +La pregunta que surge es cuál es el tipo del parámetro `obj`. Si este fuera `Cloneable` entonces el objeto puede ser clonado mediante el método `clone`, pero no puede usarse el método `reset`; Si fuera `Resetable` podríamos resetearlo mediante el método `reset`, pero no sería posible clonarlo. Para evitar casteos (refundiciones, en inglés `casting`) de tipos en situaciones como la descrita, podemos especificar que el tipo del objeto `obj` sea tanto `Clonable` como `Resetable`. En tal caso estaríamos creando un tipo compuesto; de la siguiente manera: + + def cloneAndReset(obj: Cloneable with Resetable): Cloneable = { + //... + } + +Los tipos compuestos pueden crearse a partir de varios tipos de objeto y pueden tener un refinamiento el cual puede ser usado para acotar la signatura los miembros del objeto existente. + +La forma general es: `A with B with C ... { refinamiento }` + +Un ejemplo del uso de los refinamientos se muestra en la página sobre [tipos abstractos](abstract-types.html). diff --git a/_es/tour/currying.md b/_es/tour/currying.md new file mode 100644 index 0000000000..1e0fdac05e --- /dev/null +++ b/_es/tour/currying.md @@ -0,0 +1,41 @@ +--- +layout: tour +title: Currying + +discourse: false + +partof: scala-tour + +num: 15 +language: es + +next-page: automatic-closures +previous-page: anonymous-function-syntax +--- + +_Nota de traducción: Currying es una técnica de programación funcional nombrada en honor al matemático y lógico Haskell Curry. Es por eso que no se intentará hacer ninguna traducción sobre el término Currying. Entiendase este como una acción, técnica base de PF. Como una nota al paso, el lenguaje de programación Haskell debe su nombre a este eximio matemático._ + +Los métodos pueden definir múltiples listas de parámetros. Cuando un método es invocado con un número menor de listas de parámetros, en su lugar se devolverá una función que toma las listas faltantes como sus argumentos. + +Aquí se muestra un ejemplo: + + 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))) + } + +_Nota: el método `modN` está parcialmente aplicado en las dos llamadas a `filter`; esto significa que solo su primer argumento es realmente aplicado. El término `modN(2)` devuelve una función de tipo `Int => Boolean` y es por eso un posible candidato para el segundo argumento de la función `filter`_ + +Aquí se muestra la salida del programa anterior: + + List(2,4,6,8) + List(3,6) diff --git a/_es/tour/default-parameter-values.md b/_es/tour/default-parameter-values.md new file mode 100644 index 0000000000..eb4b70911d --- /dev/null +++ b/_es/tour/default-parameter-values.md @@ -0,0 +1,69 @@ +--- +layout: tour +title: Valores de parámetros por defecto + +discourse: false + +partof: scala-tour + +num: 34 +language: es + +next-page: named-parameters +previous-page: implicit-conversions +--- + +Scala tiene la capacidad de dar a los parámetros valores por defecto que pueden ser usados para permitir a quien invoca el método o función que omita dichos parámetros. + +En Java, uno tiende a ver muchos métodos sobrecargados que solamente sirven para proveer valores por defecto para ciertos parámetros de un método largo. En especial se ve este comportamiento en constructores: + + public class HashMap { + public HashMap(Map m); + /** Create a new HashMap with default capacity (16) + * and loadFactor (0.75) + */ + public HashMap(); + /** Create a new HashMap with default loadFactor (0.75) */ + public HashMap(int initialCapacity); + public HashMap(int initialCapacity, float loadFactor); + } + +Existen realmente dos constructores aquí; uno que toma otro mapa y uno que toma una capacidad y un factor de carga. Los constructores tercero y cuarto están ahí para premitir a los usuarios de la clase HashMap crear instancias con el valor por defecto que probablemente sea el mejor para ambos, el factor de carga y la capacidad. + +Más problemático es que los valores usados para ser por defecto están tanto en la documentación (Javadoc) como en el código. Mantener ambos actualizado es dificil. Un patrón típico utilizado para no cometer estos errores es agregar constantes públicas cuyo valor será mostrado en el Javadoc: + + public class HashMap { + public static final int DEFAULT_CAPACITY = 16; + public static final float DEFAULT_LOAD_FACTOR = 0.75; + + public HashMap(Map m); + /** Create a new HashMap with default capacity (16) + * and loadFactor (0.75) + */ + public HashMap(); + /** Create a new HashMap with default loadFactor (0.75) */ + public HashMap(int initialCapacity); + public HashMap(int initialCapacity, float loadFactor); + } + +Mientras esto evita repetirnos una y otra vez, es menos que expresivo. + +Scala cuenta con soporte directo para esto: + + class HashMap[K,V](initialCapacity:Int = 16, loadFactor:Float = 0.75) { + } + + // Usa los parametros por defecto + val m1 = new HashMap[String,Int] + + // initialCapacity 20, default loadFactor + val m2= new HashMap[String,Int](20) + + // sobreescribe ambos + val m3 = new HashMap[String,Int](20,0.8) + + // sobreescribe solamente loadFactor + // mediante parametros nombrados + val m4 = new HashMap[String,Int](loadFactor = 0.8) + +Nótese cómo podemos sacar ventaja de cualquier valor por defecto al utilizar [parámetros nombrados]({{ site.baseurl }}/tutorials/tour/named-parameters.html). diff --git a/_es/tour/explicitly-typed-self-references.md b/_es/tour/explicitly-typed-self-references.md new file mode 100644 index 0000000000..ff8545c75b --- /dev/null +++ b/_es/tour/explicitly-typed-self-references.md @@ -0,0 +1,105 @@ +--- +layout: tour +title: Autorefrencias explicitamente tipadas + +discourse: false + +partof: scala-tour + +num: 27 +language: es + +next-page: local-type-inference +previous-page: lower-type-bounds +--- + +Cuando se está construyendo software extensible, algunas veces resulta útil declarar el tipo de la variable `this` explícitamente. Para motivar esto, realizaremos una pequeña representación de una estructura de datos Grafo, en Scala. + +Aquí hay una definición que sirve para describir un grafo: + + abstract class Grafo { + type Vertice + type Nodo <: NodoIntf + abstract class NodoIntf { + def conectarCon(nodo: Nodo): Vertice + } + def nodos: List[Nodo] + def vertices: List[Vertice] + def agregarNodo: Nodo + } + +Los grafos consisten de una lista de nodos y vértices (o aristas en alguna bibliografía) donde tanto el tipo nodo, como el vértice fueron declarados abstractos. El uso de [tipos abstractos](abstract-types.html) permite las implementaciones del trait `Grafo` proveer sus propias clases concretas para nodos y vértices. Además, existe un método `agregarNodo` para agregar nuevos nodos al grafo. Los nodos se conectan entre sí utilizando el método `conectarCon`. + +Una posible implementación de la clase `Grafo`es dada en el siguiente programa: + + abstract class GrafoDirigido extends Grafo { + type Vertice <: VerticeImpl + class VerticeImpl(origen: Nodo, dest: Nodo) { + def desde = origen + def hasta = dest + } + class NodoImpl extends NodoIntf { + def conectarCon(nodo: Nodo): Vertice = { + val vertice = nuevoVertice(this, nodo) + vertices = vertice :: vertices + vertice + } + } + protected def nuevoNodo: Nodo + protected def nuevoVertice(desde: Nodo, hasta: Nodo): Vertice + var nodos: List[Nodo] = Nil + var vertices: List[Vertice] = Nil + def agregarNodo: Nodo = { + val nodo = nuevoNodo + nodos = nodo :: nodos + nodo + } + } + +La clase `GrafoDirigido` especializa la clase `Grafo` al proveer una implementación parcial. La implementación es solamente parcial, porque queremos que sea posible extender `GrafoDirigido` aun más. Por lo tanto, esta clase deja todos los detalles de implementación abiertos y así tanto los tipos vértice como nodo son abstractos. De todas maneras, la clase `GrafoDirigido` revela algunos detalles adicionales sobre la implementación del tipo vértice al acotar el límite a la clase `VerticeImpl`. Además, tenemos algunas implementaciones preliminares de vértices y nodos representados por las clases `VerticeImpl` y `NodoImpl`. + +Ya que es necesario crear nuevos objetos nodo y vértice con nuestra implementación parcial del grafo, también debimos agregar los métodos constructores `nuevoNodo` y `nuevoVertice`. Los métodos `agregarNodo` y `conectarCon` están ambos definidos en términos de estos métodos constructores. Una mirada más cercana a la implementación del método `conectarCon` revela que para crear un vértice es necesario pasar la auto-referencia `this` al método constructor `newEdge`. Pero a `this` en ese contexto le es asignado el tipo `NodoImpl`, por lo tanto no es compatible con el tipo `Nodo` el cual es requerido por el correspondiente método constructor. Como consecuencia, el programa superior no está bien definido y compilador mostrará un mensaje de error. + +En Scala es posible atar a una clase otro tipo (que será implementado en el futuro) al darle su propia auto-referencia `this` el otro tipo explicitamente. Podemos usar este mecanismo para arreglar nuestro código de arriba. El tipo the `this` explícito es especificado dentro del cuerpo de la clase `GrafoDirigido`. + +Este es el progama arreglado: + + abstract class GrafoDirigido extends Grafo { + ... + class NodoImpl extends NodoIntf { + self: Nodo => + def conectarCon(nodo: Nodo): Vertice = { + val vertice = nuevoVertice(this, nodo) // ahora legal + vertices = vertice :: vertices + vertice + } + } + ... + } + +En esta nueva definición de la clase `NodoImpl`, `this` tiene el tipo `Nodo`. Ya que `Nodo` es abstracta y por lo tanto todavía no sabemos si `NodoImpl` es realmente un subtipo de `Nodo`, el sistema de tipado de Scala no permitirá instanciar esta clase. Pero de todas maneras, estipulamos con esta anotación explicita de tipo que en algún momento en el tiempo, una subclase de `NodeImpl` tiene que denotar un subtipo del tipo `Nodo` de forma de ser instanciable. + +Aquí presentamos una especialización concreta de `GrafoDirigido` donde todos los miembros abstractos son definidos: + + class GrafoDirigidoConcreto extends GrafoDirigido { + type Vertice = VerticeImpl + type Nodo = NodoImpl + protected def nuevoNodo: Nodo = new NodoImpl + protected def nuevoVertice(d: Nodo, h: Node): Vertice = + new VerticeImpl(d, h) + } + + +Por favor nótese que en esta clase nos es posible instanciar `NodoImpl` porque ahora sabemos que `NodoImpl` denota a un subtipo de `Nodo` (que es simplemente un alias para `NodoImpl`). + +Aquí hay un ejemplo de uso de la clase `GrafoDirigidoConcreto`: + + object GraphTest extends App { + val g: Grafo = new GrafoDirigidoConcreto + val n1 = g.agregarNodo + val n2 = g.agregarNodo + val n3 = g.agregarNodo + n1.conectarCon(n2) + n2.conectarCon(n3) + n1.conectarCon(n3) + } diff --git a/_es/tour/extractor-objects.md b/_es/tour/extractor-objects.md new file mode 100644 index 0000000000..9278a2e276 --- /dev/null +++ b/_es/tour/extractor-objects.md @@ -0,0 +1,41 @@ +--- +layout: tour +title: Objetos Extractores + +discourse: false + +partof: scala-tour + +num: 8 +language: es + +next-page: generic-classes +previous-page: sequence-comprehensions +--- + +En Scala pueden ser definidos patrones independientemente de las clases Caso (en inglés case classes, desde ahora clases Case). Para este fin exite un método llamado `unapply` que proveera el ya dicho extractor. Por ejemplo, en el código siguiente se define el objeto extractor `Twice` + + 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) } // imprime 21 + } + +Hay dos convenciones sintácticas que entran en juego aquí: + +El patrón `case Twice(n)` causará la invocación del método `Twice.unapply`, el cual es usado para reconocer cualquier número par; el valor de retorno de `unapply` indica si el argumento produjo una coincidencia o no, y cualquier otro sub valor que pueda ser usado para un siguiente reconocimiento. Aquí, el sub-valor es `z/2`. + +El método `apply` no es necesario para reconocimiento de patrones. Solamente es usado para proveer un constructor. `val x = Twice(21)` se puede expandir como `val x = Twice.apply(21)`. + +El tipo de retorno de un método `unapply` debería ser elegido de la siguiente manera: +* Si es solamente una comprobación, retornar un `Boolean`. Por ejemplo, `case esPar()` +* Si retorna un único sub valor del tipo T, retornar un `Option[T]` +* Si quiere retornar varios sub valores `T1,...,Tn`, es necesario agruparlos en una tupla de valores opcionales `Option[(T1,...,Tn)]`. + +Algunas veces, el número de sub valores es fijo y nos gustaría retornar una secuencia. Por esta razón, siempre es posible definir patrones a través de `unapplySeq`. El último sub valor de tipo `Tn` tiene que ser `Seq[S]`. Este mecanismo es usado por ejemplo en el patrón `case List(x1, ..., xn)`. + +Los objetos extractores pueden hacer el código más mantenible. Para más detalles lea el paper ["Matching Objects with Patterns (Reconociendo objetos con patrones)"](https://infoscience.epfl.ch/record/98468/files/MatchingObjectsWithPatterns-TR.pdf) (ver sección 4) por Emir, Odersky y Williams (Enero de 2007). diff --git a/_es/tour/generic-classes.md b/_es/tour/generic-classes.md new file mode 100644 index 0000000000..949baba87c --- /dev/null +++ b/_es/tour/generic-classes.md @@ -0,0 +1,45 @@ +--- +layout: tour +title: Clases genéricas + +discourse: false + +partof: scala-tour + +num: 9 +language: es + +next-page: implicit-parameters +previous-page: extractor-objects +--- + +Tal como en Java 5 ([JDK 1.5](http://java.sun.com/j2se/1.5/)), Scala provee soporte nativo para clases parametrizados con tipos. Eso es llamado clases genéricas y son especialmente importantes para el desarrollo de clases tipo colección. + +A continuación se muestra un ejemplo: + + 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 } + } + +La clase `Stack` modela una pila mutable que contiene elementos de un tipo arbitrario `T` (se dice, "una pila de elementos `T`). Los parámetros de tipos nos aseguran que solo elementos legales (o sea, del tipo `T`) sean insertados en la pila (apilados). De forma similar, con los parámetros de tipo podemos expresar que el método `top` solo devolverá elementos de un tipo dado (en este caso `T`). + +Aquí se muestra un ejemplo del uso de dicha pila: + + object GenericsTest extends App { + val stack = new Stack[Int] + stack.push(1) + stack.push('a') + println(stack.top) + stack.pop() + println(stack.top) + } + +La salida del programa sería: + + 97 + 1 + +_Nota: los subtipos de tipos genéricos es *invariante*. Esto significa que si tenemos una pila de caracteres del tipo `Stack[Char]`, esta no puede ser usada como una pila de enteros tipo `Stack[Int]`. Esto no sería razonable ya que nos permitiría introducir elementos enteros en la pila de caracteres. Para concluir, `Stack[T]` es solamente un subtipo de `Stack[S]` si y solo si `S = T`. Ya que esto puede llegar a ser bastante restrictivo, Scala ofrece un [mecanismo de anotación de parámetros de tipo](variances.html) para controlar el comportamiento de subtipos de tipos genéricos._ diff --git a/_es/tour/higher-order-functions.md b/_es/tour/higher-order-functions.md new file mode 100644 index 0000000000..899dca2139 --- /dev/null +++ b/_es/tour/higher-order-functions.md @@ -0,0 +1,38 @@ +--- +layout: tour +title: Funciones de orden superior + +discourse: false + +partof: scala-tour + +num: 18 +language: es + +next-page: pattern-matching +previous-page: operators +--- + +Scala permite la definición de funciones de orden superior. Estas funciones son las que _toman otras funciones como parámetros_, o las cuales _el resultado es una función_. Aquí mostramos una función `apply` la cual toma otra función `f` y un valor `v` como parámetros y aplica la función `f` a `v`: + + def apply(f: Int => String, v: Int) = f(v) + +_Nota: los métodos son automáticamente tomados como funciones si el contexto lo requiere._ + +Otro ejemplo: + + 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)) + } + +La ejecución da como valor el siguiente resultado: + + [7] + +En este ejemplo, el método `decorator.layout` es coaccionado automáticamente a un valor del tipo `Int => String` como es requerido por el método `apply`. Por favor note que el método `decorator.layout` es un _método polimórfico_ (esto es, se abstrae de algunos de sus tipos) y el compilador de Scala primero tiene que instanciar correctamente el tipo del método. diff --git a/_es/tour/implicit-conversions.md b/_es/tour/implicit-conversions.md new file mode 100644 index 0000000000..1979510108 --- /dev/null +++ b/_es/tour/implicit-conversions.md @@ -0,0 +1,16 @@ +--- +layout: tour +title: Implicit Conversions + +discourse: false + +partof: scala-tour + +num: 32 +language: es + +next-page: default-parameter-values +previous-page: variances +--- + +(this page has not been translated into Spanish) diff --git a/_es/tour/implicit-parameters.md b/_es/tour/implicit-parameters.md new file mode 100644 index 0000000000..581593a7c3 --- /dev/null +++ b/_es/tour/implicit-parameters.md @@ -0,0 +1,50 @@ +--- +layout: tour +title: Parámetros implícitos + +discourse: false + +partof: scala-tour + +num: 10 +language: es + +next-page: inner-classes +previous-page: generic-classes +--- + +Un método con _parámetros implícitos_ puede ser aplicado a argumentos tal como un método normal. En este caso la etiqueta `implicit` no tiene efecto. De todas maneras, si a un método le faltan argumentos para sus parámetros implícitos, tales argumentos serán automáticamente provistos. + +Los argumentos reales que son elegibles para ser pasados a un parámetro implícito están contenidos en dos categorías: +* Primera, son elegibles todos los identificadores x que puedan ser accedidos en el momento de la llamada al método sin ningún prefijo y que denotan una definición implícita o un parámetro implícito. +* Segunda, además son elegibles todos los miembros de modulos `companion` (ver [objetos companion] (singleton-objects.html) ) del tipo de parámetro implicito que tienen la etiqueta `implicit`. + +En el siguiente ejemplo definimos un método `sum` el cual computa la suma de una lista de elementos usando las operaciones `add` y `unit` de `Monoid`. Note que los valores implícitos no pueden ser de nivel superior (top-level), deben ser miembros de una plantilla. + + abstract class SemiGroup[A] { + def add(x: A, y: A): A + } + abstract class Monoid[A] extends SemiGroup[A] { + def unit: A + } + object ImplicitTest extends App { + 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 + } + def sum[A](xs: List[A])(implicit m: Monoid[A]): A = + if (xs.isEmpty) m.unit + else m.add(xs.head, sum(xs.tail)) + + println(sum(List(1, 2, 3))) + println(sum(List("a", "b", "c"))) + } + +Esta es la salida del programa: + + 6 + abc diff --git a/_es/tour/inner-classes.md b/_es/tour/inner-classes.md new file mode 100644 index 0000000000..c072021dc8 --- /dev/null +++ b/_es/tour/inner-classes.md @@ -0,0 +1,90 @@ +--- +layout: tour +title: Clases Internas + +discourse: false + +partof: scala-tour + +num: 11 +language: es + +next-page: mixin-class-composition +previous-page: implicit-parameters +--- + +En Scala es posible que las clases tengan como miembro otras clases. A diferencia de lenguajes similares a Java donde ese tipo de clases internas son miembros de las clases que las envuelven, en Scala esas clases internas están ligadas al objeto externo. Para ilustrar esta diferencia, vamos a mostrar rápidamente una implementación del tipo grafo: + + 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 + } + } + +En nuestro programa, los grafos son representados mediante una lista de nodos. Estos nodos son objetos de la clase interna `Node`. Cada nodo tiene una lista de vecinos que se almacena en la lista `connectedNodes`. Ahora podemos crear un grafo con algunos nodos y conectarlos incrementalmente: + + 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) + } + +Ahora vamos a completar el ejemplo con información relacionada al tipado para definir explicitamente de qué tipo son las entidades anteriormente definidas: + + 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) + } + +El código anterior muestra que al tipo del nodo le es prefijado con la instancia superior (que en nuestro ejemplo es `g`). Si ahora tenemos dos grafos, el sistema de tipado de Scala no nos permite mezclar nodos definidos en un grafo con nodos definidos en otro, ya que los nodos del otro grafo tienen un tipo diferente. + +Aquí está el programa ilegal: + + object IllegalGraphTest extends App { + val g: Graph = new Graph + val n1: g.Node = g.newNode + val n2: g.Node = g.newNode + n1.connectTo(n2) // legal + val h: Graph = new Graph + val n3: h.Node = h.newNode + n1.connectTo(n3) // ilegal! + } + +Por favor note que en Java la última linea del ejemplo anterior hubiese sido correcta. Para los nodos de ambos grafos, Java asignaría el mismo tipo `Graph.Node`; es decir, `Node` es prefijado con la clase `Graph`. En Scala un tipo similar también puede ser definido, pero es escrito `Graph#Node`. Si queremos que sea posible conectar nodos de distintos grafos, es necesario modificar la implementación inicial del grafo de la siguiente manera: + + class Graph { + class Node { + var connectedNodes: List[Graph#Node] = Nil // Graph#Node en lugar de Node + 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 + } + } + +> Por favor note que este programa no nos permite relacionar un nodo con dos grafos diferentes. Si también quisiéramos eliminar esta restricción, sería necesario cambiar el tipo de la variable `nodes` a `Graph#Node`. diff --git a/_es/tour/local-type-inference.md b/_es/tour/local-type-inference.md new file mode 100644 index 0000000000..b236ca118a --- /dev/null +++ b/_es/tour/local-type-inference.md @@ -0,0 +1,55 @@ +--- +layout: tour +title: Inferencia de tipos Local + +discourse: false + +partof: scala-tour + +num: 29 +language: es + +next-page: unified-types +previous-page: explicitly-typed-self-references +--- + +Scala tiene incorporado un mecanismo de inferencia de tipos el cual permite al programador omitir ciertos tipos de anotaciones. Por ejemplo, generalmente no es necesario especificar el tipo de una variable, ya que el compilador puede deducir el tipo mediante la expresión de inicialización de la variable. También puede generalmente omitirse los tipos de retorno de métodos ya que se corresponden con el tipo del cuerpo, que es inferido por el compilador. + +Aquí hay un ejemplo: + + object InferenceTest1 extends App { + val x = 1 + 2 * 3 // el tipo de x es Int + val y = x.toString() // el tipo de y es String + def succ(x: Int) = x + 1 // el método succ retorna valores Int + } + +Para métodos recursivos, el compilador no es capaz de inferir el tipo resultado. A continuación mostramos un programa el cual falla por esa razón: + + object InferenceTest2 { + def fac(n: Int) = if (n == 0) 1 else n * fac(n - 1) + } + +Tampoco es obligatorio especificar el tipo de los parámetros cuando se trate de [métodos polimórficos](polymorphic-methods.html) o sean instanciadas [clases genéricas](generic-classes.html). El compilador de Scala inferirá esos tipos de parámetros faltantes mediante el contexto y de los tipos de los parámetros reales del método/constructor. + +Aquí se muestra un ejemplo que ilustra esto: + + case class MyPair[A, B](x: A, y: B); + object InferenceTest3 extends App { + def id[T](x: T) = x + val p = MyPair(1, "scala") // tipo: MyPair[Int, String] + val q = id(1) // tipo: Int + } + +Las últimas dos lineas de este programa son equivalentes al siguiente código, donde todos los tipos inferidos son especificados explicitamente: + + val x: MyPair[Int, String] = MyPair[Int, String](1, "scala") + val y: Int = id[Int](1) + +En algunas situaciones puede ser bastante peligroso confiar en el mecanismo de inferencia de tipos de Scala, como se ilustra en el siguiente ejemplo: + + object InferenceTest4 { + var obj = null + obj = new Object() + } + +Este programa no compila porque el tipo inferido para la variable `obj` es `Null`. Ya que el único valor de ese tipo es `null`, es imposible hacer que esta variable refiera a otro valor. diff --git a/_es/tour/lower-type-bounds.md b/_es/tour/lower-type-bounds.md new file mode 100644 index 0000000000..0819ecde6f --- /dev/null +++ b/_es/tour/lower-type-bounds.md @@ -0,0 +1,51 @@ +--- +layout: tour +title: Límite de tipado inferior + +discourse: false + +partof: scala-tour + +num: 26 +language: es + +next-page: explicitly-typed-self-references +previous-page: upper-type-bounds +--- + +Mientras que los [límites de tipado superior](upper-type-bounds.html) limitan el tipo de un subtipo de otro tipo, los *límites de tipado inferior* declaran que un tipo sea un supertipo de otro tipo. El término `T >: A` expresa que el parámetro de tipo `T` o el tipo abstracto `T` se refiera a un supertipo del tipo `A` + +Aquí se muestra un ejemplo donde esto es de utilidad: + + 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) + } + +El programa mostrado implementa una lista enlazada con una operación `prepend` (agregar al principio). Desafortunadamente este tipo es invariante en el parámetro de tipo de la clase `ListNode`; esto es, el tipo `ListNode[String]` no es un subtipo de `ListNode[Object]`. Con la ayuda de [anotaciones de varianza](variances.html) es posible expresar tal semantica de subtipos: + + case class ListNode[+T](h: T, t: ListNode[T]) { ... } // No compila + +Desafortunadamente, este programa no compila porque una anotación covariante es solo posible si el tipo de la variable es usado solo en posiciones covariantes. Ya que la variable de tipo `T` aparece como un parámetro de tipo en el método `prepend`, esta regla se rompe. Con la ayuda de un *límite de tipado inferior*, sin embargo, podemos implementar un método `prepend` donde `T` solo aparezca en posiciones covariantes. + +Este es el código correspondiente: + + 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) + } + +_Nota: el nuevo método `prepend` tiene un tipo un poco menos restrictivo. Esto permite, por ejemplo, agregar un objeto de un supertipo a una lista ya creada. La lista resultante será una lista de este supertipo._ + +Este código ilustra el concepto: + + 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/_es/tour/mixin-class-composition.md b/_es/tour/mixin-class-composition.md new file mode 100644 index 0000000000..5a48fc0a8b --- /dev/null +++ b/_es/tour/mixin-class-composition.md @@ -0,0 +1,50 @@ +--- +layout: tour +title: Composición de clases mixin + +discourse: false + +partof: scala-tour + +num: 12 +language: es + +next-page: singleton-objects +previous-page: inner-classes +--- +_Nota de traducción: La palabra `mixin` puede ser traducida como mezcla, dando título a esta sección de: Composición de clases Mezcla, pero es preferible utilizar la notación original_ + +A diferencia de lenguajes que solo soportan _herencia simple_, Scala tiene una notación más general de la reutilización de clases. Scala hace posible reutilizar la _nueva definición de miembros de una clase_ (es decir, el delta en relación a la superclase) en la definición de una nueva clase. Esto es expresado como una _composición de clases mixin_. Considere la siguiente abstracción para iteradores. + + abstract class AbsIterator { + type T + def hasNext: Boolean + def next: T + } + +A continuación, considere una clase mezcla la cual extiende `AbsIterator` con un método `foreach` el cual aplica una función dada a cada elemento retornado por el iterador. Para definir una clase que puede usarse como una clase mezcla usamos la palabra clave `trait`. + + trait RichIterator extends AbsIterator { + def foreach(f: T => Unit) { while (hasNext) f(next) } + } + +Aquí se muestra una clase iterador concreta, la cual retorna caracteres sucesivos de una cadena de caracteres dada: + + 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 } + } + +Nos gustaría combinar la funcionalidad de `StringIterator` y `RichIterator` en una sola clase. Solo con herencia simple e interfaces esto es imposible, ya que ambas clases contienen implementaciones para sus miembros. Scala nos ayuda con sus _compisiciones de clases mezcladas_. Permite a los programadores reutilizar el delta de la definición de una clase, esto es, todas las nuevas definiciones que no son heredadas. Este mecanismo hace posible combinar `StringIterator` con `RichIterator`, como es hecho en el siguiente programa, el cual imprime una columna de todos los caracteres de una cadena de caracteres dada. + + object StringIteratorTest { + def main(args: Array[String]) { + class Iter extends StringIterator(args(0)) with RichIterator + val iter = new Iter + iter foreach println + } + } + +La clase `Iter` en la función `main` es construida de una composición mixin de los padres `StringIterator` y `RichIterator` con la palabra clave `with`. El primera padre es llamado la _superclase_ de `Iter`, mientras el segundo padre (y cualquier otro que exista) es llamada un _mixin_. diff --git a/_es/tour/named-parameters.md b/_es/tour/named-parameters.md new file mode 100644 index 0000000000..6ecb8c877d --- /dev/null +++ b/_es/tour/named-parameters.md @@ -0,0 +1,37 @@ +--- +layout: tour +title: Parámetros nombrados + +discourse: false + +partof: scala-tour + +num: 35 +language: es + +previous-page: default-parameter-values +--- + +En la invocación de métodos y funciones se puede usar el nombre de las variables explícitamente en la llamada, de la siguiente manera: + + def imprimirNombre(nombre:String, apellido:String) = { + println(nombre + " " + apellido) + } + + imprimirNombre("John","Smith") + // Imprime "John Smith" + imprimirNombre(first = "John",last = "Smith") + // Imprime "John Smith" + imprimirNombre(last = "Smith",first = "John") + // Imprime "John Smith" + +Note que una vez que se utilizan parámetros nombrados en la llamada, el orden no importa, mientras todos los parámetros sean nombrados. Esta característica funciona bien en conjunción con [valores de parámetros por defecto]({{ site.baseurl }}/tutorials/tour/default_parameter_values.html): + + def imprimirNombre(nombre:String = "John", apellido:String = "Smith") = { + println(nombre + " " + apellido) + } + + printName(apellido = "Jones") + // Imprime "John Jones" + +Ya que es posible colocar los parámetros en cualquier orden que te guste, puedes usar el valor por defecto para parámetros que aparecen primero en la lista de parámetros. diff --git a/_es/tour/nested-functions.md b/_es/tour/nested-functions.md new file mode 100644 index 0000000000..c592b30c65 --- /dev/null +++ b/_es/tour/nested-functions.md @@ -0,0 +1,33 @@ +--- +layout: tour +title: Funciones Anidadas + +discourse: false + +partof: scala-tour + +num: 13 +language: es + +next-page: anonymous-function-syntax +previous-page: singleton-objects +--- + +En scala es posible anidar definiciones de funciones. El siguiente objeto provee una función `filter` para extraer valores de una lista de enteros que están por debajo de un valor determinado: + + 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)) + } + +_Nota: la función anidada `process` utiliza la variable `threshold` definida en el ámbito externo como un parámetro de `filter`._ + +La salida del programa es: + + List(1,2,3,4) diff --git a/_es/tour/operators.md b/_es/tour/operators.md new file mode 100644 index 0000000000..fa7c55cf65 --- /dev/null +++ b/_es/tour/operators.md @@ -0,0 +1,34 @@ +--- +layout: tour +title: Operadores + +discourse: false + +partof: scala-tour + +num: 17 +language: es + +next-page: higher-order-functions +previous-page: automatic-closures +--- + +En Scala, cualquier método el cual reciba un solo parámetro puede ser usado como un *operador de infijo (infix)*. Aquí se muestra la definición de la clase `MyBool`, la cual define tres métodos `and`, `or`, y `negate`. + + 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 = new MyBool(!x) + } + +Ahora es posible utilizar `and` y `or` como operadores de infijo: + + def not(x: MyBool) = x negate; // punto y coma necesario aquí + def xor(x: MyBool, y: MyBool) = (x or y) and not(x and y) + +Como muestra la primera linea del código anterior, es también posible utilizar métodos nularios (que no reciban parámetros) como operadores de postfijo. La segunda linea define la función `xor` utilizando los métodos `and`y `or` como también la función `not`. En este ejemplo el uso de los _operadores de postfijo_ ayuda a crear una definición del método `xor` más fácil de leer. + +Para demostrar esto se muestra el código correspondiente a las funciones anteriores pero escritas en una notación orientada a objetos más tradicional: + + def not(x: MyBool) = x.negate; // punto y coma necesario aquí + def xor(x: MyBool, y: MyBool) = x.or(y).and(x.and(y).negate) diff --git a/_es/tour/pattern-matching.md b/_es/tour/pattern-matching.md new file mode 100644 index 0000000000..d20d83bf56 --- /dev/null +++ b/_es/tour/pattern-matching.md @@ -0,0 +1,44 @@ +--- +layout: tour +title: Reconocimiento de patrones + +discourse: false + +partof: scala-tour + +num: 20 +language: es + +next-page: polymorphic-methods +previous-page: higher-order-functions +--- + +_Nota de traducción: Es dificil encontrar en nuestro idioma una palabra que se relacione directamente con el significado de `match` en inglés. Podemos entender a `match` como "coincidir" o "concordar" con algo. En algunos lugares se utiliza la palabra `machear`, aunque esta no existe en nuestro idioma con el sentido que se le da en este texto, sino que se utiliza como traducción de `match`._ + +Scala tiene incorporado un mecanismo general de reconocimiento de patrones. Este permite identificar cualquier tipo de datos una política primero-encontrado. Aquí se muestra un pequeño ejemplo el cual muestra cómo coincidir un valor entero: + + object MatchTest1 extends App { + def matchTest(x: Int): String = x match { + case 1 => "one" + case 2 => "two" + case _ => "many" + } + println(matchTest(3)) + } + +El bloque con las sentencias `case` define una función la cual mapea enteros a cadenas de caracteres (strings). La palabra reservada `match` provee una manera conveniente de aplicar una función (como la función anterior) a un objeto. + +Aquí se muestra un ejemplo el cual coincide un valor contra un patrón de diferentes tipos: + + 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")) + } + +El primer `case` coincide si `x` se refiere a un valor entero `1`. El segundo `case` coincide si `x` es igual al string `"two"`. El tercero consiste en un patrón tipado (se provee un tipo); se produce una coincidencia contra cualquier entero que se provea y además se liga la variable `y` al valor pasado `x` de tipo entero. + +El reconocimiento de patrones en Scala es más útil para hacer coincidir tipos algebráicos expresados mediante [clases case](case-classes.html). Scala también permite la definición de patrones independientemente de las clases Case, a través del método `unapply` de [objetos extractores](extractor-objects.html). diff --git a/_es/tour/polymorphic-methods.md b/_es/tour/polymorphic-methods.md new file mode 100644 index 0000000000..c14ca2a18d --- /dev/null +++ b/_es/tour/polymorphic-methods.md @@ -0,0 +1,30 @@ +--- +layout: tour +title: Métodos polimórficos + +discourse: false + +partof: scala-tour + +num: 21 +language: es + +next-page: regular-expression-patterns +previous-page: pattern-matching +--- + +Los métodos en Scala pueden ser parametrizados tanto con valores como con tipos. Como a nivel de clase, parámetros de valores son encerrados en un par de paréntesis, mientras que los parámetros de tipo son declarados dentro de un par de corchetes. + +Aquí hay un ejemplo: + + object PolyTest extends App { + def dup[T](x: T, n: Int): List[T] = + if (n == 0) Nil + else x :: dup(x, n - 1) + println(dup[Int](3, 4)) // linea 5 + println(dup("three", 3)) // linea 6 + } + +El método `dup` en el objeto `PolyTest` es parametrizado con el tipo `T` y con los parámetros `x: T` y `n: Int`. Cuando el método `dup` es llamado, el programador provee los parámetros requeridos _(vea la linea 5 del programa anterior)_, pero como se muestra en la linea 6 no es necesario que se provea el parámetro de tipo `T` explicitamente. El sistema de tipado de Scala puede inferir estos tipos. Esto es realizado a través de la observación del tipo de los parámetros pasados y del contexto donde el método es invocado. + +Por favor note que el trait `App` está diseñado para escribir programas cortos de pruebas. Debe ser evitado en código en producción (para versiones de Scala 2.8.x y anteriores) ya que puede afectar la habilidad de la JVM de optimizar el código resultante; por favor use `def main()` en su lugar. diff --git a/_es/tour/regular-expression-patterns.md b/_es/tour/regular-expression-patterns.md new file mode 100644 index 0000000000..bb0571a27a --- /dev/null +++ b/_es/tour/regular-expression-patterns.md @@ -0,0 +1,44 @@ +--- +layout: tour +title: Patrones basados en expresiones regulares + +discourse: false + +partof: scala-tour + +num: 22 +language: es + +next-page: traits +previous-page: polymorphic-methods +--- + +## Patrones de secuencias que ignoran a la derecha ## + +Los patrones de secuencias que ignoran a la derecha son una característica útil para separar cualquier dato que sea tanto un subtipo de `Seq[A]` o una clase case con un parámetro iterador formal, como por ejemplo + + Elem(prefix:String, label:String, attrs:MetaData, scp:NamespaceBinding, children:Node*) + +En esos casos, Scala permite a los patrones que utilicen el cómodin `_*` en la posición más a la derecha que tomen lugar para secuencias arbitrariamente largas. El siguiente ejemplo demuestra un reconocimiento de patrones el cual identifica un prefijo de una secuencia y liga el resto a la variable `rest`. + + 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 + } + } + } + + +A diferencia de versiones previas de Scala, ya no está permitido tener expresiones regulares arbitrarias, por las siguientes razones. + +###Patrones generales de expresiones regulares (`RegExp`) temporariamente retirados de Scala### + +Desde que descubrimos un problema en la precisión, esta característica está temporariamente retirada del lenguaje. Si existiese una petición de parte de la comunidad de usuarios, podríamos llegar a reactivarla de una forma mejorada. + +De acuerdo a nuestra opinión los patrones basados en expresiones regulares no resultaron útiles para el procesamiento de XML. En la vida real, las aplicaciones que procesan XML, XPath parece una opción mucho mejor. Cuando descubrimos que nuestra traducción de los patrones para expresiones regulares tenía algunos errores para patrones raros y poco usados, aunque difícil de excluir, decidimos que sería tiempo de simplificar el lenguaje. diff --git a/_es/tour/sequence-comprehensions.md b/_es/tour/sequence-comprehensions.md new file mode 100644 index 0000000000..203a01b4c8 --- /dev/null +++ b/_es/tour/sequence-comprehensions.md @@ -0,0 +1,60 @@ +--- +layout: tour +title: Sequencias por Comprensión + +discourse: false + +partof: scala-tour + +num: 7 +language: es + +next-page: extractor-objects +previous-page: compound-types +--- + +Scala cuenta con una notación ligera para expresar *sequencias por comprensión* (*sequence comprehensions*). Las comprensiones tienen la forma `for (enumeradores) yield e`, donde `enumeradores` se refiere a una lista de enumeradores separados por el símbolo punto y coma (;). Un *enumerador* puede ser tanto un generador el cual introduce nuevas variables, o un filtro. La comprensión evalúa el cuerpo `e` por cada paso (o ciclo) generado por los enumeradores y retorna una secuencia de estos valores. + +Aquí hay un ejemplo: + + object ComprehensionTest1 extends App { + def pares(desde: Int, hasta: Int): List[Int] = + for (i <- List.range(desde, hasta) if i % 2 == 0) yield i + Console.println(pares(0, 20)) + } + +La expresión `for` en la función introduce una nueva variable `i` de tipo `Int` la cual es subsecuentemente atada a todos los valores de la lista `List(desde, desde + 1, ..., hasta - 1)`. La guarda `if i % 2 == 0` filtra los números impares por lo que el cuerpo (que solo consiste de la expresión `i`) es solamente evaluado para números pares. Consecuentemente toda la expresión `for` retorna una lista de números pares. + +El programa produce los siguientes valores + + List(0, 2, 4, 6, 8, 10, 12, 14, 16, 18) + +Aquí se muestra un ejemplo más complicado que computa todos los pares de números entre `0` y `n-1` cuya suma es igual a un número dado `v`: + + object ComprehensionTest2 extends App { + def foo(n: Int, v: Int) = + for (i <- 0 until n; + j <- i until n if i + j == v) yield + Pair(i, j); + foo(20, 32) foreach { + case (i, j) => + println("(" + i + ", " + j + ")") + } + } + +Este ejemplo muestra que las comprensiones no están restringidas solo a listas. El programa anterior usa iteradores en su lugar. Cualquier tipo de datos que soporte las operaciones `withFilter`, `map`, y `flatMap` (con los tipos apropiados) puede ser usado en la comprensión de secuencias. + +Esta es la salida del programa: + + (13, 19) + (14, 18) + (15, 17) + (16, 16) + +Existe también una forma especial de comprensión de secuencias la cual retorna `Unit`. En este caso las variables que son creadas por la lista de generadores y filtros son usados para realizar tareas con efectos colaterales (modificaciones de algún tipo). El programador tiene que omitir la palabra reservada `yield` para usar una comprensión de este tipo. + + object ComprehensionTest3 extends App { + for (i <- Iterator.range(0, 20); + j <- Iterator.range(i, 20) if i + j == 32) + println("(" + i + ", " + j + ")") + } diff --git a/_es/tour/singleton-objects.md b/_es/tour/singleton-objects.md new file mode 100644 index 0000000000..8c15cf50f4 --- /dev/null +++ b/_es/tour/singleton-objects.md @@ -0,0 +1,68 @@ +--- +layout: tour +title: Singleton Objects + +discourse: false + +partof: scala-tour + +num: 12 +language: es + +next-page: nested-functions +previous-page: mixin-class-composition +--- + +Métodos y valores que no están asociados con instancias individuales de una [clase](classes.html) se denominan *objetos singleton* y se denotan con la palabra reservada `object` en vez de `class`. + + package test + + object Blah { + def sum(l: List[Int]): Int = l.sum + } + +Este método `sum` está disponible de manera global, y puede ser referenciado, o importado, como `test.Blah.sum`. + +Los objetos singleton son una especie de mezcla entre la definición de una clase de utilización única, la cual no pueden ser instanciada directamente, y un miembro `val`. De hecho, de la misma menera que los `val`, los objetos singleton pueden ser definidos como miembros de un [trait](traits.html) o de una clase, aunque esto no es muy frecuente. + +Un objeto singleton puede extender clases y _traits_. De hecho, una [clase Case](case-classes.html) sin [parámetros de tipo](generic-classes.html) generará por defecto un objeto singleton del mismo nombre, con una [`Función*`](http://www.scala-lang.org/api/current/scala/Function1.html) trait implementada. + +## Acompañantes ## + +La mayoría de los objetos singleton no están solos, sino que en realidad están asociados con clases del mismo nombre. El "objeto singleton del mismo nombre" de una case Case, mencionada anteriormente es un ejemplo de esto. Cuando esto sucede, el objeto singleton es llamado el *objeto acompañante* de la clase, y la clase es a su vez llamada la *clase acompañante* del objeto. + +[Scaladoc](https://wiki.scala-lang.org/display/SW/Introduction) proporciona un soporte especial para ir y venir entre una clase y su acompañante: Si el gran círculo conteniendo la “C” u la “O” tiene su borde inferior doblado hacia adentro, es posible hacer click en el círculo para ir a su acompañante. + +Una clase y su objeto acompañante, si existe, deben estar definidos en el mismo archivo fuente. Como por ejemplo: + + 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)) + } + +Es común ver instancias de clases tipo como [valores implícitos](implicit-parameters.html), (`ipord` en el ejemplo anterior) definida en el acompañante cuando se sigue el patron de clases tipo. Esto es debido a que los miembros del acompañante se incluyen en la búsqueda de implícitos por defecto. + +## Notas para los programadores Java ## +`static` no es una palabra reservada en Scala. En cambio, todos los miembros que serían estáticos, incluso las clases, van en los objetos acompañantes. Estos, pueden ser referenciados usando la misma sintaxis, importados de manera individual o en grupo, etc. + +Frecuentemente, los programadores Java, definen miembros estáticos, incluso definidos como `private`, como ayudas en la implementacion de los miembros de la instancia. Estos elementos también van en el objeto acompañante. Un patrón comúnmente utilizado es de importar los miembros del objeto acompañante en la clase, como por ejemplo: + + class X { + import X._ + + def blah = foo + } + + object X { + private def foo = 42 + } + +Esto permite ilustrar otra característica: en el contexto de un `private`, una clase y su acompañante son amigos. El `objecto X` puede acceder miembros de la `clase X`, y vice versa. Para hacer un miembro *realmente* privado para uno u otro, utilice `private[this]`. + +Para conveniencia de Java, los métodos que incluyen `var` y `val`, definidos directamente en un objeto singleton también tienen un método estático definido en la clase acompañante, llamado *static forwarder*. Otros miembros son accesibles por medio del campo estático `X$.MODULE$` para el `objeto X`. + +Si todos los elementos se mueven al objeto acompanante y se descubre que lo que queda es una clase que no se quiere instanciar, entonces simplemente bórrela. Los *static forwarder* de todas formas van a ser creados. diff --git a/_es/tour/tour-of-scala.md b/_es/tour/tour-of-scala.md new file mode 100644 index 0000000000..ba2fe0eeaf --- /dev/null +++ b/_es/tour/tour-of-scala.md @@ -0,0 +1,48 @@ +--- +layout: tour +title: Introducción + +discourse: false + +partof: scala-tour + +num: 1 +language: es + +next-page: abstract-types +--- + +Scala es un lenguaje de programación moderno multi-paradigma diseñado para expresar patrones de programación comunes de una forma concisa, elegante, y de tipado seguro. Integra fácilmente características de lenguajes orientados a objetos y funcionales. + +## Scala es orientado a objetos ## +Scala es un lenguaje puramente orientado a objetos en el sentido de que [todo es un objeto](unified_types.html). Los tipos y comportamientos de objetos son descritos por [clases](classes.html) y [traits](traits.html) (que podría ser traducido como un "rasgo"). Las clases pueden ser extendidas a través de subclases y un mecanismo flexible [de composición mezclada](mixin-class-composition.html) que provee un claro remplazo a la herencia múltiple. + +## Scala es funcional ## +Scala es también un lenguaje funcional en el sentido que [toda función es un valor](unified_types.html). Scala provee una [sintaxis ligera](anonymous-function-syntax.html) para definir funciones anónimas. Soporta [funciones de primer orden](higher-order-functions.html), permite que las funciones sean [anidadas](nested-functions.html), y soporta [currying](currying.html). Las [clases caso](case-classes.html) de Scala y las construcciones incorporadas al lenguaje para [reconocimiento de patrones](pattern-matching.html) modelan tipos algebráicos usados en muchos lenguajes de programación funcionales. + +Además, la noción de reconocimiento de patrones de Scala se puede extender naturalmente al [procesamiento de datos XML](xml-processing.html) con la ayuda de [patrones de expresiones regulares](regular-expression-patterns.html). En este contexto, [seq comprehensions](sequence-comprehensions.html) resultan útiles para formular consultas. Estas características hacen a Scala ideal para desarrollar aplicaciones como Web Services. + +## Scala estáticamente tipado ## +Scala cuenta con un expresivo sistema de tipado que fuerza estáticamente las abstracciones a ser usadas en una manera coherente y segura. En particular, el sistema de tipado soporta: +* [Clases genéricas](generic-classes.html) +* [anotaciones variables](variances.html), +* límites de tipado [superiores](upper-type-bounds.html) e [inferiores](lower-type-bounds.html), +* [clases internas](inner-classes.html) y [tipos abstractos](abstract-types.html) como miembros de objetos, +* [tipos compuestos](compound-types.html) +* [auto-referencias explicitamente tipadas](explicitly-typed-self-references.html) +* [implicit conversions](implicit-conversions.html) +* [métodos polimórficos](polymorphic-methods.html) + +El [mecanismo de inferencia de tipos locales](local-type-inference.html) se encarga de que el usuario no tengan que anotar el programa con información redundante de tipado. Combinadas, estas características proveen una base poderosa para el reuso seguro de abstracciones de programación y para la extensión segura (en cuanto a tipos) de software. + +## Scala es extensible ## + +En la práctica el desarrollo de aplicaciones específicas para un dominio generalmente requiere de "Lenguajes de dominio específico" (DSL). Scala provee una única combinación de mecanismos del lenguaje que simplifican la creación de construcciones propias del lenguaje en forma de librerías: +* cualquier método puede ser usado como un operador de [infijo o postfijo](operators.html) +* [las closures son construidas automáticamente dependiendo del tipo esperado](automatic-closures.html) (tipos objetivo). + +El uso conjunto de ambas características facilita la definición de nuevas sentencias sin tener que extender la sintaxis y sin usar facciones de meta-programación como tipo macros. + +Scala está diseñado para interoperar bien con el popular Entorno de ejecución de Java 2 (JRE). En particular, la interacción con el lenguaje orientado a objetos Java es muy sencillo. Scala tiene el mismo esquema de compilación (compilación separada, carga de clases dinámica) que java y permite acceder a las miles de librerías de gran calidad existentes. + +Por favor continúe a la próxima página para conocer más. diff --git a/_es/tour/traits.md b/_es/tour/traits.md new file mode 100644 index 0000000000..363fee8148 --- /dev/null +++ b/_es/tour/traits.md @@ -0,0 +1,48 @@ +--- +layout: tour +title: Traits + +discourse: false + +partof: scala-tour + +num: 24 +language: es + +next-page: upper-type-bounds +previous-page: regular-expression-patterns +--- + +_Nota de traducción: La palabra `trait` en inglés puede traducirse literalmente como `rasgo` o `caracteristica`. Preferimos la designación original trait por ser una característica muy natural de Scala._ + +De forma similar a las interfaces de Java, los traits son usados para definir tipos de objetos al especificar el comportamiento mediante los métodos provistos. A diferencia de Java, Scala permite a los traits ser parcialmente implementados, esto es, es posible definir implementaciones por defecto para algunos métodos. En contraste con las clases, los traits no pueden tener parámetros de constructor. +A continuación se muestra un ejemplo: + + trait Similarity { + def isSimilar(x: Any): Boolean + def isNotSimilar(x: Any): Boolean = !isSimilar(x) + } + +Este trait consiste de dos métodos `isSimilar` y `isNotSimilar`. Mientras `isSimilar` no provee una implementación concreta del método (es abstracto en la terminología Java), el método `isNotSimilar` define una implementación concreta. Consecuentemente, las clases que integren este trait solo tienen que proveer una implementación concreta para `isSimilar`. El comportamiento de `isNotSimilar` es directamente heredado del trait. Los traits típicamente son integrados a una clase (u otros traits) mediante una [Composición de clases mixin](mixin-class-composition.html): + + 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) + println(p1.isNotSimilar(p2)) + println(p1.isNotSimilar(p3)) + println(p1.isNotSimilar(2)) + } + +Esta es la salida del programa: + + false + true + true diff --git a/_es/tour/unified-types.md b/_es/tour/unified-types.md new file mode 100644 index 0000000000..3bdc3541af --- /dev/null +++ b/_es/tour/unified-types.md @@ -0,0 +1,48 @@ +--- +layout: tour +title: Tipos Unificados + +discourse: false + +partof: scala-tour + +num: 30 +language: es + +next-page: variances +previous-page: local-type-inference +--- + +A diferencia de Java, todos los valores en Scala son objetos (incluyendo valores numéricos y funciones). Dado que Scala está basado en clases, todos los valores son instancias de una clase. El diagrama siguiente ilustra esta jerarquía de clases: + +![Jerarquía de Tipos de Scala]({{ site.baseurl }}/resources/images/classhierarchy.img_assist_custom.png) + +## Jerarquía de clases en Scala ## + +La superclase de todas las clases, `scala.Any`, tiene dos subclases directas, `scala.AnyVal` y `scala.AnyRef` que representan dos mundos de clases muy distintos: clases para valores y clases para referencias. Todas las clases para valores están predefinidas; se corresponden con los tipos primitivos de los lenguajes tipo Java. Todas las otras clases definen tipos referenciables. Las clases definidas por el usuario son definidas como tipos referenciables por defecto, es decir, siempre (indirectamente) extienden de `scala.AnyRef`. Toda clase definida por usuario en Scala extiende implicitamente el trait `scala.ScalaObject`. Clases pertenecientes a la infraestructura en la cual Scala esté corriendo (ejemplo, el ambiente de ejecución de Java) no extienden de `scala.ScalaObject`. Si Scala es usado en el contexto de un ambiente de ejecución de Java, entonces `scala.AnyRef` corresponde a `java.lang.Object`. +Por favor note que el diagrama superior también muestra conversiones implícitas llamadas viestas entre las clases para valores. + +Aquí se muestra un ejemplo que demuestra que tanto valores numéricos, de caracteres, buleanos y funciones son objetos, tal como cualquier otro objeto: + + object UnifiedTypes extends App { + val set = new scala.collection.mutable.LinkedHashSet[Any] + set += "This is a string" // suma un String + set += 732 // suma un número + set += 'c' // suma un caracter + set += true // suma un valor booleano + set += main _ // suma la función main + val iter: Iterator[Any] = set.iterator + while (iter.hasNext) { + println(iter.next.toString()) + } + } + +El programa declara una aplicación `UnifiedTypes` en forma de un objeto singleton de primer nivel con un método `main`. La aplicación define una variable local `set` (un conjunto), la cual se refiere a una instancia de la clase `LinkedHashSet[Any]`. El programa suma varios elementos a este conjunto. Los elementos tienen que cumplir con el tipo declarado para los elementos del conjunto, que es `Any`. Al final, una representación en texto (cadena de caracteres, o string) es impresa en pantalla. + +Aquí se muestra la salida del programa: + + This is a string + 732 + c + true + diff --git a/_es/tour/upper-type-bounds.md b/_es/tour/upper-type-bounds.md new file mode 100644 index 0000000000..84124c8944 --- /dev/null +++ b/_es/tour/upper-type-bounds.md @@ -0,0 +1,37 @@ +--- +layout: tour +title: Límite de tipado superior + +discourse: false + +partof: scala-tour + +num: 25 +language: es + +next-page: lower-type-bounds +previous-page: traits +--- + +En Scala, los [parámetros de tipo](generic-classes.html) y los [tipos abstractos](abstract-types.html) pueden ser restringidos por un límite de tipado. Tales límites de tipado limitan los valores concretos de las variables de tipo y posiblemente revelan más información acerca de los miembros de tales tipos. Un _límite de tipado superior_ `T <: A` declara que la variable de tipo `T` es un subtipo del tipo `A`. +Aquí se muestra un ejemplo el cual se basa en un límite de tipado superior para la implementación del método polimórfico `findSimilar`: + + trait Similar { + def isSimilar(x: Any): Boolean + } + case class MyInt(x: Int) extends Similar { + def isSimilar(m: Any): Boolean = + m.isInstanceOf[MyInt] && + m.asInstanceOf[MyInt].x == x + } + object UpperBoundTest extends App { + def findSimilar[T <: Similar](e: T, xs: List[T]): Boolean = + if (xs.isEmpty) false + else if (e.isSimilar(xs.head)) true + else findSimilar[T](e, xs.tail) + val list: List[MyInt] = List(MyInt(1), MyInt(2), MyInt(3)) + println(findSimilar[MyInt](MyInt(4), list)) + println(findSimilar[MyInt](MyInt(2), list)) + } + +Sin la anotación del límite de tipado superior no sería posible llamar al método `isSimilar` en el método `findSimilar`. El uso de los límites de tipado inferiores se discute [aquí](lower-type-bounds.html). diff --git a/_es/tour/variances.md b/_es/tour/variances.md new file mode 100644 index 0000000000..ad4a8fc3c2 --- /dev/null +++ b/_es/tour/variances.md @@ -0,0 +1,43 @@ +--- +layout: tour +title: Varianzas + +discourse: false + +partof: scala-tour + +num: 31 +language: es + +next-page: implicit-conversions +previous-page: unified-types +--- + +Scala soporta anotaciones de varianza para parámetros de tipo para [clases genéricas](generic-classes.html). A diferencia de Java 5 ([JDK 1.5](http://java.sun.com/j2se/1.5/)), las anotaciones de varianza pueden ser agregadas cuando una abstracción de clase es definidia, mientras que en Java 5, las anotaciones de varianza son dadas por los clientes cuando una albstracción de clase es usada. + +En el artículo sobre clases genéricas dimos un ejemplo de una pila mutable. Explicamos que el tipo definido por la clase `Stack[T]` es objeto de subtipos invariantes con respecto al parámetro de tipo. Esto puede restringir el reuso de la abstracción (la clase). Ahora derivaremos una implementación funcional (es decir, inmutable) para pilas que no tienen esta restricción. Nótese que este es un ejemplo avanzado que combina el uso de [métodos polimórficos](polymorphic-methods.html), [límites de tipado inferiores](lower-type-bounds.html), y anotaciones de parámetros de tipo covariante de una forma no trivial. Además hacemos uso de [clases internas](inner-classes.html) para encadenar los elementos de la pila sin enlaces explícitos. + +```tut +class Stack[+T] { + def push[S >: T](elem: S): Stack[S] = new Stack[S] { + override def top: S = elem + override def pop: Stack[S] = Stack.this + override def toString: String = + elem.toString + " " + Stack.this.toString + } + def top: T = sys.error("no element on stack") + def pop: Stack[T] = sys.error("no element on stack") + override def toString: String = "" +} + +object VariancesTest extends App { + var s: Stack[Any] = new Stack().push("hello") + s = s.push(new Object()) + s = s.push(7) + println(s) +} +``` + +La anotación `+T` declara que el tipo `T` sea utilizado solamente en posiciones covariantes. De forma similar, `-T` declara que `T` sea usado en posiciones contravariantes. Para parámetros de tipo covariantes obtenemos una relación de subtipo covariante con respecto al parámetro de tipo. Para nuestro ejemplo, esto significa que `Stack[T]` es un subtipo de `Stack[S]` si `T` es un subtipo de `S`. Lo contrario se cumple para parámetros de tipo que son etiquetados con un signo `-`. + +Para el ejemplo de la pila deberíamos haber usado el parámetro de tipo covariante `T` en una posición contravariante para que nos sea posible definir el método `push`. Ya que deseamos que existan subtipos covariantes para las pilas, utilizamos un truco y utilizamos un parámetro de tipo abstracto en el método `push`. De esta forma obtenemos un método polimórfico en el cual utilizamos el tipo del elemento `T` como límite inferior de la variable de tipo de `push`. Esto tiene el efecto de sincronizar la varianza de `T` con su declaración como un parámetro de tipo covariante. Ahora las pilas son covariantes, y nuestra solución permite por ejemplo apilar un String en una pila de enteros (Int). El resultado será una pila de tipo `Stack[Any]`; por lo tanto solo si el resultado es utilizado en un contexto donde se esperan pilas de enteros se detectará un error. De otra forma, simplemente se obtiene una pila con un tipo más general. diff --git a/_es/tutorials/scala-for-java-programmers.md b/_es/tutorials/scala-for-java-programmers.md new file mode 100644 index 0000000000..71a7ba6023 --- /dev/null +++ b/_es/tutorials/scala-for-java-programmers.md @@ -0,0 +1,412 @@ +--- +layout: singlepage-overview +title: Tutorial de Scala para programadores Java + +partof: scala-for-java-programmers + +discourse: true +language: es +--- + +Por Michel Schinz y Philipp Haller. +Traducción y arreglos Santiago Basulto. + +## Introducción + +Este documento provee una rápida introducción al lenguaje Scala como también a su compilador. Está pensado para personas que ya poseen cierta experiencia en programación y quieren una vista rápida de lo que pueden hacer con Scala. Se asume como un conocimiento básico de programación orientada a objetos, especialmente en Java. + +## Un primer ejemplo + +Como primer ejemplo, usaremos el programa *Hola mundo* estándar. No es muy fascinante, pero de esta manera resulta fácil demostrar el uso de herramientas de Scala sin saber demasiado acerca del lenguaje. Veamos como luce: + + object HolaMundo { + def main(args: Array[String]) { + println("¡Hola, mundo!") + } + } + +La estructura de este programa debería ser familiar para programadores Java: consiste de un método llamado `main` que toma los argumentos de la línea de comando (un array de objetos String) como parámetro; el cuerpo de este método consiste en una sola llamada al método predefinido `println` con el saludo amistoso como argumento. El método `main` no retorna un valor (se puede entender como un procedimiento). Por lo tanto, no es necesario que se declare un tipo retorno. + +Lo que es menos familiar a los programadores Java es la declaración de `object` que contiene al método `main`. Esa declaración introduce lo que es comúnmente conocido como *objeto singleton*, que es una clase con una sola instancia. Por lo tanto, dicha construcción declara tanto una clase llamada `HolaMundo` como una instancia de esa clase también llamada `HolaMundo`. Esta instancia es creada bajo demanda, es decir, la primera vez que es utilizada. + +El lector astuto notará que el método `main` no es declarado como `static`. Esto es así porque los miembros estáticos (métodos o campos) no existen en Scala. En vez de definir miembros estáticos, el programador de Scala declara estos miembros en un objeto singleton. + +### Compilando el ejemplo + +Para compilar el ejemplo utilizaremos `scalac`, el compilador de Scala. `scalac` funciona como la mayoría de los compiladores. Toma un archivo fuente como argumento, algunas opciones y produce uno o varios archivos objeto. Los archivos objeto que produce son archivos class de Java estándar. + +Si guardamos el programa anterior en un archivo llamado `HolaMundo.scala`, podemos compilarlo ejecutando el siguiente comando (el símbolo mayor `>` representa el prompt del shell y no debe ser escrita): + + > scalac HolaMundo.scala + +Esto generará algunos archivos class en el directorio actual. Uno de ellos se llamará `HolaMundo.class` y contiene una clase que puede ser directamente ejecutada utilizando el comando `scala`, como mostramos en la siguiente sección. + +### Ejecutando el ejemplo + +Una vez compilado, un programa Scala puede ser ejecutado utilizando el comando `scala`. Su uso es muy similar al comando `java` utilizado para ejecutar programas Java, y acepta las mismas opciones. El ejemplo de arriba puede ser ejecutado utilizando el siguiente comando, que produce la salida esperada: + + > scala -classpath . HolaMundo + + ¡Hola, mundo! + +## Interacción con Java + +Una de las fortalezas de Scala es que hace muy fácil interactuar con código Java. Todas las clases del paquete `java.lang` son importadas por defecto, mientras otras necesitan ser importadas explícitamente. + +Veamos un ejemplo que demuestra esto. Queremos obtener y formatear la fecha actual de acuerdo a convenciones utilizadas en un país específico, por ejemplo Francia. + +Las librerías de clases de Java definen clases de utilería poderosas, como `Date` y `DateFormat`. Ya que Scala interacciona fácilmente con Java, no es necesario implementar estas clases equivalentes en las librerías de Scala --podemos simplemente importar las clases de los correspondientes paquetes de Java: + + import java.util.{Date, Locale} + import java.text.DateFormat + import java.text.DateFormat._ + + object FrenchDate { + def main(args: Array[String]) { + val ahora = new Date + val df = getDateInstance(LONG, Locale.FRANCE) + println(df format ahora) + } + } + +Las declaraciones de importación de Scala lucen muy similares a las de Java, sin embargo, las primeras son bastante más poderosas. Múltiples clases pueden ser importadas desde el mismo paquete al encerrarlas en llaves como se muestra en la primer línea. Otra diferencia es que podemos importar todos los nombres de un paquete o clase, utilizando el carácter guión bajo (`_`) en vez del asterisco (`*`). Eso es porque el asterisco es un identificador válido en Scala (quiere decir que por ejemplo podemos nombrar a un método `*`), como veremos más adelante. + +La declaración `import` en la tercer línea por lo tanto importa todos los miembros de la clase `DateFormat`. Esto hace que el método estático `getDateInstance` y el campo estático `LONG` sean directamente visibles. + +Dentro del método `main` primero creamos una instancia de la clase `Date` la cual por defecto contiene la fecha actual. A continuación definimos un formateador de fechas utilizando el método estático `getDateInstance` que importamos previamente. Finalmente, imprimimos la fecha actual formateada de acuerdo a la instancia de `DateFormat` que fue "localizada". Esta última línea muestra una propiedad interesante de la sintaxis de Scala. Los métodos que toman un solo argumento pueden ser usados con una sintaxis de infijo Es decir, la expresión + + df format ahora + +es solamente otra manera más corta de escribir la expresión: + + df.format(ahora) + +Esto parece tener como un detalle sintáctico menor, pero tiene importantes consecuencias, una de ellas la exploraremos en la próxima sección. + +Para concluir esta sección sobre la interacción con Java, es importante notar que es también posible heredar de clases Java e implementar interfaces Java directamente en Scala. + +## Todo es un objeto + +Scala es un lenguaje puramente orientado a objetos en el sentido de que *todo* es un objeto, incluyendo números o funciones. Difiere de Java en este aspecto, ya que Java distingue tipos primitivos (como `boolean` e `int`) de tipos referenciales, y no nos permite manipular las funciones como valores. + +### Los números son objetos + +Ya que los números son objetos, estos también tienen métodos. De hecho, una expresión aritmética como la siguiente: + + 1 + 2 * 3 / x + +Consiste exclusivamente de llamadas a métodos, porque es equivalente a la siguiente expresión, como vimos en la sección anterior: + + (1).+(((2).*(3))./(x)) + +Esto también indica que `+`, `*`, etc. son identificadores válidos en Scala. + +Los paréntesis alrededor de los números en la segunda versión son necesarios porque el analizador léxico de Scala usa la regla de "mayor coincidencia". Por lo tanto partiría la siguiente expresión: + + 1.+(2) + +En estas partes: `1.`, `+`, y `2`. La razón que esta regla es elegida es porque `1.` es una coincidencia válida y es mayor que `1`, haciendo a este un `Double` en vez de un `Int`. Al escribir la expresión así: + + (1).+(2) + +previene que el `1` sea tomado como un `Double`. + +### Las funciones son objetos + +Tal vez suene más sorprendente para los programadores Java, las funciones en Scala también son objetos. Por lo tanto es posible pasar funciones como argumentos, almacenarlas en variables, y retornarlas desde otras funciones. Esta habilidad de manipular funciones como valores es una de las valores fundamentales de un paradigma de programación muy interesante llamado *programación funcional*. + +Como un ejemplo muy simple de por qué puede ser útil usar funciones como valores consideremos una función *temporizador* (o timer, en inglés) cuyo propósito es realizar alguna acción cada un segundo. ¿Cómo pasamos al temporizador la acción a realizar? Bastante lógico, como una función. Este simple concepto de pasar funciones debería ser familiar para muchos programadores: es generalmente utilizado en código relacionado con Interfaces gráficas de usuario (GUIs) para registrar "retrollamadas" (call-back en inglés) que son invocadas cuando un evento ocurre. + +En el siguiente programa, la función del temporizador se llama `unaVezPorSegundo` y recibe una función call-back como argumento. El tipo de esta función es escrito de la siguiente manera: `() => Unit` y es el tipo de todas las funciones que no toman argumentos ni retornan valores (el tipo `Unit` es similar a `void` en Java/C/C++). La función principal de este programa simplemente invoca esta función temporizador con una call-back que imprime una sentencia en la terminal. En otras palabras, este programa imprime interminablemente la sentencia "El tiempo vuela como una flecha" cada segundo. + + object Temporizador { + def unaVezPorSegundo(callback: () => Unit) { + while (true) { callback(); Thread sleep 1000 } + } + def tiempoVuela() { + println("El tiempo vuela como una flecha...") + } + def main(args: Array[String]) { + unaVezPorSegundo(tiempoVuela) + } + } + +_Nota: si nunca tuviste experiencias previas con programación funcional te recomiendo que te tomes unos segundos para analizar cuando se utilizan paréntesis y cuando no en los lugares donde aparece *callback*. Por ejemplo, dentro de la declaración de `unaVezPorSegundo` no aparece, ya que se trata de la función como un "valor", a diferencia de cómo aparece dentro del método, ya que en ese caso se la está invocando (por eso los paréntesis)._ +Note that in order to print the string, we used the predefined method +`println` instead of using the one from `System.out`. + +#### Funciones anónimas + +El programa anterior es fácil de entender, pero puede ser refinado aún más. Primero que nada es interesante notar que la función `tiempoVuela` está definida solamente para ser pasada posteriormente a la función `unaVezPorSegundo`. Tener que nombrar esa función, que es utilizada solamente una vez parece un poco innecesario y sería bueno poder construirla justo cuando sea pasada a `unaVezPorSegundo`. Esto es posible en Scala utilizando *funciones anónimas*, que son exactamente eso: funciones sin nombre. La versión revisada de nuestro temporizador utilizando una función anónima luce así: + + object TemporizadorAnonimo { + def unaVezPorSegundo(callback: () => Unit) { + while (true) { callback(); Thread sleep 1000 } + } + def main(args: Array[String]) { + unaVezPorSegundo( + () => println("El tiempo vuela como una flecha...") + ) + } + } + +La presencia de una función anónima en este ejemplo es revelada por la flecha a la derecha `=>` que separa los argumentos de la función del cuerpo de esta. En este ejemplo, la lista de argumentos está vacía, como se ve por el par de paréntesis vacíos a la izquierda de la flecha. El cuerpo de la función es el mismo que en `tiempoVuela` del programa anterior. + +## Clases + +Como hemos visto anteriormente, Scala es un lenguaje orientado a objetos, y como tal tiene el concepto de Clase (en realidad existen lenguajes orientados a objetos que no cuentan con el concepto de clases, pero Scala no es uno de ellos). Las clases en Scala son declaradas utilizando una sintaxis que es cercana a la de Java. Una diferencia importante es que las clases en Scala pueden tener parámetros. Ilustramos esto en el siguiente ejemplo, la definición de un número complejo: + + class Complejo(real: Double, imaginaria: Double) { + def re() = real + def im() = imaginaria + } + +Esta clase compleja toma dos argumentos, que son las partes real e imaginarias de un número complejo. Estos argumentos deben ser pasados cuando se crea una instancia de la clase `Complejo`, de la siguiente manera: + + new Complejo(1.5, 2.3) + +La clase contiene dos métodos llamados `re` e `im`, que proveen acceso a las dos partes del número. + +Debe notarse que el tipo de retorno de estos dos métodos no está expresado explícitamente. Será inferido automáticamente por el compilador, que primero mira la parte derecha de estos métodos y puede deducir que ambos retornan un valor de tipo `Double`. + +El compilador no es siempre capaz de inferir los tipos como lo hace aquí, y desafortunadamente no existe una regla simple para saber cuándo será y cuándo no. En la práctica, esto generalmente no es un problema ya que el compilador se queja cuando no es capaz de inferir un tipo que no fue explícitamente fijado. Como regla simple, los programadores de Scala novatos deberían tratar de omitir las declaraciones de tipos que parecen ser simples de deducir del contexto y ver si el compilador no lanza errores. Después de algún tiempo, el programador debería tener una buena idea de cuando omitir tipos y cuando explicitarlos. + +### Métodos sin argumentos + +Un pequeño problema de los métodos `re` e `im` es que para poder llamarlos es necesario agregar un par de paréntesis vacíos después de sus nombres, como muestra el siguiente ejemplo: + + object NumerosComplejos { + def main(args: Array[String]) { + val c = new Complejo(1.2, 3.4) + println("Parte imaginaria: " + c.im()) + } + } + +Sería mejor poder acceder las partes imaginarias y reales como si fueran campos, sin poner los paréntesis vacíos. Esto es perfectamente realizable en Scala, simplemente al definirlos como *métodos sin argumentos*. Tales métodos difieren de los métodos con cero o más argumentos en que no tienen paréntesis después de su nombre, tanto en la definición como en el uso. Nuestra clase `Complejo` puede ser reescrita así: + + class Complejo(real: Double, imaginaria: Double) { + def re = real + def im = imaginaria + } + + +### Herencia y sobreescritura + +Todas las clases en Scala heredan de una superclase. Cuando ninguna superclase es especificada, como es el caso de `Complejo` se utiliza implícitamente `scala.AnyRef`. + +Es posible sobreescribir métodos heredados de una superclase en Scala. Aunque es necesario explicitar específicamente que un método sobreescribe otro utilizando el modificador `override`, de manera de evitar sobreescrituras accidentales. Como ejemplo, nuestra clase `Complejo` puede ser aumentada con la redefinición del método `toString` heredado de `Object`. + + class Complejo(real: Double, imaginaria: Double) { + def re = real + def im = imaginaria + override def toString() = + "" + re + (if (im < 0) "" else "+") + im + "i" + } + +## Clases Case y Reconocimiento de patrones + +Un tipo de estructura de datos que aparece seguido en programas es el Árbol. Por ejemplo, los intérpretes y compiladores usualmente representan los programas internamente como árboles; los documentos XML son árboles; y muchos otros tipos de contenedores están basados en árboles, como los árboles rojo y negro. + +Ahora examinaremos cómo estos árboles son representados y manipulados en Scala mediante un pequeño programa que oficie de calculadora. El objetivo de este programa es manipular expresiones aritméticas simples compuestas de sumas de enteros y variables. Dos ejemplos de estas expresiones pueden ser: `1+2` y `(x+x)+(7+y)`. + +Primero tenemos que decidir una representación para tales expresiones. La más natural es un árbol, donde los nodos son las operaciones (la adición en este caso) y las hojas son valores (constantes o variables). + +En Java, un árbol así sería representado utilizando una superclase abstracta para los árboles, y una subclase concreta por nodo u hoja. En un lenguaje de programación funcional uno utilizaría un tipo de dato algebraico para el mismo propósito. Scala provee el concepto de *clases case* que está en el medio de los dos conceptos anteriores. Aquí mostramos como pueden ser usadas para definir el tipo de los árboles en nuestro ejemplo: + + abstract class Arbol + case class Sum(l: Arbol, r: Arbol) extends Arbol + case class Var(n: String) extends Arbol + case class Const(v: Int) extends Arbol + +El hecho de que las clases `Sum`, `Var` y `Const` sean declaradas como clases case significa que dififieren de las clases normales en varios aspectos: + +- no es obligatorio utilizar la palabra clave `new` para crear + instancias de estas clases (es decir, se puede escribir `Const(5)` + en lugar de `new Const(5)`), +- se crea automáticamente un "getter" (un método para obtener el valor) + para los parámetros utilizados en el constructor (por ejemplo es posible + obtener el valor de `v` de una instancia `c` de la clase `Const` de la + siguiente manera: `c.v`), +- se proveen definiciones por defecto de los métodos `equals` y `hashCode`, + que trabajan sobre la estructura de las instancias y no sobre su identidad, +- se crea una definición por defecto del método `toString` que + imprime el valor de una forma "tipo código) (ej: la expresión + del árbol `x+1` se imprimiría `Sum(Var(x),Const(1))`), +- las instancias de estas clases pueden ser descompuestas + mediante *reconocimiento de patrones* (pattern matching) + como veremos más abajo. + +Ahora que hemos definido el tipo de datos para representar nuestra expresión aritmética podemos empezar definiendo operaciones para manipularlas. Empezaremos con una función para evaluar una expresión en un *entorno*. El objetivo del entorno es darle valores a las variables. Por ejemplo, la expresión `x+1` evaluada en un entorno que asocia el valor `5` a la variable `x`, escrito `{ x -> 5 }`, da como resultado `6`. + +Por lo tanto tenemos que encontrar una manera de representar entornos. Podríamos por supuesto utilizar alguna estructura de datos asociativa como una tabla hash, pero podemos directamente utilizar funciones! Un entorno realmente no es nada más que una función la cual asocia valores a variables. El entorno `{ x -> 5 }` mostrado anteriormente puede ser fácilmente escrito de la siguiente manera en Scala: + + { case "x" => 5 } + +Esta notación define una función la cual, dado un string `"x"` como argumento retorna el entero `5`, y falla con una excepción si no fuera así. + +Antes de escribir la función evaluadora, démosle un nombre al tipo de los entornos. Podríamos por supuesto simplemente utilizar `String => Int` para los entornos, pero simplifica el programa introducir un nombre para este tipo, y hace que los futuros cambios sean más fáciles. Esto lo realizamos de la siguiente manera: + + type Entorno = String => Int + +De ahora en más, el tipo `Entorno` puede ser usado como un alias del tipo de funciones definidas de `String` a `Int`. + +Ahora podemos dar la definición de la función evaluadora. Conceptualmente, es muy sencillo: el valor de una suma de dos expresiones es simplemente la suma de los valores de estas expresiones; el valor de una variable es obtenido directamente del entorno; y el valor de una constante es la constante en sí misma. Expresar esto en Scala no resulta para nada difícil: + + def eval(a: Arbol, ent: Entorno): Int = a match { + case Sum(i, d) => eval(i, ent) + eval(d, env) + case Var(n) => ent(n) + case Const(v) => v + } + +Esta función evaluadora función realizando un *reconocimiento de patrones* (pattern matching) en el árbol `a`. Intuitivamente, el significado de la definición de arriba debería estar claro: + +1. Primero comprueba si el árbol `t`es una `Sum`, y si lo es, asocia el sub-arbol izquierdo a una nueva variable llamada `i` y el sub-arbol derecho a la variable `r`, y después procede con la evaluación de la expresión que sigue a la flecha (`=>`); esta expresión puede (y hace) uso de las variables asociadas por el patrón que aparece del lado izquierdo de la flecha. +2. si la primer comprobación (la de `Sum`) no prospera, es decir que el árbol no es una `Sum`, sigue de largo y comprueba si `a` es un `Var`; si lo es, asocia el nombre contenido en el nodo `Var` a la variable `n` y procede con la parte derecha de la expresión. +3. si la segunda comprobación también falla, resulta que `a` no es un `Sum` ni un `Var`, por lo tanto comprueba que sea un `Const`, y si lo es, asocia el valor contenido en el nodo `Const` a la variable `v`y procede con el lado derecho. +4. finalmente, si todos las comprobaciones fallan, una excepción es lanzada para dar cuenta el fallo de la expresión; esto puede pasar solo si existen más subclases de `Arbol`. + +Hemos visto que la idea básica del reconocimiento de patrones es intentar coincidir un valor con una serie de patrones, y tan pronto como un patrón coincida, extraer y nombrar las varias partes del valor para finalmente evaluar algo de código que típicamente hace uso de esas partes nombradas. + +Un programador con experiencia en orientación a objetos puede preguntarse por qué no definimos `eval` como un método de la clase `Arbol` y sus subclases. En realidad podríamos haberlo hecho, ya que Scala permite la definición de métodos en clases case tal como en clases normales. Por lo tanto decidir en usar reconocimiento de patrones o métodos es una cuestión de gustos, pero también tiene grandes implicancias en cuanto a la extensibilidad: + +- cuando usamos métodos, es fácil añadir un nuevo tipo de nodo ya que esto puede ser realizado simplemente al definir una nueva subclase de `Arbol`; por otro lado, añadir una nueva operación para manipular el árbol es tedioso, ya que requiere la modificación en todas las subclases. + +- cuando utilizamos reconocimiento de patrones esta situación es inversa: agregar un nuevo tipo de nodo requiere la modificación de todas las funciones que hacen reconocimiento de patrones sobre el árbol, para tomar en cuenta un nuevo nodo; pero por otro lado agregar una nueva operación fácil, solamente definiendolo como una función independiente. + +Para explorar un poco más esto de pattern matching definamos otra operación aritmética: derivación simbólica. El lector recordará las siguientes reglas sobre esta operación: + +1. la derivada de una suma es la suma de las derivadas, +2. la derivada de una variable `v` es uno (1) si `v` es la variable relativa a la cual la derivada toma lugar, y cero (0)de otra manera, +3. la derivada de una constante es cero (0). + +Estas reglas pueden ser traducidas casi literalmente en código Sclaa, para obtener la siguiente definición. + + def derivada(a: Arbol, v: String): Arbol = a match { + case Sum(l, r) => Sum(derivada(l, v), derivada(r, v)) + case Var(n) if (v == n) => Const(1) + case _ => Const(0) + } + +Esta función introduce dos nuevos conceptos relacionados al pattern matching. Primero que nada la expresión `case` para variables tienen una *guarda*, una expresión siguiendo la palabra clave `if`. Esta guarda previene que el patrón concuerde al menos que la expresión sea verdadera. Aquí es usada para asegurarse que retornamos la constante 1 solo si el nombre de la variable siendo derivada es el mismo que la variable derivada `v`. El segundo concepto nuevo usado aquí es el *comodín*, escrito con el guión bajo `_`, que coincide con cualquier valor que aparezca, sin darle un nombre. + +No hemos explorado el completo poder del pattern matching aún, pero nos detendremos aquí para mantener este documento corto. Todavía nos queda pendiente ver cómo funcionan las dos funciones de arriba en un ejemplo real. Para ese propósito, escribamos una función main simple que realice algunas operaciones sobre la expresión `(x+x)+(7+y)`: primero computa su valor en el entorno `{ x -> 5, y -> 7 }` y después computa su derivada con respecto a `x` y después a `y`. + + def main(args: Array[String]) { + val exp: Arbol = Sum(Sum(Var("x"),Var("x")),Sum(Const(7),Var("y"))) + val ent: Entonrno = { case "x" => 5 case "y" => 7 } + println("Expresión: " + exp) + println("Evaluación con x=5, y=7: " + eval(exp, ent)) + println("Derivada con respecto a x:\n " + derivada(exp, "x")) + println("Derivada con respecto a y:\n " + derivada(exp, "y")) + } + +Al ejecutar este programa obtenemos el siguiente resultado: + + Expresión: Sum(Sum(Var(x),Var(x)),Sum(Const(7),Var(y))) + Evaluación con x=5, y=7: 24 + Derivada con respecto a x: + Sum(Sum(Const(1),Const(1)),Sum(Const(0),Const(0))) + Derivada con respecto a y: + Sum(Sum(Const(0),Const(0)),Sum(Const(0),Const(1))) + +Al examinar la salida vemos que el resultado de la derivada debería ser simplificado antes de ser presentado al usuario. Definir una función de simplificación básica utilizando reconocimiento de patrones es un problema interesante (y, por no decir complejo, que necesita una solución astuta), lo dejamos para un ejercicio para el lector. + +## Traits + +_Nota: La palabra Trait(/treɪt/, pronunciado Treit) puede ser traducida literalmente como "Rasgo". De todas maneras decido utilizar la notación original por ser un concepto muy arraigado a Scala_ + +Aparte de poder heredar código de una super clase, una clase en Scala puede también importar código de uno o varios *traits*. + +Tal vez la forma más fácil para un programador Java de entender qué son los traits es verlos como interfaces que también pueden contener código. En Scala, cuando una clase hereda de un trait, implementa la interface de ese trait, y hereda todo el código contenido en el trait. + +Para ver la utilidad de los traits, veamos un ejemplo clásico: objetos ordenados. Generalmente es útil tener la posibilidad de comparar objetos de una clase dada entre ellos, por ejemplo, para ordenarlos. En Java, los objetos que son comparables implementan la interfaz `Comparable`. En Scala, podemos hacer algo un poco mejor que en Java al definir un trait equivalente `Comparable` que invocará a `Ord`. + +Cuando comparamos objetos podemos utilizar seis predicados distintos: menor, menor o igual, igual, distinto, mayor o igual y mayor. De todas maneras, definir todos estos es fastidioso, especialmente que cuatro de estos pueden ser expresados en base a los otros dos. Esto es, dados los predicados "igual" y "menor" (por ejemplo), uno puede expresar los otros. En Scala, todas estas observaciones pueden ser fácilmente capturadas mediante la siguiente declaración de un Trait: + + trait Ord { + def < (that: Any): Boolean + def <=(that: Any): Boolean = (this < that) || (this == that) + def > (that: Any): Boolean = !(this <= that) + def >=(that: Any): Boolean = !(this < that) + } + +Esta definición crea un nuevo tipo llamado `Ord` el cual juega el mismo rol que la interfaz `Comparable`, como también provee implementaciones de tres predicados en términos de un cuarto, abstracto. Los predicados para igualidad y su inverso (distinto, no igual) no aparecen aquí ya que por defecto están presenten en todos los objetos. + +El tipo `Any` el cual es usado arriba es el supertipo de todos los otros tipos en Scala. Puede ser visto como una versión más general del tipo `Object` en Java, ya que `Any` también es supertipo de `Int`, `Float`, etc. cosa que no se cumple en Java (`int` por ejemplo es un tipo primitivo). + +Para hacer a un objeto de la clase comparable es suficiente definir los predicados que comprueban la igualdad y la inferioridad y mezclar la clase `Ord` de arriba. Como un ejemplo, definamos una clase `Fecha` que representa fechas en el calendario gregoriano. + + class Fecha(d: Int, m: Int, a: Int) extends Ord { + def anno = a + def mes = m + def dia = d + override def toString(): String = anno + "-" + mes + "-" + dia + +La parte importante aquí es la declaración `extends Ord` la cual sigue al nombre de la clase y los parámetros. Declara que la clase `Fecha` hereda del trait `Ord`. + +Después redefinimos el método `equals`, heredado de `Object`, para comparar correctamente fechas mediante sus campos individuales. La implementación por defecto de `equals` no es utilizable, porque como en Java, compara los objetos físicamente. Por lo tanto llegamos a esto: + + override def equals(that: Any): Boolean = + that.isInstanceOf[Fecha] && { + val o = that.asInstanceOf[Fecha] + o.dia== dia && o.mes == mes && o.anno== anno + } + +Este método utiliza el método predefinido `isInstanceOf` ("es instancia de") y `asInstanceOf` ("como instancia de"). El primero `isInstanceOf` se corresponde con el operador java `instanceOf` y retorna `true` si y solo si el objeto en el cual es aplicado es una instancia del tipo dado. El segundo, `asInstanceOf`, corresponde al operador de casteo en Java: si el objeto es una instancia de un tipo dado, esta es vista como tal, de otra manera se lanza una excepción `ClassCastException`. + +Finalmente el último método para definir es el predicado que comprueba la inferioridad. Este hace uso de otro método predefinido, `error` que lanza una excepción con el mensaje de error provisto. + + def <(that: Any): Boolean = { + if (!that.isInstanceOf[Fecha]) + error("no se puede comparar" + that + " y una fecha") + + val o = that.asInstanceOf[Fecha] + (anno < o.anno) || + (anno== o.anno && (mes < o.mes || + (mes == o.mes && dia < o.dia))) + } + +Esto completa la definición de la clase `Fecha`. Las instancias de esta clase pueden ser vistas tanto como fechas o como objetos comparables. Además, todas ellas definen los seis predicados de comparación mencionados arriba: `equals` y `<` porque aparecen directamente en la definición de la clase `Fecha` y los otros porque son heredados del trait `Ord`. + +Los traits son útiles en muchas otras más situaciones que las aquí mostrada, pero discutir sus aplicaciones está fuera del alcance de este documento. + +## Tipos Genéricos + +_Nota: El diseñador de los tipos genéricos en Java fue nada más ni nada menos que Martin Odersky, el diseñador de Scala._ + +La última característica de Scala que exploraremos en este tutorial es la de los tipos genéricos. Los programadores de Java deben estar bien al tanto de los problemas que genera la falta de genéricos en su lenguaje, lo cual es solucionado en Java 1.5. + +Los tipos genéricos proveen al programador la habilidad de escribir código parametrizado por tipos. Por ejemplo, escribir una librería para listas enlazadas se enfrenta al problema de decidir qué tipo darle a los elementos de la lista. Ya que esta lista está pensada para ser usada en diferentes contextos, no es posible decidir que el tipo de elementos sea, digamos, `Int`. Esto sería completamente arbitrario y muy restrictivo. + +Los programadores Java cuentan como último recurso con `Object`, que es el supertipo de todos los objetos. Esta solución de todas maneras está lejos de ser ideal, ya que no funciona con tipos primitivos (`int`, `long`, `float`, etc.) e implica que el programador tenga que realizar muchos casteos de tipos en su programa. + +Scala hace posible definir clases genéricas (y métodos) para resolver este problema. Examinemos esto con un ejemplo del contenedor más simple posible: una referencia, que puede estar tanto vacía como apuntar a un objeto de algún tipo. + + class Referencia[T] { + private var contenido: T = _ + def set(valor: T) { contenido = valor } + def get: T = contenido + } + +La clase `Referencia` es parametrizada por un tipo llamado `T`, que es el tipo de sus elementos. Este tipo es usado en el cuerpo de la clase como el tipo de la variable `contenido`, el argumento del método `set` y el tipo de retorno del método `get`. + +El ejemplo anterior introduce a las variables en Scala, que no deberían requerir mayor explicación. Es interesante notar que el valor inicial dado a la variable `contenido` es `_`, que representa un valor por defecto. Este valor por defecto es 0 para tipos numéricos, `false` para tipos `Boolean`, `()` para el tipo `Unit` y `null` para el resto de los objetos. + +Para utilizar esta clase `Referencia`, uno necesita especificar qué tipo utilizar por el parámetro `T`, es decir, el tipo del elemento contenido por la referencia. Por ejemplo, para crear y utilizar una referencia que contenga un entero, podríamos escribir lo siguiente: + + object ReferenciaEntero { + def main(args: Array[String]) { + val ref = new Referencia[Int] + ref.set(13) + println("La referncia tiene la mitad de " + (ref.get * 2)) + } + } + +Como puede verse en el ejemplo, no es necesario castear el valor retornado por el método `get` antes de usarlo como un entero. Tampoco es posible almacenar otra cosa que no sea un entero en esa referencia en particular, ya que fue declarada como contenedora de un entero. + +## Conclusión + +Scala es un lenguaje tremendamente poderoso que ha sabido heredar las mejores cosas de cada uno de los lenguajes más exitosos que se han conocido. Java no es la excepción, y comparte muchas cosas con este. La diferencia que vemos es que para cada uno de los conceptos de Java, Scala los aumenta, refina y mejora. Poder aprender todas las características de Scala nos equipa con más y mejores herramientas a la hora de escribir nuestros programas. +Si bien la programación funcional no ha sido una característica de Java, el programador experimentado puede notar la falta de soporte de este paradigma en múltiples ocasiones. El solo pensar en el código necesario para proveer a un `JButton` con el código que debe ejecutar al ser presionado nos muestra lo necesario que sería contar con herramientas funcionales. Recomendamos entonces tratar de ir incorporando estas características, por más que sea difícil para el programador Java al estar tan acostumbrado al paradigma imperativo de este lenguaje. + +Este documento dio una rápida introducción al lenguaje Scala y presento algunos ejemplos básicos. El lector interesado puede seguir, por ejemplo, leyendo el *Tutorial de Scala* que figura en el sitio de documentación, o *Scala by Example* (en inglés). También puede consultar la especificación del lenguaje cuando lo desee. diff --git a/_fr/cheatsheets/index.md b/_fr/cheatsheets/index.md new file mode 100644 index 0000000000..9cb47c6f8d --- /dev/null +++ b/_fr/cheatsheets/index.md @@ -0,0 +1,88 @@ +--- +layout: cheatsheet +title: Scalacheat + +partof: cheatsheet + +by: Brendan O'Connor +about: Grâce à Brendan O'Connor, ce memento vise à être un guide de référence rapide pour les constructions syntaxiques en Scala. Licencié par Brendan O'Connor sous licence CC-BY-SA 3.0. + +language: fr +--- + +###### Contribué par {{ page.by }} +{{ page.about }} + +| variables | | +| `var x = 5` | variable | +| Good `val x = 5`
Bad `x=6` | constante | +| `var x: Double = 5` | type explicite | +| fonctions | | +| Good `def f(x: Int) = { x*x }`
Bad `def f(x: Int) { x*x }` | définition d'une fonction
erreur cachée : sans le = c'est une procédure qui retourne un Unit ; occasionnant des problèmes incontrôlés. | +| Good `def f(x: Any) = println(x)`
Bad `def f(x) = println(x)` | définition d'une fonction
erreur de syntaxe : chaque argument a besoin d'être typé. | +| `type R = Double` | alias de type | +| `def f(x: R)` vs.
`def f(x: => R)` | appel par valeur
appel par nom (paramètres paresseux (lazy)) | +| `(x:R) => x*x` | fonction anonyme | +| `(1 to 5).map(_*2)` vs.
`(1 to 5).reduceLeft( _+_ )` | fonction anonyme : l'underscore est associé à la position du paramètre en argument. | +| `(1 to 5).map( x => x*x )` | fonction anonyme : pour utiliser un argument deux fois, il faut le nommer. | +| Good `(1 to 5).map(2*)`
Bad `(1 to 5).map(*2)` | fonction anonyme : méthode bornée et infixée. Pour votre santé, préférez la syntaxe `2*_`. | +| `(1 to 5).map { x => val y=x*2; println(y); y }` | fonction anonyme : la dernière expression d'un bloc est celle qui est retournée. | +| `(1 to 5) filter {_%2 == 0} map {_*2}` | fonctions anonymes : style "pipeline". (ou avec des parenthèses). | +| `def compose(g:R=>R, h:R=>R) = (x:R) => g(h(x))`
`val f = compose({_*2}, {_-1})` | fonctions anonymes : pour passer plusieurs blocs, il faut les entourer par des parenthèses. | +| `val zscore = (mean:R, sd:R) => (x:R) => (x-mean)/sd` | curryfication, syntaxe évidente. | +| `def zscore(mean:R, sd:R) = (x:R) => (x-mean)/sd` | curryfication, syntaxe évidente. | +| `def zscore(mean:R, sd:R)(x:R) = (x-mean)/sd` | curryfication, sucre syntaxique. mais alors : | +| `val normer = zscore(7, 0.4) _` | il faut ajouter l'underscore dans la fonction partielle, mais ceci uniquement pour la version avec le sucre syntaxique. | +| `def mapmake[T](g:T=>T)(seq: List[T]) = seq.map(g)` | type générique. | +| `5.+(3); 5 + 3`
`(1 to 5) map (_*2)` | sucre syntaxique pour opérateurs infixés. | +| `def sum(args: Int*) = args.reduceLeft(_+_)` | arguments variadiques. | +| paquetages | | +| `import scala.collection._` | import global. | +| `import scala.collection.Vector`
`import scala.collection.{Vector, Sequence}` | import sélectif. | +| `import scala.collection.{Vector => Vec28}` | renommage d'import. | +| `import java.util.{Date => _, _}` | importe tout de java.util excepté Date. | +| `package pkg` _en début de fichier_
`package pkg { ... }` | déclare un paquetage. | +| structures de données | | +| `(1,2,3)` | tuple littéral. (`Tuple3`) | +| `var (x,y,z) = (1,2,3)` | liaison déstructurée : le déballage du tuple se fait par le "pattern matching". | +| Bad`var x,y,z = (1,2,3)` | erreur cachée : chaque variable est associée au tuple au complet. | +| `var xs = List(1,2,3)` | liste (immuable). | +| `xs(2)` | indexe un élément par le biais des parenthèses. ([transparents](http://www.slideshare.net/Odersky/fosdem-2009-1013261/27)) | +| `1 :: List(2,3)` | créé une liste par le biais de l'opérateur "cons".| +| `1 to 5` _est équivalent à_ `1 until 6`
`1 to 10 by 2` | sucre syntaxique pour les plages de valeurs. | +| `()` _(parenthèses vides)_ | l'unique membre de type Unit (à l'instar de void en C/Java). | +| structures de constrôle | | +| `if (check) happy else sad` | test conditionnel. | +| `if (check) happy` _est équivalent à_
`if (check) happy else ()` | sucre syntaxique pour un test conditionnel. | +| `while (x < 5) { println(x); x += 1}` | boucle while. | +| `do { println(x); x += 1} while (x < 5)` | boucle do while. | +| `import scala.util.control.Breaks._`
`breakable {`
` for (x <- xs) {`
` if (Math.random < 0.1) break`
` }`
`}`| break. ([transparents](http://www.slideshare.net/Odersky/fosdem-2009-1013261/21)) | +| `for (x <- xs if x%2 == 0) yield x*10` _est équivalent à_
`xs.filter(_%2 == 0).map(_*10)` | *for comprehension*: filter/map | +| `for ((x,y) <- xs zip ys) yield x*y` _est équivalent à_
`(xs zip ys) map { case (x,y) => x*y }` | *for comprehension* : liaison déstructurée | +| `for (x <- xs; y <- ys) yield x*y` _est équivalent à_
`xs flatMap {x => ys map {y => x*y}}` | *for comprehension* : produit cartésien. | +| `for (x <- xs; y <- ys) {`
`println("%d/%d = %.1f".format(x, y, x/y.toFloat))`
`}` | *for comprehension* : à la manière impérative
[sprintf-style](http://java.sun.com/javase/6/docs/api/java/util/Formatter.html#syntax) | +| `for (i <- 1 to 5) {`
`println(i)`
`}` | *for comprehension* : itère jusqu'à la borne supérieure comprise. | +| `for (i <- 1 until 5) {`
`println(i)`
`}` | *for comprehension* : itère jusqu'à la borne supérieure non comprise. | +| pattern matching | | +| Good `(xs zip ys) map { case (x,y) => x*y }`
Bad `(xs zip ys) map( (x,y) => x*y )` | cas d’utilisation d’une fonction utilisée avec un "pattern matching". | +| Bad
`val v42 = 42`
`Some(3) match {`
` case Some(v42) => println("42")`
` case _ => println("Not 42")`
`}` | "v42" est interprété comme un nom ayant n’importe quelle valeur de type Int, donc "42" est affiché. | +| Good
`val v42 = 42`
`Some(3) match {`
`` case Some(`v42`) => println("42")``
`case _ => println("Not 42")`
`}` | "\`v42\`" x les "backticks" est interprété avec la valeur de val `v42`, et "Not 42" est affiché. | +| Good
`val UppercaseVal = 42`
`Some(3) match {`
` case Some(UppercaseVal) => println("42")`
` case _ => println("Not 42")`
`}` | `UppercaseVal`i est traité avec la valeur contenue dans val, plutôt qu’un nouvelle variable du "pattern", parce que cela commence par une lettre en capitale. Ainsi, la valeur contenue dans `UppercaseVal` est comparée avec `3`, et "Not 42" est affiché. | +| l'orienté objet | | +| `class C(x: R)` _est équivalent à_
`class C(private val x: R)`
`var c = new C(4)` | paramètres du constructeur - privé | +| `class C(val x: R)`
`var c = new C(4)`
`c.x` | paramètres du constructeur - public | +| `class C(var x: R) {`
`assert(x > 0, "positive please")`
`var y = x`
`val readonly = 5`
`private var secret = 1`
`def this = this(42)`
`}`|
le constructeur est dans le corps de la classe
déclare un membre public
déclare un accesseur
déclare un membre privé
constructeur alternatif | +| `new{ ... }` | classe anonyme | +| `abstract class D { ... }` | définition d’une classe abstraite. (qui n’est pas instanciable). | +| `class C extends D { ... }` | définition d’une classe qui hérite d’une autre. | +| `class D(var x: R)`
`class C(x: R) extends D(x)` | héritage et constructeurs paramétrés. (souhaits : pouvoir passer les paramètres automatiquement par défaut). +| `object O extends D { ... }` | définition d’un singleton. (à la manière d'un module) | +| `trait T { ... }`
`class C extends T { ... }`
`class C extends D with T { ... }` | traits.
interfaces avec implémentation. constructeur sans paramètre. [mixin-able]({{ site.baseurl }}/tutorials/tour/mixin-class-composition.html). +| `trait T1; trait T2`
`class C extends T1 with T2`
`class C extends D with T1 with T2` | multiple traits. | +| `class C extends D { override def f = ...}` | doit déclarer une méthode surchargée. | +| `new java.io.File("f")` | créé un objet. | +| Bad `new List[Int]`
Good `List(1,2,3)` | erreur de typage : type abstrait
: au contraire, par convention : la fabrique appelée masque le typage.| +| `classOf[String]` | classe littérale. | +| `x.isInstanceOf[String]` | vérification de types (à l’exécution) | +| `x.asInstanceOf[String]` | "casting" de type (à l’exécution) | +| `x: String` | attribution d’un type (à la compilation) | diff --git a/_includes/allsids.txt b/_includes/allsids.txt deleted file mode 100644 index dd33e8b365..0000000000 --- a/_includes/allsids.txt +++ /dev/null @@ -1,28 +0,0 @@ -

Pending SIPs

-
    - {% for post in site.categories.pending %} -
  • {{ post.title }} ( {{ post.date | date: "%b %Y" }} ) - {% if post.vote-status %} - {% if post.vote-status == 'accepted' %} - Accepted - {% elsif post.vote-status == 'deferred' %} - Deferred - {% elsif post.vote-status == 'postponed' %} - Postponed - {% elsif post.vote-status == 'under review' %} - Under Review - {% elsif post.vote-status == 'under revision' %} - Under Revision - {% elsif post.vote-status == 'numbered' %} - Numbered - {% elsif post.vote-status == 'dormant' %} - Dormant - {% elsif post.vote-status == 'not accepted' %} - Not Accepted - {% elsif post.vote-status == 'rejected' %} - Not Accepted - {% endif %} - {% endif %} -
  • - {% endfor %} -
diff --git a/_includes/blog-list.html b/_includes/blog-list.html new file mode 100644 index 0000000000..a82eeccc4e --- /dev/null +++ b/_includes/blog-list.html @@ -0,0 +1,75 @@ +{% comment %}Use the include variable 'category' to select the category to show (included in blog-categories.yml), or assign it to "all" if you want to show all posts.{% endcomment %} + +
+
+
+
+ +
+ {% for post in paginator.posts %} +
+

{{post.title}}

+

{{post.date | date: "%A %-d %B %Y"}}

+ {% if post.by %}

{{post.by}}

{% endif %} + {% if post.tags %} +
    + {% for tag in post.tags %} +
  • {{tag}}
  • + {% endfor %} +
+ {% endif %} +
+ {% endfor %} +
+
+ {% for category in site.data.blog-categories %} + {% if category.categoryId == include.category %} + {% assign currentCategoryPath = category.url %} + {% endif %} + {% endfor %} + + {% capture urlPath %}{% if include.category == "all" %}blog{% else %}{{currentCategoryPath}}{% endif %}{% endcapture %} + {% assign urlPath = urlPath | split: '/' | join: '/' | remove_first: '/' %} + {% include paginator.html urlPath=urlPath %} +
+ {% assign highlights = "" | split: "," %} + {% for post in site.posts %} + {% if post.isHighlight == true %} + {% assign highlights = highlights | push: post %} + {% endif %} + {% endfor %} + + {% for post in highlights %} + {% if forloop.first %} +
+
+
Highlights
+
+ {% endif %} +
+

{{post.title}}

+ {% if post.by %}

{{post.by}}

{% endif %} + {% if post.tags %} + {% for tag in post.tags %} +
    +
  • {{tag}}
  • +
+ {% endfor %} + {% endif %} +
+ {% if forloop.last %} +
+
+
+ {% endif %} + {% endfor %} + +
+
\ No newline at end of file diff --git a/_includes/books.html b/_includes/books.html new file mode 100644 index 0000000000..bbf2448be6 --- /dev/null +++ b/_includes/books.html @@ -0,0 +1,21 @@ + +
+ {% for book in site.books %} +
+ +
+

{{book.status}}

+

{{site.data.common.texts.booksBy}} {{book.authors | join: ", "}}

+

{{site.data.common.texts.booksPublishedBy}} {{book.publisher}}

+

{{book.content}}

+
+
+ {% endfor %} +
diff --git a/_includes/cheatsheet-header.txt b/_includes/cheatsheet-header.txt index 32965d410c..c4c9c3cc5b 100644 --- a/_includes/cheatsheet-header.txt +++ b/_includes/cheatsheet-header.txt @@ -2,33 +2,22 @@ - {% if page.title %}{{ page.title }} - {% endif %}{{ site.title }} - {% if page.description %} - - {% endif %} - - - - - - + + - + - - - + + + - + - - - - + diff --git a/_includes/contributing-toc.txt b/_includes/contributing-toc.txt deleted file mode 100644 index 471940e7ec..0000000000 --- a/_includes/contributing-toc.txt +++ /dev/null @@ -1,7 +0,0 @@ -
-
-

Contents

-
- {% include thanks-to.txt %} -
-
diff --git a/_includes/contributions-projects-list.html b/_includes/contributions-projects-list.html new file mode 100644 index 0000000000..5a4c9bdbe7 --- /dev/null +++ b/_includes/contributions-projects-list.html @@ -0,0 +1,36 @@ +{% comment %} + Layouts using this include should pass an include variable called 'collection' referencing a collection carrying the data +{% endcomment %} +
+ {% for item in include.collection %} +
+ +
+

{{item.description}}

+
    + {% if item.homeLink %} +
  • Home
  • + {% if item.contributingLink or item.readmeLink or item.issuesLink %}
  • {% endif %} + {% endif %} + {% if item.issuesLink %} +
  • Issues
  • + {% if item.contributingLink or item.readmeLink %}
  • {% endif %} + {% endif %} + {% if item.readmeLink %} +
  • ReadMe
  • + {% if item.contributingLink %}
  • {% endif %} + {% endif %} + {% if item.contributingLink %} +
  • Contributing
  • + {% endif %} +
+
+
+ {% endfor %} + +
\ No newline at end of file diff --git a/_includes/coursera-stats-js.txt b/_includes/coursera-stats-js.txt deleted file mode 100644 index a65c30925b..0000000000 --- a/_includes/coursera-stats-js.txt +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/_includes/download-resource-list.html b/_includes/download-resource-list.html new file mode 100644 index 0000000000..017af455af --- /dev/null +++ b/_includes/download-resource-list.html @@ -0,0 +1,31 @@ +

Other resources

+ +

You can find the installer download links for other operating systems, as well as documentation and source code archives for Scala {{page.release_version}} below.

+ +
+ + + + + {% unless page.dont_show_sizes %} + + {% endunless %} + + + +{% for resource in page.resources %} + + + + {% unless page.dont_show_sizes %} + + {% endunless %} + +{% endfor %} + +
ArchiveSystemSize
+ + {{ resource[1] }} + + {{ resource[3] }}{{ resource[4] }}
+
diff --git a/_includes/downloads-list.html b/_includes/downloads-list.html new file mode 100644 index 0000000000..f8a7554901 --- /dev/null +++ b/_includes/downloads-list.html @@ -0,0 +1,19 @@ +{% for top in (0..3) reversed %} + {% for major in (0..20) reversed %} + {% assign possibleVersionShort = top | append:'.' | append:major %} + {% assign sz = possibleVersionShort | size %} + {% if 3 == sz %} + {% assign possibleVersion = possibleVersionShort | append:'.' %} + {% else %} + {% assign possibleVersion = possibleVersionShort %} + {% endif %} + {% for page in site.downloads %} + {% assign releaseVersion = page.release_version | truncate:4, '' %} + {% if releaseVersion == possibleVersion %} + + {% endif %} + {% endfor %} + {% endfor %} +{% endfor %} \ No newline at end of file diff --git a/_includes/events-training-list-bottom.html b/_includes/events-training-list-bottom.html new file mode 100644 index 0000000000..f863e64506 --- /dev/null +++ b/_includes/events-training-list-bottom.html @@ -0,0 +1,12 @@ + + + {% if paginator.total_pages > 1 %} +
    + {% for page in (1..paginator.total_pages) %} +
  • + {{page}} +
  • + {% endfor %} +
+ {% endif %} + \ No newline at end of file diff --git a/_includes/events-training-list-top.html b/_includes/events-training-list-top.html new file mode 100644 index 0000000000..e3a7d0004c --- /dev/null +++ b/_includes/events-training-list-top.html @@ -0,0 +1,73 @@ +{% capture currentYear %}{{site.time | date: '%Y' | plus: 0}}{% endcapture %} + +
+
+
+ {% comment %}Because of Jekyll limitations, we need to pass the paginated collection to iterate in an include variable 'collection'{% endcomment %} + + {% capture firstMonth %}{{include.collection.first.date | date: "%m"}}{% endcapture %} + {% assign firstMonthNum = firstMonth | plus: 0 %} + {% capture lastMonth %}{{include.collection.last.date | date: "%m"}}{% endcapture %} + {% assign lastMonthNum = lastMonth | plus: 0 %} + + {% for m in (firstMonth..lastMonth) %} + {% assign currentMonthEvents = '' | split: ','' %} + + {% for event in include.collection %} + {% capture month %}{{event.date | date: "%m"}}{% endcapture %} + {% assign monthNum = month | plus: 0 %} + {% if monthNum == m %} + {% assign currentMonthEvents = currentMonthEvents | push: event %} + {% endif %} + {% endfor %} + + {% capture monthName %} + {% case m %} + {% when 1 %}January + {% when 2 %}February + {% when 3 %}March + {% when 4 %}April + {% when 5 %}May + {% when 6 %}June + {% when 7 %}July + {% when 8 %}August + {% when 9 %}September + {% when 10 %}October + {% when 11 %}November + {% when 12 %}December + {% endcase %} + {% endcapture %} + + {% for event in currentMonthEvents %} + {% capture year %}{{event.date | date: "%Y"}}{% endcapture %} + {% capture day %}{{event.date | date: "%d"}}{% endcapture %} + {% if forloop.first %} +

{{monthName}} {{year}}

+ + {% endif %} + {% endfor %} + {% endfor %} \ No newline at end of file diff --git a/_includes/footer.html b/_includes/footer.html new file mode 100644 index 0000000000..3762a98d4e --- /dev/null +++ b/_includes/footer.html @@ -0,0 +1,97 @@ +
+
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +{% if page.layout == "sips"%} + +{% endif %} + + + diff --git a/_includes/footer.txt b/_includes/footer.txt deleted file mode 100644 index 65dfc817bc..0000000000 --- a/_includes/footer.txt +++ /dev/null @@ -1,15 +0,0 @@ -{% include footerbar.txt %} - - - - - - - - diff --git a/_includes/footerbar.txt b/_includes/footerbar.txt deleted file mode 100644 index 881aad4375..0000000000 --- a/_includes/footerbar.txt +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - diff --git a/_includes/frontpage-content.txt b/_includes/frontpage-content.txt deleted file mode 100644 index 4ffab9b891..0000000000 --- a/_includes/frontpage-content.txt +++ /dev/null @@ -1,70 +0,0 @@ -
- -

Community-driven documentation for Scala.

- -
    -
  • - Thumbnail -

    Overviews and Guides

    -

    Collections, Actors, Swing, and more.

    -

    Go there

    -
  • -
  • - Thumbnail -

    Tutorials

    -

    Coming from Java? Python? Ruby? Tutorials which help the transition from language XYZ to Scala.

    -

    Go there

    -
  • -
  • - Thumbnail -

    Glossary

    -

    Lost on some terminology? Check the glossary, direct from the book, Programming in Scala.

    -

    Go there

    -
  • -
-
- -
-
-
- -
- -

Available Documentation

- -

Scala Improvement Process Available

-

Read language improvement proposals, participate in discussions surrounding submitted proposals, or submit your own improvement proposal.

- -

Guides and Overviews Some Available

-

Some guides, such as Martin Odersky’s Collections Overview are now available.

- -

Tutorials Some Available

-

Some tutorials, such as the Scala for Java Programmers guide, are now available.

- -

Glossary Available

-

With permission from Artima Inc., we reproduce the glossary from Programming in Scala here, for easy reference.

- -

Cheatsheets Available

-

We've currently got one cheatsheet, thanks to Brendan O’Connor. Contributions in this area are welcome.

- -

Scala Style Guide Available

-

Thanks to Daniel Spiewak and David Copeland for putting together such an excellent style guide, and thanks to Simon Ochsenreither for converting it to Markdown.

- -

Language Specification Available

-

The official definition of Scala. For when you just have to know the truth.

- -

 

 

- -
-
-

Contributions Welcomed!

- This site was designed for core committers and the community alike to build documentation. We’d love help of any kind – from detailed articles or overviews of Scala’s language features, to help converting documents to Markdown. -

 

-

If you’d like to help, please see the Contribute section of the site, and feel free to contact Heather.

- - -
- -
-
-
diff --git a/_includes/frontpage-footer.txt b/_includes/frontpage-footer.txt deleted file mode 100644 index 5dd888b0d8..0000000000 --- a/_includes/frontpage-footer.txt +++ /dev/null @@ -1,6 +0,0 @@ - - {% include footerbar.txt %} - - - - diff --git a/_includes/frontpage-header.txt b/_includes/frontpage-header.txt deleted file mode 100644 index e806c92910..0000000000 --- a/_includes/frontpage-header.txt +++ /dev/null @@ -1,113 +0,0 @@ - - - - - {% if page.title %}{{ page.title }} - {% endif %}{{ site.title }} - {% if page.description %} - - {% endif %} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/_includes/gen-toc.txt b/_includes/gen-toc.txt deleted file mode 100644 index c5124cfd18..0000000000 --- a/_includes/gen-toc.txt +++ /dev/null @@ -1,6 +0,0 @@ -
-
-

Contents

-
-
-
diff --git a/_includes/glossary-header.html b/_includes/glossary-header.html new file mode 100644 index 0000000000..61ad5b35e9 --- /dev/null +++ b/_includes/glossary-header.html @@ -0,0 +1,340 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/_includes/glossary-header.txt b/_includes/glossary-header.txt deleted file mode 100644 index 12ad34335e..0000000000 --- a/_includes/glossary-header.txt +++ /dev/null @@ -1,349 +0,0 @@ - - - - - {% if page.title %}{{ page.title }} - {% endif %}{{ site.title }} - {% if page.description %} - - {% endif %} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/_includes/glossary-sidebar.txt b/_includes/glossary-sidebar.txt deleted file mode 100644 index e047cce8c2..0000000000 --- a/_includes/glossary-sidebar.txt +++ /dev/null @@ -1,6 +0,0 @@ -
-
-

Terms

-
-
-
diff --git a/_includes/header-coursera.txt b/_includes/header-coursera.txt deleted file mode 100644 index 49a664246c..0000000000 --- a/_includes/header-coursera.txt +++ /dev/null @@ -1,180 +0,0 @@ - - - - - {% if page.partof %}{{ page.partof | replace: '-',' ' | split:" " | capitalize | join:" " }} - {% endif %}{% if page.title %}{{ page.title }} - {% endif %}{{ site.title }} - {% if page.description %} - - {% endif %} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/_includes/header.txt b/_includes/header.txt deleted file mode 100644 index ff4295a4b0..0000000000 --- a/_includes/header.txt +++ /dev/null @@ -1,159 +0,0 @@ - - - - {% if page.partof %}{% assign words = page.partof | split: '-' %}{% for word in words %}{{ word | capitalize }} {% endfor %}- {% endif %}{% if page.title %}{{ page.title }} - {% endif %}{{ site.title }} - {% if page.description %} - - {% endif %} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/_includes/headerbottom.html b/_includes/headerbottom.html new file mode 100644 index 0000000000..d9e11dedea --- /dev/null +++ b/_includes/headerbottom.html @@ -0,0 +1,3 @@ + + + diff --git a/_includes/headertop.html b/_includes/headertop.html new file mode 100644 index 0000000000..7ef0bcea80 --- /dev/null +++ b/_includes/headertop.html @@ -0,0 +1,30 @@ + + + + {% if page.title %}{{ page.title }} | {% endif %}{{ site.title }} + {% if page.description %} + + {% endif %} + + + + + + + + + + + + + + + + + + + + + + diff --git a/_includes/index-header.txt b/_includes/index-header.txt deleted file mode 100644 index 4a2043a74e..0000000000 --- a/_includes/index-header.txt +++ /dev/null @@ -1,264 +0,0 @@ - - - - - {% if page.title %}{{ page.title }} - {% endif %}{{ site.title }} - {% if page.description %} - - {% endif %} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/_includes/inner-page-blog-detail-main-content.html b/_includes/inner-page-blog-detail-main-content.html new file mode 100644 index 0000000000..bf616325b4 --- /dev/null +++ b/_includes/inner-page-blog-detail-main-content.html @@ -0,0 +1,27 @@ +
+
+
+
+
+
+

{{page.date | date: "%A %-d %B %Y"}}

+

{{page.by}}

+
+ {% if page.tags %} +
    + {% for tag in page.tags %} +
  • {{tag}}
  • + {% endfor %} +
+ {% endif %} +
+
{{page.category | upcase}}
+

{{page.title}}

+ {{content}} +
+
+ + {% include sidebar-toc.html %} +
+
+
\ No newline at end of file diff --git a/_includes/inner-page-main-content.html b/_includes/inner-page-main-content.html new file mode 100644 index 0000000000..9587a304cb --- /dev/null +++ b/_includes/inner-page-main-content.html @@ -0,0 +1,12 @@ +
+
+
+
+ {{content}} +
+
+ + + {% include sidebar-toc.html %} +
+
diff --git a/_includes/localized-overview-index.txt b/_includes/localized-overview-index.txt deleted file mode 100644 index c63454870b..0000000000 --- a/_includes/localized-overview-index.txt +++ /dev/null @@ -1,34 +0,0 @@ -{% for enpost in site.categories.core %} -{% for post in site.pages %} -{% if post.overview and post.overview == enpost.overview and post.language == page.language %} - -{% if post.partof %} -* {{ post.title }} {{ post.label-text }} - {% for pg in site.pages %} - {% if pg.partof == post.partof and pg.outof and pg.language == page.language %} - {% assign totalPages = pg.outof %} - {% endif %} - {% endfor %} - - {% if totalPages %} -
    - {% for i in (1..totalPages) %} - {% for pg in site.pages %} - {% if pg.partof == post.partof and pg.num and pg.num == i and pg.language == page.language %} -
  • {{ pg.title }}
  • - {% endif %} - {% endfor %} - {% endfor %} -
- {% else %} **ERROR**. Couldn't find the total number of pages in this set of tutorial articles. Have you declared the `outof` tag in your YAML front matter? - {% endif %} -{% else %} - {% if post.hidden == true %} - {% else %} -* [{{ post.title }}]({{ site.baseurl }}{{ post.url }}) {{ post.label-text }} - {% endif %} -{% endif %} - -{% endif %} -{% endfor %} -{% endfor %} diff --git a/_includes/masthead-community.html b/_includes/masthead-community.html new file mode 100644 index 0000000000..1d5f4912a7 --- /dev/null +++ b/_includes/masthead-community.html @@ -0,0 +1,37 @@ +
+
+
+
+
+

Discourse

+ Mailing list +
    + {% for forum in site.data.chats-forums.discourseForums %} +
  • + {{forum.title}} +
    +

    {{forum.title}}

    +

    {{forum.subtitle}}

    +
    +
  • + {% endfor %} +
+
+
+

Gitter

+ Real-time (topic-specialized) chat + +
+
+
+
+
\ No newline at end of file diff --git a/_includes/masthead-documentation.html b/_includes/masthead-documentation.html new file mode 100644 index 0000000000..0a3c1c9403 --- /dev/null +++ b/_includes/masthead-documentation.html @@ -0,0 +1,34 @@ +
+
+
+
+ {% for section in page.sections %} +
+

{{ section.title }}

+ {% for link in section.links %} + +
+ +
{{link.title}}
+
+
+

{{link.description}}

+
+
+ {% endfor %} +
+
  + {% if section.more-resources %} + More Resources: + + {% endif %} +
+ {% endfor %} +
+
+
+
diff --git a/_includes/navbar-inner.html b/_includes/navbar-inner.html new file mode 100644 index 0000000000..4889a32540 --- /dev/null +++ b/_includes/navbar-inner.html @@ -0,0 +1,67 @@ + +
+
+ + +
+
diff --git a/_includes/online-courses.html b/_includes/online-courses.html new file mode 100644 index 0000000000..73fba6a50c --- /dev/null +++ b/_includes/online-courses.html @@ -0,0 +1,91 @@ + +
+
+

Online Courses

+
+ + {% comment %} + We're going to follow the next ordering for the online courses: + 1- First we'll show those items that belong to an specific specialization (i.e.: Scala's progfun in Coursera). Those will be ordered alphabetically by title and each item under the specialization by the `specialization-order` tag in each one. + 2- After those, courses that don't belong to any specific specialization. + We'll only show those courses that are not finished yet. + {% endcomment %} + + {% assign specializations = '' | split: ',' %} + {% assign courses = '' | split: ',' %} + {% assign upcomingCourses = '' | split: ',' %} + {% capture now %}{{site.time | date: '%s' | plus: 0}}{% endcapture %} + + {% for course in site.online_courses %} + {% unless specializations contains course.specialization %} + {% assign specializations = specializations | push: course.specialization %} + {% endunless %} + + {% capture endDate %}{{course.end-date | date: '%s' | plus: 86400}}{% endcapture %} + {% if now <= endDate %} + {% assign upcomingCourses = upcomingCourses | push: course %} + {% endif %} + {% endfor %} + + {% for specialization in specializations %} + {% assign specCourses = '' | split: ',' %} + + {% for course in upcomingCourses %} + {% if course.specialization %} + {% if course.specialization == specialization %} + {% assign specCourses = specCourses | push: course %} + {% endif %} + + {% assign sortedSpecCourses = specCourses | sort: 'specialization-order' %} + {% endif %} + {% endfor %} + {% for sortedCourse in sortedSpecCourses %} + {% assign courses = courses | push: sortedCourse %} + {% endfor %} + {% endfor %} + + {% for course in upcomingCourses %} + {% unless course.specialization %} + {% assign courses = courses | push: course %} + {% endunless %} + {% endfor %} + + +
+

Visit all the Online Courses courses

+
+
\ No newline at end of file diff --git a/_includes/pager.txt b/_includes/pager.txt index 417efc705a..b4c4be59c2 100644 --- a/_includes/pager.txt +++ b/_includes/pager.txt @@ -1,10 +1,10 @@ diff --git a/_includes/paginator.html b/_includes/paginator.html new file mode 100644 index 0000000000..7212e643f4 --- /dev/null +++ b/_includes/paginator.html @@ -0,0 +1,9 @@ +{% if paginator.total_pages > 1 %} +
    + {% for page in (1..paginator.total_pages) %} +
  • + {{page}} +
  • + {% endfor %} +
+{% endif %} \ No newline at end of file diff --git a/_includes/scastie.html b/_includes/scastie.html new file mode 100644 index 0000000000..fb7a6ec65a --- /dev/null +++ b/_includes/scastie.html @@ -0,0 +1,23 @@ +
+
+
+

Run Scala in your browser

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer commodo neque eget placerat dapibus. Mauris ullamcorper dui eu pellentesque venenatis. Nam non elit vitae dolor posuere eleifend a facilisis diam

+
+
+ +
+
+
+
+ +
+
+ + Run Scala code interactively +
+
+
+
\ No newline at end of file diff --git a/_includes/search-header.txt b/_includes/search-header.txt deleted file mode 100644 index a013e1d165..0000000000 --- a/_includes/search-header.txt +++ /dev/null @@ -1,267 +0,0 @@ - - - - - {% if page.title %}{{ page.title }} - {% endif %}{{ site.title }} - {% if page.description %} - - {% endif %} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/_includes/sidebar-toc-glossary.html b/_includes/sidebar-toc-glossary.html new file mode 100644 index 0000000000..03159bdb6f --- /dev/null +++ b/_includes/sidebar-toc-glossary.html @@ -0,0 +1,10 @@ + diff --git a/_includes/sidebar-toc-multipage-overview.html b/_includes/sidebar-toc-multipage-overview.html new file mode 100644 index 0000000000..cfd6ee71a6 --- /dev/null +++ b/_includes/sidebar-toc-multipage-overview.html @@ -0,0 +1,51 @@ +
+ +
diff --git a/_includes/sidebar-toc-singlepage-overview.html b/_includes/sidebar-toc-singlepage-overview.html new file mode 100644 index 0000000000..bc417951a7 --- /dev/null +++ b/_includes/sidebar-toc-singlepage-overview.html @@ -0,0 +1,31 @@ +
+ +
diff --git a/_includes/sidebar-toc-style.html b/_includes/sidebar-toc-style.html new file mode 100644 index 0000000000..d0714f721e --- /dev/null +++ b/_includes/sidebar-toc-style.html @@ -0,0 +1,59 @@ +{% if page.includeTOC or layout.includeTOC %} + {% if page.includeTOC != false %} + + + {% for pg in site.posts %} + {% if pg.overview == page.overview and pg.languages %} + {% assign languages = pg.languages %} + {% endif %} + {% endfor %} + + {% for pg in site.pages %} + {% if pg.overview == page.overview and pg.languages %} + {% assign languages = pg.languages %} + {% endif %} + {% endfor %} + + {% if page.language %} + {% capture intermediate %}{{ page.url | remove_first: page.language }}{% endcapture %} + {% capture rootTutorialURL %}{{ intermediate | remove_first: '/' }}{% endcapture %} + {% else %} + {% assign rootTutorialURL = page.url %} + {% endif %} + +
+ +
+ {% endif %} +{% endif %} diff --git a/_includes/sidebar-toc-tour-overview.html b/_includes/sidebar-toc-tour-overview.html new file mode 100644 index 0000000000..28a1a1636a --- /dev/null +++ b/_includes/sidebar-toc-tour-overview.html @@ -0,0 +1,49 @@ +
+ +
diff --git a/_includes/sidebar-toc.html b/_includes/sidebar-toc.html new file mode 100644 index 0000000000..d0714f721e --- /dev/null +++ b/_includes/sidebar-toc.html @@ -0,0 +1,59 @@ +{% if page.includeTOC or layout.includeTOC %} + {% if page.includeTOC != false %} + + + {% for pg in site.posts %} + {% if pg.overview == page.overview and pg.languages %} + {% assign languages = pg.languages %} + {% endif %} + {% endfor %} + + {% for pg in site.pages %} + {% if pg.overview == page.overview and pg.languages %} + {% assign languages = pg.languages %} + {% endif %} + {% endfor %} + + {% if page.language %} + {% capture intermediate %}{{ page.url | remove_first: page.language }}{% endcapture %} + {% capture rootTutorialURL %}{{ intermediate | remove_first: '/' }}{% endcapture %} + {% else %} + {% assign rootTutorialURL = page.url %} + {% endif %} + +
+ +
+ {% endif %} +{% endif %} diff --git a/_includes/sips-topbar.txt b/_includes/sips-topbar.txt deleted file mode 100644 index 65400087e2..0000000000 --- a/_includes/sips-topbar.txt +++ /dev/null @@ -1,17 +0,0 @@ - - diff --git a/_includes/thanks-to.txt b/_includes/thanks-to.txt deleted file mode 100644 index 1c375077b8..0000000000 --- a/_includes/thanks-to.txt +++ /dev/null @@ -1,14 +0,0 @@ -
-

Thank you

It helps many

-
- -This site and the documentation it contains is the result of a tremendous amount of work by a large number of people over time, from the first Scala team members, to today’s newcomers. In an effort to only scratch the surface, we list some of those whose help was invaluable in the realization of this iteration of the Scala Documentation repository. - -
    -
  • Josh Suereth
  • -
  • Dave Copeland
  • -
  • Daniel Spiewak
  • -
  • Simon Ochsenreither
  • -
  • Brendan O’Connor
  • -
  • Yuvi Masory
  • -
      diff --git a/_includes/toc-large.txt b/_includes/toc-large.txt deleted file mode 100644 index d5d9dfc6a3..0000000000 --- a/_includes/toc-large.txt +++ /dev/null @@ -1,28 +0,0 @@ -
      -
      -

      Contents

      - -{% for pg in site.pages %} - {% if pg.partof == page.partof and pg.outof %} - {% assign totalPages = pg.outof %} - {% endif %} -{% endfor %} - -{% if totalPages %} -
        - {% for i in (1..totalPages) %} - {% for pg in site.pages %} - {% if pg.partof == page.partof and pg.num and pg.num == i and page.language == pg.language %} -
      • {{ pg.title }}
      • - {% endif %} - {% if pg.partof == page.partof and pg.num and page.num and page.num == pg.num and pg.num == i and page.language == pg.language %} -
        - {% endif %} - {% endfor %} - {% endfor %} -
      -{% else %} **ERROR**. Couldn't find the total number of pages in this set of tutorial articles. Have you declared the `outof` tag in your YAML front matter? -{% endif %} - -
      -
      \ No newline at end of file diff --git a/_includes/toc.txt b/_includes/toc.txt deleted file mode 100644 index 9a0e4d914c..0000000000 --- a/_includes/toc.txt +++ /dev/null @@ -1,57 +0,0 @@ -
      -
      - {% if page.vote-status %} - {% if page.vote-status == 'accepted' %} -

      SIP Committee Decision

      -
      - Accepted -

      {{ page.vote-text }}

      -
      - {% elsif page.vote-status == 'deferred' %} -

      SIP Committee Decision

      -
      - Decision Deferred Until Next SIP Committee Meeting -

      {{ page.vote-text }}

      -
      - {% elsif page.vote-status == 'postponed' %} -

      SIP Committee Decision

      -
      - Postponed To A Future Release -

      {{ page.vote-text }}

      -
      - {% elsif page.vote-status == 'numbered' %} -

      SIP Committee Decision

      -
      - Numbered -

      {{ page.vote-text }}

      -
      - {% elsif page.vote-status == 'under review' %} -

      SIP Committee Decision

      -
      - Under Review -

      {{ page.vote-text }}

      -
      - {% elsif page.vote-status == 'under revision' %} -

      SIP Committee Decision

      -
      - Under Revision -

      {{ page.vote-text }}

      -
      - {% elsif page.vote-status == 'dormant' %} -

      SIP Committee Decision

      -
      - Dormant -

      {{ page.vote-text }}

      -
      - {% elsif page.vote-status == 'rejected' %} -

      SIP Committee Decision

      -
      - Not Accepted -

      {{ page.vote-text }}

      -
      - {% endif %} - {% endif %} -

      Contents

      -
      -
      -
      diff --git a/_includes/topbar.txt b/_includes/topbar.txt deleted file mode 100644 index 7ea77533a9..0000000000 --- a/_includes/topbar.txt +++ /dev/null @@ -1,47 +0,0 @@ - -
      -
      -
      - Documentation - -
      -
      -
      diff --git a/_includes/tutorial-list.html b/_includes/tutorial-list.html new file mode 100644 index 0000000000..da4ca58923 --- /dev/null +++ b/_includes/tutorial-list.html @@ -0,0 +1,7 @@ +{% comment %}Use include variable 'column' to describe which column list to draw (0 or 1){% endcomment %} +{% for tutorial in site.data.tutorials limit: 6 %} + {% assign loopindex = forloop.index | modulo: 2 %} + {% if loopindex == include.column %} +
    • {{tutorial.title | truncate: 60}}
    • + {% endif %} +{% endfor %} \ No newline at end of file diff --git a/_includes/tutorial-toc.html b/_includes/tutorial-toc.html new file mode 100644 index 0000000000..b1deacf0eb --- /dev/null +++ b/_includes/tutorial-toc.html @@ -0,0 +1,29 @@ + + {% for pg in site.categories.tour %} + {% if pg.languages %} + {% assign languages = pg.languages %} + {% endif %} + {% endfor %} + + {% if page.language %} + {% capture intermediate %}{{ page.url | remove_first: page.language }}{% endcapture %} + {% capture rootTutorialURL %}{{ intermediate | remove_first: '/' }}{% endcapture %} + {% else %} + {% assign rootTutorialURL = page.url %} + {% endif %} + +
      + +
      diff --git a/_includes/tutorial-toc.txt b/_includes/tutorial-toc.txt deleted file mode 100644 index a72f610920..0000000000 --- a/_includes/tutorial-toc.txt +++ /dev/null @@ -1,6 +0,0 @@ -
      -
      -

      Contents

      - {% include tutorial-tour-list.txt %} -
      -
      diff --git a/_includes/twitter-feed.html b/_includes/twitter-feed.html new file mode 100644 index 0000000000..44629abb5a --- /dev/null +++ b/_includes/twitter-feed.html @@ -0,0 +1,15 @@ + \ No newline at end of file diff --git a/_includes/upcoming-training.html b/_includes/upcoming-training.html new file mode 100644 index 0000000000..7b205eb794 --- /dev/null +++ b/_includes/upcoming-training.html @@ -0,0 +1,38 @@ + +
      +
      +

      Upcoming Training

      +
      +
      + {% assign upcomingTrainings = '' | split: ',' %} + {% capture now %}{{site.time | date: '%s' | plus: 0}}{% endcapture %} + {% for training in site.trainings %} + {% capture date %}{{training.when | date: '%s' | plus: 86400}}{% endcapture %} + {% if now <= date %} + {% assign upcomingTrainings = upcomingTrainings | push: training %} + {% endif %} + {% endfor %} + {% for training in upcomingTrainings %} + + + + {% capture date %}{{training.date | date: '%B' }}{% endcapture %} + {{date | truncate: 3, ""}} + + {{training.date | date: '%-d' }} + +
      +

      {{training.title}}

      +
        +
      • {{training.where | upcase}}
      • +
      • +
      • {{training.organizer}}
      • +
      +
      +
      + {% endfor %} +
      + +
      \ No newline at end of file diff --git a/_includes/worldmap.html b/_includes/worldmap.html deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/_it/tutorials/scala-for-java-programmers.md b/_it/tutorials/scala-for-java-programmers.md new file mode 100644 index 0000000000..35205679c9 --- /dev/null +++ b/_it/tutorials/scala-for-java-programmers.md @@ -0,0 +1,716 @@ +--- +layout: singlepage-overview +title: Un'introduzione a Scala per programmatori Java + +partof: scala-for-java-programmers + +discourse: false +language: it +--- + +Di Michel Schinz e Philipp Haller. +Traduzione italiana a cura di Mirco Veltri. + +## Introduzione + +Lo scopo di questo documento è quello di fornire una rapida +introduzione al linguaggio e al compilatore Scala. È rivolto a +chi ha già qualche esperienza di programmazione e desidera una +panoramica di cosa è possibile fare con Scala. Si assume una +conoscenza di base dei concetti di programmazione orientata +agli oggetti, specialmente in Java. + +## Un Primo Esempio + +Come primo esempio useremo lo standard *Hello world*. Non è sicuramente +un esempio entusiasmante ma rende facile dimostrare l'uso dei tool di +Scala senza richiedere troppe conoscenze del linguaggio stesso. +Ecco come appeare il codice: + + object HelloWorld { + def main(args: Array[String]) { + println("Hello, world!") + } + } + +La struttura di questo programma dovrebbe essere familiare ai +programmatori Java: c'è un metodo chiamato `main` che accetta argomenti, +un array di stringhe, forniti da riga di comando come +parametri; Il corpo del metodo consiste di una singola chiamata al +predefinito `println` che riceve il nostro amichevole saluto +come parametro. Il metodo `main` non ritorna alcun valore +(è un metodo procedura), pertanto non è necessario dichiararne uno +di ritorno. + +Ciò che è meno familiare ai programmatori Java è la +dichiarazione di `object` contenente il metodo `main`. +Tale dichiarazione introduce ciò che è comunemente chiamato +*oggetto singleton*, cioè una classe con una unica istanza. +La dichiarazione precedente infatti crea sia la classe `HelloWorld` +che una istanza di essa, chiamata `HelloWorld`. L'istanza è creata +su richiesta la prima volta che è usata. + + +Il lettore astuto avrà notato che il metodo `main` non è stato +dichiarato come `static`. Questo perchè i membri (metodi o campi) +statici non esistono in Scala. Invece che definire membri statici, +il programmatore Scala li dichiara in oggetti singleton. + +### Compiliamo l'esempio + +Per compilare l'esempio useremo `scalac`, il compilatore Scala. +`scalac` lavora come la maggior parte dei compilatori: prende un file +sorgente come argomento, eventuali opzioni e produce uno o più object +file come output. Gli object file sono gli standard file delle classi +di Java. + +Se salviamo il file precedente come `HelloWorld.scala` e lo compiliamo +con il seguente comando (il segno maggiore `>' rappresenta il prompt +dei comandi e non va digitato): + + > scalac HelloWorld.scala + +sarà generato qualche class file nella directory corrente. Uno di questi +avrà il nome `HelloWorld.class` e conterrà una classe che può essere +direttamente eseguita con il comando `scala`, come mostra la seguente +sezione. + +### Eseguimo l'esempio + +Una volta compilato il programma può esser facilmente eseguito con il +comando scala. L'uso è molto simile al comando java ed accetta le stesse +opzioni. Il precedente esempio può esser eseguito usando il seguente +comando. L'output prodotto è quello atteso: + + > scala -classpath . HelloWorld + + Hello, world! + +## Interazione con Java + +Uno dei punti di forza di Scala è quello di rendere semplice l’interazione con +codice Java. Tutte le classi del package `java.lang` sono importate di +default mentre le altre richiedono l’esplicito import. + +Osserviamo un esempio che lo dimostra. Vogliamo ottenere la data +corrente e formattarla in accordo con la convezione usata in uno +specifico paese del mondo, diciamo la Francia. (Altre regioni, come la parte +di lingua francese della Svizzera, utilizzano le stesse convenzioni.) + +Le librerie delle classi Java definiscono potenti classi di utilità come +`Date` e `DateFormat`. Poiché Scala interagisce direttamente con Java, non +esistono le classi equivalenti nella libreria delle classi di Scala--possiamo +semplicemente importare le classi dei corrispondenti package Java: + + import java.util.{Date, Locale} + import java.text.DateFormat + import java.text.DateFormat._ + + object FrenchDate { + def main(args: Array[String]) { + val now = new Date + val df = getDateInstance(LONG, Locale.FRANCE) + println(df format now) + } + } + +L’istruzione import di Scala è molto simile all’equivalente in Java +tuttavia, risulta essere più potente. Più classi possono essere importate +dallo stesso package includendole in parentesi graffe come nella prima riga +di codice precedentemente riportato. Un’altra differenza è evidente +nell’uso del carattere underscore (`_`) al posto dell’asterisco (`*`) per +importare tutti i nomi di un package o di una classe. Questo perché +l’asterisco è un identificatore valido (e.g. nome di un metodo), come +vedremo più avanti. + +Inoltre, l’istruzione import sulla terza riga importa tutti i membri +della classe `DateFormat`. Questo rende disponibili il metodo statico +`getDateInstance` ed il campo statico `LONG`. + +All’interno del metodo `main` creiamo un’istanza della classe `Date` di +Java che di default contiene la data corrente. Successivamente, definiamo il +formato della data usando il metodo statico `getDateInstance` importato +precedentemente. Infine, stampiamo la data corrente, formattata secondo la +localizzazione scelta, con l’istanza `DateFormat`; quest’ultima linea mostra +un’importante proprietà di Scala.I metodi che prendono un argomento possono +essere usati con una sintassi non fissa. Questa forma dell’espressione + + df format now + +è solo un altro modo meno esteso di scriverla come + + df.format(now) + +Apparentemente sembra un piccolo dettaglio sintattico ma, presenta delle +importanti conseguenze. Una di queste sarà esplorata nella prossima +sezione. + +A questo punto, riguardo l’integrazione con Java, abbiamo notato che è +altresì possibile ereditare dalle classi Java ed implementare le interfacce +direttamente in Scala. + + +## Tutto è un Oggetto + +Scala è un linguaggio orientato agli oggetti (_object-oriented_) puro nel +senso che *ogni cosa* è un oggetto, inclusi i numeri e le funzioni. In questo +differisce da Java che invece distingue tra tipi primitivi (come `boolean` + e `int` ) e tipi referenziati. Inoltre, Java non permette la manipolazione + di funzioni come fossero valori. + +### I numeri sono oggetti + +Poichè i numeri sono oggetti, hanno dei metodi. Di fatti +un’espressione aritmetica come la seguente: + + 1 + 2 * 3 / x + +consiste esclusivamente di chiamate a metodi e risulta equivalente alla +seguente espressione, come visto nella sezione precedente: + + (1).+(((2).*(3))./(x)) + +Questo significa anche che `+`, `*`, etc. sono identificatori validi in +in Scala. + +Le parentesi intorno ai numeri nella seconda versione sono necessarie +perché l’analizzatore lessicale di Scala usa le regole di match più +lunghe per i token quindi, dovrebbe dividere la seguente espressione: + + 1.+(2) + +nei token `1.`, `+`, and `2`. La ragione per cui si è scelto questo tipo +di assegnazione di significato è perché `1.` è un match più lungo e valido +di `1`. Il token `1.` è interpretato come `1.0` rendendolo un `Double` e +non più un `Int`. Scrivendo l’espressione come: + + (1).+(2) + +si evita che `1` sia interpretato come un `Double`. + +### Le funzioni sono oggetti + +Forse per i programmatori Java è più sorprendente scoprire che in Scala +anche le funzioni sono oggetti. È pertanto possibile passare le funzioni +come argomenti, memorizzarle in variabili e ritornarle da altre funzioni. +L’abilità di manipolare le funzioni come valori è uno dei punti +cardini di un interessante paradigma di programmazione chiamato +*programmazione funzionale*. + +Come esempio semplice del perché può risultare utile usare le funzioni +come valori consideriamo una funzione timer che deve eseguire delle +azione ogni secondo. Come specifichiamo l’azione da eseguire? +Logicamente come una funzione. Questo tipo di passaggio di funzione è +familiare a molti programmatori: viene spesso usato nel codice delle +interfacce utente per registrare le funzioni di call-back richiamate +quando un evento si verifica. + +Nel successivo programma la funzione timer è chiamata `oncePerSecond` e +prende come argomento una funzione di call-back. Il tipo di questa +funzione è scritto come `() => Unit` che è il tipo di tutte le funzioni +che non prendono nessun argomento e non restituiscono niente (il tipo + `Unit` è simile al `void` del C/C++). La funzione principale di questo +programma è quella di chiamare la funzione timer con una call-back che +stampa una frase sul terminale. In altre parole questo programma stampa la +frase “time flies like an arrow” ogni secondo. + + object Timer { + def oncePerSecond(callback: () => Unit) { + while (true) { callback(); Thread sleep 1000 } + } + def timeFlies() { + println("time flies like an arrow...") + } + def main(args: Array[String]) { + oncePerSecond(timeFlies) + } + } + +Notare che per stampare la stringa usiamo il metodo `println` predefinito +invece di quelli inclusi in `System.out`. + +#### Funzioni anonime + +Il codice precedente è semplice da capire e possiamo raffinarlo ancora +un po’. Notiamo preliminarmente che la funzione `timeFlies` è definita +solo per esser passata come argomento alla funzione `oncePerSecond`. +Nominare esplicitamente una funzione con queste caratteristiche non è +necessario. È più interessante costruire detta funzione nel momento in +cui viene passata come argomento a `oncePerSecond`. Questo è possibile +in Scala usando le *funzioni anonime*, funzioni cioè senza nome. La +versione rivista del nostro programma timer usa una funzione anonima +invece di *timeFlies* e appare come di seguito: + + object TimerAnonymous { + def oncePerSecond(callback: () => Unit) { + while (true) { callback(); Thread sleep 1000 } + } + def main(args: Array[String]) { + oncePerSecond(() => + println("time flies like an arrow...")) + } + } + +La presenza delle funzioni anonime in questo esempio è rivelata dal +simbolo `=>` che separa la lista degli argomenti della funzione dal +suo corpo. In questo esempio la lista degli argomenti è vuota e di fatti +la coppia di parentesi sulla sinistra della freccia è vuota. Il corpo della +funzione `timeFlies` è lo stesso del precedente. + +## Le Classi + +Come visto precedentemente Scala è un linguaggio orientato agli oggetti e +come tale presenta il concetto di classe. (Per ragioni di completezza +va notato che alcuni linguaggi orientati agli oggetti non hanno il concetto +di classe; Scala non è uno di questi.) Le classi in Scala sono dichiarate +usando una sintassi molto simile a quella usata in Java. Un'importante +differenza è che le classi in Scala possono avere dei parametri. Questo +concetto è mostrato nella seguente definizione dei numeri complessi. + + class Complex(real: Double, imaginary: Double) { + def re() = real + def im() = imaginary + } + +Questa classe per i numeri complessi prende due argomenti, la parte +immaginaria e quella reale del numero complesso. Questi possono esser +passati quando si crea una istanza della classe `Complex` nel seguente +modo: `new Complex(1.5, 2.3)`. La classe ha due metodi, `re` e `im` che +danno l’accesso rispettivamente alla parte reale e a quella immaginaria +del numero complesso. + +Da notare che il tipo di ritorno dei due metodi non è specificato esplicitamante. +Sarà il compilatore che lo dedurrà automaticamente osservando la parte a destra +del segno uguale dei metodi e deducendo che per entrambi si tratta di +valori di tipo `Double`. + +Il compilatore non è sempre capace di dedurre i tipi come nel caso precedente; +purtroppo non c’è una regola semplice capace di dirci quando sarà in grado di +farlo e quando no. Nella pratica questo non è un problema poiché il compilatore +sa quando non è in grado di stabilire il tipo che non è stato definito +esplicitamente. Come semplice regola i programmatori Scala alle prime armi +dovrebbero provare ad omettere la dichiarazione di tipi che sembrano semplici +da dedurre per osservare il comportamento del compilatore. Dopo qualche tempo si +avrà la sensazione di quando è possibile omettere il tipo e quando no. + +### Metodi senza argomenti + +Un piccolo problema dei metodi `re` e `im` è che, per essere invocati, è +necessario far seguire il nome del metodo da una coppia di parentesi tonde +vuote, come mostrato nel codice seguente: + + object ComplexNumbers { + def main(args: Array[String]) { + val c = new Complex(1.2, 3.4) + println("imaginary part: " + c.im()) + } + } + +Sarebbe decisamente meglio riuscire ad accedere alla parte reale ed immaginaria +come se fossero campi senza dover scrivere anche la coppia vuota di parentesi. +Questo è perfettamente fattibile in Scala semplicemente definendo i relativi +metodi *senza argomenti*. Tali metodi differiscono da quelli con zero argomenti +perché non presentano la coppia di parentesi dopo il nome nè nella loro +definizione, nè nel loro utilizzo. La nostra classe `Complex` può essere +riscritta come segue: + + class Complex(real: Double, imaginary: Double) { + def re = real + def im = imaginary + } + + +### Eredità e overriding + +In Scala tutte le classi sono figlie di una super-classe. Quando nessuna +super-classe viene specificata, come nell’esempio della classe `Complex`, +la classe `scala.AnyRef` è implicitamente usata. + +In Scala è possibile eseguire la sovrascrittura (_override_) dei metodi +ereditati dalla super-classe. È pertanto necessario specificare esplicitamente +il metodo che si sta sovrascrivendo usando il modificatore `override` per +evitare sovrascritture accidentali. Come esempio estendiamo la nostra classe + `Complex` ridefinendo il metodo `toString` ereditato da `Object`. + + class Complex(real: Double, imaginary: Double) { + def re = real + def im = imaginary + override def toString() = + "" + re + (if (im < 0) "" else "+") + im + "i" + } + + +## Classi Case e Pattern Matching + +Un tipo di struttura dati che spesso si trova nei programmi è l’albero. +Ad esempio, gli interpreti ed i compilatori abitualmente rappresentano i +programmi internamente come alberi. I documenti XML sono alberi e +diversi tipi di contenitori sono basati sugli alberi, come gli alberi +red-black. + +Esamineremo ora come gli alberi sono rappresentati e manipolati in Scala +attraverso un semplice programma calcolatrice. Lo scopo del programma è +manipolare espressioni aritmetiche molto semplici composte da somme, +costanti intere e variabili intere. Due esempi di tali espressioni sono +`1+2` e `(x+x)+(7+y)`. + +A questo punto è necessario definire il tipo di rappresentazione per +dette espressioni e, a tale proposito, l’albero è la più naturale, con +i nodi che rappresentano le operazioni (nel nostro caso, l’addizione) mentre +le foglie sono i valori (costanti o variabili). + +In Scala questo albero è abitualmente rappresentato usando una super-classe +astratta per gli alberi e una concreta sotto-classe per i nodi o le +foglie. In un linguaggio funzionale useremmo un tipo dati algebrico per +lo stesso scopo. Scala fornisce il concetto di _classi case_ (_case classes_) +che è qualcosa che si trova nel mezzo delle due rappresentazioni. +Mostriamo come può essere usato per definire il tipo di alberi per il nostro +esempio: + + abstract class Tree + case class Sum(l: Tree, r: Tree) extends Tree + case class Var(n: String) extends Tree + case class Const(v: Int) extends Tree + +Il fatto che le classi `Sum`, `Var` e `Const` sono dichiarate come classi case +significa che rispetto alle classi standard differiscono in diversi aspetti: + +- la parola chiave `new` non è necessaria per creare un’istanza di queste + classi (i.e si può scrivere `Const(5)` invece di `new Const(5)`), +- le funzioni getter sono automaticamente definite per i parametri del + costruttore (i.e. è possibile ricavare il valore del parametro `v` del + costruttore di qualche istanza della classe `c` semplicemente + scrivendo `c.v`), +- sono disponibili le definizioni di default dei metodi `equals` e + `hashCode` che lavorano sulle *strutture* delle istanze e non sulle + loro identità, +- è disponibile la definizione di default del metodo `toString` che stampa + il valore in “source form” (e.g. l’albero per l’espressione `x+1` stampa + `Sum(Var(x),Const(1))`), +- le istanze di queste classi possono essere decomposte con il + *pattern matching* come vedremo più avanti. + +Ora che abbiamo definito il tipo dati per rappresentare le nostre +espressioni aritmetiche possiamo iniziare a definire le operazioni per +manipolarle. Iniziamo con una funzione per valutare l’espressione in un +qualche *ambiente* (_environment_) di valutazione. Lo scopo dell’environment +è quello di dare i valori alle variabili. Per esempio, l’espressione +`x+1` valutata nell’environment con associato il valore `5` alla +variabile `x`, scritto `{ x -> 5 }`, restituisce `6` come risultato. + +Inoltre, dobbiamo trovare un modo per rappresentare gli environment. +Potremmo naturalmente usare alcune strutture dati associative come una +hash table ma, possiamo anche usare direttamente delle funzioni! Un +environment in realtà non è altro che una funzione con associato un +valore al nome di una variabile. L’environment `{ x -> 5 }` +mostrato sopra può essere semplicemente scritto in Scala come: + + { case "x" => 5 } + +Questa notazione definisce una funzione che quando riceve la stringa `"x"` +come argomento restituisce l’intero `5` e fallisce con un’eccezione negli +altri casi. + +Prima di scrivere la funzione di valutazione diamo un nome al tipo di +environment. Potremmo usare sempre il tipo `String => Int` per gli environment +ma semplifichiamo il programma se introduciamo un nome per questo tipo +rendendo anche i cambiamenti futuri più facili. Questo è fatto in con la +seguente notazione: + + type Environment = String => Int + +Da ora in avanti il tipo `Environment` può essere usato come un alias per +il tipo delle funzioni da `String` a `Int`. + +Possiamo ora passare alla definizione della funzione di valutazione. +Concettualmente è molto semplice: il valore della somma di due +espressioni è pari alla somma dei valori delle loro espressioni; il +valore di una variabile è ottenuto direttamente dall’environment; il +valore di una costante è la costante stessa. Esprimere quanto appena +detto in Scala non è difficile: + + def eval(t: Tree, env: Environment): Int = t match { + case Sum(l, r) => eval(l, env) + eval(r, env) + case Var(n) => env(n) + case Const(v) => v + } + +Questa funzione di valutazione lavora effettuando un *pattern matching* +sull’albero `t`. Intuitivamente il significato della definizione precendente +dovrebbe esser chiaro: + +1. prima controlla se l’albero `t` è un `Sum`; se lo è, esegue il bind del + sottoalbero sinistro con una nuova variabile chiamata `l` ed il sotto + albero destro con una variabile chiamata `r` e procede con la + valutazione dell’espressione che segue la freccia; questa + espressione può (e lo fa) utilizzare le variabili marcate dal pattern che + appaiono alla sinistra della freccia, i.e. `l` e `r`; +2. se il primo controllo non è andato a buon fine, cioè l’albero non è + un `Sum`, va avanti e controlla se `t` è un `Var`; se lo è, esegue il bind + del nome contenuto nel nodo `Var` con una variabile `n` e procede con la + valutazione dell’espressione sulla destra; +3. se anche il secondo controllo fallisce e quindi `t` non è nè `Sum` nè `Var`, + controlla se si tratta di un `Const` e se lo è, combina il valore contenuto + nel nodo `Const` con una variabile `v` e procede con la valutazione + dell’espressione sulla destra; +4. infine, se tutti i controlli falliscono, viene sollevata + un’eccezione per segnalare il fallimento del pattern matching + dell’espressione; questo caso può accadere qui solo se si + dichiarasse almeno una sotto classe di `Tree`. + +L’idea alla base del pattern matching è quella di eseguire il match di +un valore con una serie di pattern e, non appena il match è trovato, estrarre +e nominare varie parti del valore per valutare il codice che ne fa uso. + +Un programmatore object-oriented esperto potrebbe sorprendersi del fatto +che non abbiamo definito `eval` come *metodo* della classe e delle sue +sottoclassi. Potremmo averlo fatto perchè Scala permette la definizione di +metodi nelle case classes così come nelle classi normali. Decidere quando +usare il pattern matching o i metodi è quindi una questione di gusti ma, +ha anche implicazioni importanti riguardo l’estensibilità: + +- quando si usano i metodi è facile aggiungere un nuovo tipo di nodo + definendo una sotto classe di `Tree` per esso; d’altro canto, aggiungere + una nuova operazione per manipolare l’albero è noioso e richiede la + modifica di tutte le sotto classi `Tree`; +- quando si usa il pattern matching la situazione è ribaltata: + aggiungere un nuovo tipo di nodo richiede la modifica di tutte le + funzioni in cui si fa pattern matching sull’albero per prendere in + esame il nuovo nodo; d’altro canto, aggiungere una nuova operazione + è semplice, basta definirla come una funzione indipendente. + +Per esplorare ulteriormente il pattern matching definiamo un’altra +operazione sulle espressioni aritmetiche: la derivazione simbolica. È +necessario ricordare le seguenti regole che riguardano questa +operazione: + +1. la derivata di una somma è la somma delle derivate, +2. la derivata di una variabile `v` è uno se `v` è la variabile di + derivazione, zero altrimenti, +3. la derivata di una costante è zero. + +Queste regole possono essere tradotte quasi letteralmente in codice Scala e +ottenere la seguente definizione: + + def derive(t: Tree, v: String): Tree = t match { + case Sum(l, r) => Sum(derive(l, v), derive(r, v)) + case Var(n) if (v == n) => Const(1) + case _ => Const(0) + } + +Questa funzione introduce due nuovi concetti relativi al pattern +matching. Prima di tutto l’istruzione `case` per le variabili ha un +*controllo*, un’espressione che segue la parola chiave `if`. Questo +controllo fa si che il pattern matching è eseguito solo se l’espressione +è vera. Qui viene usato per esser sicuri che restituiamo la costante `1` +solo se il nome della variabile da derivare è lo stesso della variabile +di derivazione `v`. La seconda nuova caratteristica del pattern matching qui +introdotta è la *wild-card*, scritta `_`, che corrisponde a qualunque +valore, senza assegnargli un nome. + +Non abbiamo esplorato del tutto la potenza del pattern matching ma ci +fermiamo qui per brevità. Vogliamo ancora osservare come le due +precedenti funzioni lavorano in un esempio reale. A tale scopo +scriviamo una semplice funzione `main` che esegue diverse operazioni +sull’espressione `(x+x)+(7+y)`: prima calcola il suo valore +nell’environment `{ x -> 5, y -> 7 }`, dopo calcola la +derivata relativa ad `x` e poi ad `y`. + + def main(args: Array[String]) { + val exp: Tree = Sum(Sum(Var("x"),Var("x")),Sum(Const(7),Var("y"))) + val env: Environment = { case "x" => 5 case "y" => 7 } + println("Expression: " + exp) + println("Evaluation with x=5, y=7: " + eval(exp, env)) + println("Derivative relative to x:\n " + derive(exp, "x")) + println("Derivative relative to y:\n " + derive(exp, "y")) + } + +Eseguendo questo programma otteniamo l’output atteso: + + Expression: Sum(Sum(Var(x),Var(x)),Sum(Const(7),Var(y))) + Evaluation with x=5, y=7: 24 + Derivative relative a x: + Sum(Sum(Const(1),Const(1)),Sum(Const(0),Const(0))) + Derivative relative to y: + Sum(Sum(Const(0),Const(0)),Sum(Const(0),Const(1))) + +Esaminando l’output notiamo che il risultato della derivata dovrebbe +essere semplificato prima di essere visualizzato all’utente. La +definizione di una funzione di semplificazione usando il pattern +matching rappresenta un interessante (ma sorprendentemente +ingannevole) problema che lasciamo come esercizio per il lettore. + + +## I Trait + +Una classe in Scala oltre che poter ereditare da una super-classe può anche +importare del codice da uno o più *trait*. + +Probabilmente per i programmatori Java il modo più semplice per capire cosa +sono i trait è concepirli come interfacce che possono contenere del codice. +In Scala quando una classe eredita da un trait ne implementa la relativa +interfaccia ed eredita tutto il codice contenuto in essa. + +Per comprendere a pieno l’utilità dei trait osserviamo un classico +esempio: gli oggetti ordinati. Si rivela spesso utile riuscire a confrontare +oggetti di una data classe con se stessi, ad esempio per ordinarli. In Java +gli oggetti confrontabili implementano l’interfaccia `Comparable`. In Scala +possiamo fare qualcosa di meglio che in Java definendo l’equivalente codice +di `Comparable` come un trait, che chiamiamo `Ord`. + +Sei differenti predicati possono essere utili per confrontare gli +oggetti: minore, minore o uguale, uguale, diverso, maggiore e maggiore o uguale. + Tuttavia definirli tutti è noioso, specialmente perché 4 di +essi sono esprimibili con gli altri due. Per esempio, dati i predicati di +uguale e minore, è possibile esprimere gli altri. In Scala tutte queste +osservazioni possono essere piacevolemente inclusi nella seguente +dichiarazione di un trait: + + trait Ord { + def < (that: Any): Boolean + def <=(that: Any): Boolean = (this < that) || (this == that) + def > (that: Any): Boolean = !(this <= that) + def >=(that: Any): Boolean = !(this < that) + } + +Questa definizione crea un nuovo tipo chiamato `Ord` che ha lo stesso +ruolo dell’interfaccia `Comparable` in Java e, fornisce l’implementazione +di default di tre predicati in termini del quarto astraendone uno. +I predicati di uguaglianza e disuguaglianza non sono presenti in questa +dichiarazione poichè sono presenti di default in tutti gli oggetti. + +Il tipo `Any` usato precedentemente è il super-tipo dati di tutti gli +altri tipi in Scala. Può esser visto come una versione generica del +tipo `Object` in Java dato che è altresì il super-tipo dei tipi base come +`Int`, `Float` ecc. + +Per rendere confrontabili gli oggetti di una classe è quindi sufficiente +definire i predicati con cui testare uguaglianza ed minoranza e unire la +precedente classe `Ord`. Come esempio definiamo una classe `Date` che +rappresenta le date nel calendario Gregoriano. Tali date sono composte dal +giorno, dal mese e dall’anno che rappresenteremo tutti con interi. Iniziamo +definendo la classe `Date` come segue: + + class Date(y: Int, m: Int, d: Int) extends Ord { + def year = y + def month = m + def day = d + override def toString(): String = year + "-" + month + "-" + day + +La parte importante qui è la dichiarazione `extends Ord` che segue il nome +della classe e dei parametri. Dichiara che la classe `Date` eredita il +codice dal trait `extends Ord`. + +Successivamente ridefiniamo il metodo `equals`, ereditato da `Object`, +in modo tale che possa confrontare in modo corretto le date confrontando +i singoli campi. L’implementazione di default del metodo `equals` non è +utilizzabile perché, come in Java, confronta fisicamente gli oggetti. +Arriviamo alla seguente definizione: + + override def equals(that: Any): Boolean = + that.isInstanceOf[Date] && { + val o = that.asInstanceOf[Date] + o.day == day && o.month == month && o.year == year + } + +Questo metodo fa uso di due metodi predefiniti `isInstanceOf` e `asInstanceOf`. +Il primo, `isInstanceOf`, corrisponde all’operatore `instanceOf` di Java e +restituisce true se e solo se l’oggetto su cui è applicato è una istanza del +tipo dati. Il secondo, `asInstanceOf`, corrisponde all’operatore di cast in +Java: se l’oggetto è una istanza del tipo dati è visto come tale altrimenti +viene sollevata un’eccezione `ClassCastException`. + +L’ultimo metodo da definire è il predicato che testa la condizione di +minoranza. Fa uso di un altro metodo predefinito, `error`, che solleva +un'eccezione con il messaggio di errore specificato. + + def <(that: Any): Boolean = { + if (!that.isInstanceOf[Date]) + error("cannot compare " + that + " and a Date") + + val o = that.asInstanceOf[Date] + (year < o.year) || + (year == o.year && (month < o.month || + (month == o.month && day < o.day))) + } + +Questo completa la definizione della classe `Date`. Istanze di questa classe +possono esser viste sia come date che come oggetti confrontabili. +Inoltre, tutti e sei i predicati di confronto menzionati precedentemente +sono definiti: `equals` e `<` perché appaiono direttamente nella definizione +della classe `Date` e gli altri perché sono ereditati dal trait `Ord`. + +I trait naturalmente sono utili in molte situazioni più interessanti di quella +qui mostrata, ma la discussione delle loro applicazioni è fuori dallo scopo di +questo documento. + +## Programmazione Generica + +L’ultima caratteristica di Scala che esploreremo in questo tutorial è la +programmazione generica. Gli sviluppatori Java dovrebbero essere bene +informati dei problemi relativi alla mancanza della programmazione +generica nel loro linguaggio, un’imperfezione risolta in Java 1.5. + +La programmazione generica riguarda la capacità di scrivere codice +parametrizzato dai tipi. Per esempio un programmatore che scrive una +libreria per le liste concatenate può incontrare il problema di decidere +quale tipo dare agli elementi della lista. Dato che questa lista è stata +concepita per essere usata in contesti differenti, non è possibile +decidere che il tipo degli elementi deve essere, per esempio, `Int`. +Questo potrebbe essere completamente arbitrario ed eccessivamente +restrittivo. + +I programmatori Java hanno fatto ricorso all’uso di `Object`, che è il +super-tipo di tutti gli oggetti. Questa soluzione è in ogni caso ben lontana +dall’esser ideale perché non funziona per i tipi base (`int`, `long`, `float`, +ecc.) ed implica che molto type casts dinamico deve esser fatto dal +programmatore. + +Scala rende possibile la definizione delle classi generiche (e metodi) per +risolvere tale problema. Esaminiamo ciò con un esempio del più semplice +container di classe possibile: un riferimento, che può essere o vuoto o +un puntamento ad un oggetto di qualche tipo. + + class Reference[T] { + private var contents: T = _ + def set(value: T) { contents = value } + def get: T = contents + } + +La classe `Reference` è parametrizzata da un tipo, chiamato `T`, che è il tipo +del suo elemento. Questo tipo è usato nel corpo della classe come il tipo della +variabile `contents`, l’argomento del metodo , ed il tipo restituito dal metodo +`get`. + +Il precedente codice d’esempio introduce le variabili in Scala che non +dovrebbero richiedere ulteriori spiegazioni. È tuttavia interessante +notare che il valore iniziale dato a quella variabile è `_`, che +rappresenta un valore di default. Questo valore di default è 0 per i +tipi numerici, `false` per il tipo `Boolean`, `())`per il tipo `Unit` +e `null` per tutti i tipi oggetto. + +Per usare la classe `Reference` è necessario specificare quale tipo usare per +il tipo parametro `T`, il tipo di elemento contenuto dalla cella. Ad esempio, +per creare ed usare una cella che contiene un intero si potrebbe scrivere il +seguente codice: + + object IntegerReference { + def main(args: Array[String]) { + val cell = new Reference[Int] + cell.set(13) + println("Reference contains the half of " + (cell.get * 2)) + } + } + +Come si può vedere in questo esempio non è necessario il cast del +tipo ritornato dal metodo `get` prima di usarlo come intero. Non risulta +possibile memorizzare niente di diverso da un intero nella varibile +poiché è stata dichiarata per memorizzare un intero. + +## Conclusioni + +Questo documento ha fornito una veloce introduzione del linguaggio Scala e +presentato alcuni esempi di base. Il lettore interessato può continuare, per +esempio, leggendo il documento *Scala By Example* che contiene esempi molti più +avanzati e consultare al bisogno la documentazione +*Scala Language Specification*. diff --git a/_ja/cheatsheets/index.md b/_ja/cheatsheets/index.md new file mode 100644 index 0000000000..b133ef48e4 --- /dev/null +++ b/_ja/cheatsheets/index.md @@ -0,0 +1,89 @@ +--- +layout: cheatsheet +title: Scalacheat + +partof: cheatsheet + +by: Kenji Ohtsuka +about: Thanks to Brendan O'Connor. このチートシートは Scala 構文 のクイックリファレンスとして作成されました。 Licensed by Brendan O'Connor under a CC-BY-SA 3.0 license. + +language: ja +--- + +###### Contributed by {{ page.by }} +{{ page.about }} + + +| 変数 | | +| `var x = 5` | 変数 | +| Good `val x = 5`
      Bad `x=6` | 定数 | +| `var x: Double = 5` | 明示的な型 | +| 関数 | | +| Good `def f(x: Int) = { x*x }`
      Bad `def f(x: Int) { x*x }` | 関数定義
      落とし穴: = を書かないと Unit を返す手続きになり、大惨事の原因になります。 | +| Good `def f(x: Any) = println(x)`
      Bad `def f(x) = println(x)` | 関数定義
      シンタックスエラー: すべての引数に型指定が必要です。 | +| `type R = Double` | 型エイリアス | +| `def f(x: R)` vs.
      `def f(x: => R)` | 値渡し
      名前渡し (遅延評価パラメータ) | +| `(x:R) => x*x` | 無名関数 | +| `(1 to 5).map(_*2)` vs.
      `(1 to 5).reduceLeft( _+_ )` | 無名関数: アンダースコアは位置に応じて引数が代入されます。 | +| `(1 to 5).map( x => x*x )` | 無名関数: 引数を2回使用する場合は名前をつけます。 | +| Good `(1 to 5).map(2*)`
      Bad `(1 to 5).map(*2)` | 無名関数: 片側が束縛された中置演算。 わかりづらいので `2*_` と書くことを推奨します。 | +| `(1 to 5).map { val x=_*2; println(x); x }` | 無名関数: ブロックスタイルでは最後の式の結果が戻り値になります。 | +| `(1 to 5) filter {_%2 == 0} map {_*2}` | 無名関数: パイプラインスタイル (括弧でも同様) 。 | +| `def compose(g:R=>R, h:R=>R) = (x:R) => g(h(x))`
      `val f = compose({_*2}, {_-1})` | 無名関数: 複数のブロックを渡す場合は外側の括弧が必要です。 | +| `val zscore = (mean:R, sd:R) => (x:R) => (x-mean)/sd` | カリー化の明示的記法 | +| `def zscore(mean:R, sd:R) = (x:R) => (x-mean)/sd` | カリー化の明示的記法 | +| `def zscore(mean:R, sd:R)(x:R) = (x-mean)/sd` | カリー化の糖衣構文、ただしこの場合、 | +| `val normer = zscore(7, 0.4)_` | 部分関数を取得するには末尾にアンダースコアが必要です。 | +| `def mapmake[T](g:T=>T)(seq: List[T]) = seq.map(g)` | ジェネリック型 | +| `5.+(3); 5 + 3`
      `(1 to 5) map (_*2)` | 中置記法 | +| `def sum(args: Int*) = args.reduceLeft(_+_)` | 可変長引数 | +| パッケージ | | +| `import scala.collection._` | ワイルドカードでインポートします。 | +| `import scala.collection.Vector`
      `import scala.collection.{Vector, Sequence}` | 個別にインポートします。 | +| `import scala.collection.{Vector => Vec28}` | 別名でインポートします。 | +| `import java.util.{Date => _, _}` | Date を除いて java.util のすべてをインポートします。 | +| _(ファイル先頭の)_ `package pkg`
      `package pkg { ... }` | パッケージ宣言 | +| データ構造 | | +| `(1,2,3)` | タプルリテラル (`Tuple3`) | +| `var (x,y,z) = (1,2,3)` | 構造化代入: パターンマッチによるタプルの展開。 | +| Bad`var x,y,z = (1,2,3)` | 隠れたエラー: 各変数にタプル全体が代入されます。 | +| `var xs = List(1,2,3)` | リスト (イミュータブル) | +| `xs(2)` | 括弧を使って添字を書きます。 ([slides](http://www.slideshare.net/Odersky/fosdem-2009-1013261/27)) | +| `1 :: List(2,3)` | 先頭に要素を追加 | +| `1 to 5` _(_ `1 until 6`
      `1 to 10 by 2` _と同じ)_ | Range の糖衣構文 | +| `()` _(中身のない括弧)_ | Unit 型 の唯一の値(C/Java でいう void) 。 | +| 制御構文 | | +| `if (check) happy else sad` | 条件分岐 | +| `if (check) happy`
      _(_ `if (check) happy else ()` _と同じ)_ | 条件分岐の省略形 | +| `while (x < 5) { println(x); x += 1}` | while ループ | +| `do { println(x); x += 1} while (x < 5)` | do while ループ | +| `import scala.util.control.Breaks._`
      `breakable {`
      ` for (x <- xs) {`
      ` if (Math.random < 0.1) break`
      ` }`
      `}`| break ([slides](http://www.slideshare.net/Odersky/fosdem-2009-1013261/21)) | +| `for (x <- xs if x%2 == 0) yield x*10`
      _(_ `xs.filter(_%2 == 0).map(_*10)` _と同じ)_ | for 内包表記: filter/map | +| `for ((x,y) <- xs zip ys) yield x*y`
      _(_ `(xs zip ys) map { case (x,y) => x*y }` _と同じ)_ | for 内包表記: 構造化代入 | +| `for (x <- xs; y <- ys) yield x*y`
      _(_ `xs flatMap {x => ys map {y => x*y}}` _と同じ)_ | for 内包表記: 直積 | +| `for (x <- xs; y <- ys) {`
      `println("%d/%d = %.1f".format(x, y, x/y.toFloat))`
      `}` | for 内包表記: 命令型の記述
      [sprintf-style](http://java.sun.com/javase/6/docs/api/java/util/Formatter.html#syntax) | +| `for (i <- 1 to 5) {`
      `println(i)`
      `}` | for 内包表記: 上限を含んだ走査 | +| `for (i <- 1 until 5) {`
      `println(i)`
      `}` | for 内包表記: 上限を除いた走査 | +| パターンマッチング | | +| Good `(xs zip ys) map { case (x,y) => x*y }`
      Bad `(xs zip ys) map( (x,y) => x*y )` | case をパターンマッチのために関数の引数で使っています。 | +| Bad
      `val v42 = 42`
      `Some(3) match {`
      ` case Some(v42) => println("42")`
      ` case _ => println("Not 42")`
      `}` | "v42" は任意の Int の値とマッチする変数名として解釈され、 "42" が表示されます。 | +| Good
      `val v42 = 42`
      `Some(3) match {`
      `` case Some(`v42`) => println("42")``
      `case _ => println("Not 42")`
      `}` | バッククオートで囲んだ "\`v42\`" は既に存在する `v42` として解釈され、 "Not 42" が表示されます。 | +| Good
      `val UppercaseVal = 42`
      `Some(3) match {`
      ` case Some(UppercaseVal) => println("42")`
      ` case _ => println("Not 42")`
      `}` | 大文字から始まる `UppercaseVal` は既に存在する定数として解釈され、新しい変数としては扱われません。 これにより `UppercaseVal` は `3` とは異なる値と判断され、 "Not 42" が表示されます。 | +| オブジェクト指向 | | +| `class C(x: R)`
      _(_ `class C(private val x: R)`
      `var c = new C(4)` _と同じ)_ | コンストラクタの引数 - private | +| `class C(val x: R)`
      `var c = new C(4)`
      `c.x` | コンストラクタの引数 - public | +| `class C(var x: R) {`
      `assert(x > 0, "positive please")`
      `var y = x`
      `val readonly = 5`
      `private var secret = 1`
      `def this = this(42)`
      `}`|
      コンストラクタはクラスの body 部分 です。
      public メンバ の宣言
      読取可能・書込不可なメンバの宣言
      private メンバ の宣言
      代替コンストラクタ | +| `new{ ... }` | 無名クラス | +| `abstract class D { ... }` | 抽象クラスの定義 (生成不可) | +| `class C extends D { ... }` | 継承クラスの定義 | +| `class D(var x: R)`
      `class C(x: R) extends D(x)` | 継承とコンストラクタのパラメータ (要望: 自動的にパラメータを引き継げるようになってほしい) +| `object O extends D { ... }` | シングルトンオブジェクトの定義 (モジュールに似ている) | +| `trait T { ... }`
      `class C extends T { ... }`
      `class C extends D with T { ... }` | トレイト
      実装を持ったインターフェースで、コンストラクタのパラメータを持つことができません。 [mixin-able]({{ site.baseurl }}/tutorials/tour/mixin-class-composition.html). +| `trait T1; trait T2`
      `class C extends T1 with T2`
      `class C extends D with T1 with T2` | 複数のトレイトを組み合わせられます。 | +| `class C extends D { override def f = ...}` | メソッドの override は明示する必要があります。 | +| `new java.io.File("f")` | オブジェクトの生成 | +| Bad `new List[Int]`
      Good `List(1,2,3)` | 型のエラー: 抽象型のオブジェクトは生成できません。
      代わりに、習慣として、型を隠蔽するファクトリを使います。 | +| `classOf[String]` | クラスの情報取得 | +| `x.isInstanceOf[String]` | 型のチェック (実行時) | +| `x.asInstanceOf[String]` | 型のキャスト (実行時) | +| `x: String` | 型帰属 (コンパイル時) | diff --git a/_ja/overviews/collections/arrays.md b/_ja/overviews/collections/arrays.md new file mode 100644 index 0000000000..705ce2ddf9 --- /dev/null +++ b/_ja/overviews/collections/arrays.md @@ -0,0 +1,124 @@ +--- +layout: multipage-overview +title: 配列 + +discourse: false + +partof: collections +overview-name: Collections + +num: 10 +language: ja +--- + +配列 ([`Array`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/Array.html)) は Scala のコレクションの中でも特殊なものだ。Scala 配列は、Java の配列と一対一で対応する。どういう事かと言うと、Scala の配列 `Array[Int]` は Java の `int[]` で実装されており、`Array[Double]` は Java の `double[]`、`Array[String]` は Java の `String[]` で実装されている。その一方で、Scala の配列は Java のそれに比べて多くの機能を提供する。まず、Scala の配列は**ジェネリック**であることができる。 つまり、型パラメータか抽象型の `T` に対する `Array[T]` を定義することができる。次に、Scala の配列は Scala の列と互換性があり、`Seq[T]` が期待されている所に `Array[T]` を渡すことができる。さらに、Scala の配列は列の演算の全てをサポートする。以下に具体例で説明する: + + scala> val a1 = Array(1, 2, 3) + a1: Array[Int] = Array(1, 2, 3) + scala> val a2 = a1 map (_ * 3) + a2: Array[Int] = Array(3, 6, 9) + scala> val a3 = a2 filter (_ % 2 != 0) + a3: Array[Int] = Array(3, 9) + scala> a3.reverse + res0: Array[Int] = Array(9, 3) + +Scala の配列は Java の配列で実装されているのに、どのようにして新たな機能をサポートしてるのだろうか。実は、Scala 2.8 とその前のバージョンではその問に対する答が変わってくる。以前は Scala のコンパイラが、ボックス化 (boxing) とボックス化解除 (unboxing) と呼ばれる「魔法」により配列と `Seq` オブジェクトの間を変換していた。この詳細は、特にジェネリック型の `Array[T]` が作成された場合、非常に複雑なものとなる。不可解な特殊ケースなどもあり、配列演算の性能特性は予想不可能なものとなった。 + +Scala 2.8 の設計はより単純なものだ。ほぼ全てのコンパイラの魔法は無くなった。代わりに、Scala 2.8 配列の実装は全体的に暗黙の変換 (implicit conversion) を利用する。Scala 2.8 では、配列はあたかも列であるようなふりをしない。 そもそもネイティブな配列のデータ型の実装は `Seq` の子クラスではないため、それは不可能というものだ。代わりに、配列を `Seq` の子クラスである `scala.collection.mutable.WrappedArray` クラスで「ラッピング」する暗黙の変換が行われる。以下に具体例で説明する: + + scala> val seq: Seq[Int] = a1 + seq: Seq[Int] = WrappedArray(1, 2, 3) + scala> val a4: Array[Int] = s.toArray + a4: Array[Int] = Array(1, 2, 3) + scala> a1 eq a4 + res1: Boolean = true + +上記のやりとりは、配列から `WrappedArray` への暗黙の変換があるため、配列と列に互換性があることを示す。`WrappedArray` から `Array` へ逆の方向に変換するには、`Traversable` に定義されている `toArray` メソッドを使うことで実現できる。上記の REPL の最後の行は、ラッピングした後、`toArray` でそれを解除したときに、同一の配列が得られることを示す。 + +配列に適用されるもう一つの暗黙の変換がある。この変換は単に列メソッドの全てを配列に「追加」するだけで、配列自身を列には変換しない。この「追加」は、配列が全ての列メソッドをサポートする `ArrayOps` 型のオブジェクトにラッピングされることを意味している。典型的には、この `ArrayOps` は短命で、列メソッドを呼び出し終えた後にはアクセス不可能となり、そのメモリ領域はリサイクルされる。現代的な仮想機械 (VM) は、しばしばこのようなオブジェクトの生成そのものを省略できる。 + +以下の REPL のやりとりで、二つの暗黙の変換の違いを示す: + + scala> val seq: Seq[Int] = a1 + seq: Seq[Int] = WrappedArray(1, 2, 3) + scala> seq.reverse + res2: Seq[Int] = WrappedArray(3, 2, 1) + scala> val ops: collection.mutable.ArrayOps[Int] = a1 + ops: scala.collection.mutable.ArrayOps[Int] = [I(1, 2, 3) + scala> ops.reverse + res3: Array[Int] = Array(3, 2, 1) + +実際には `WrappedArray` である `seq` に対して `reverse` を呼ぶと再び `WrappedArray` が返っているのが分かる。`WrappedArray` は `Seq` であり、`Seq` に対して `reverse` を呼ぶと再び `Seq` が返るため、この結果は論理的だ。一方、`ArrayOps` クラスの値 `ops` に対して `reverse` を呼ぶと、`Seq` ではなく、`Array` が返る。 + +上記の `ArrayOps` の例は。`WrappedArray` との違いを示すためだけのかなり恣意的な物で、通常は `ArrayOps` クラスの値を定義することはありえない。単に配列に対して `Seq` メソッドを呼び出すだけでいい: + + scala> a1.reverse + res4: Array[Int] = Array(3, 2, 1) + +`ArrayOps` オブジェクトは暗黙の変換により自動的に導入されるからだ。よって、上の一行は以下に等しく、 + + scala> intArrayOps(a1).reverse + res5: Array[Int] = Array(3, 2, 1) + +`intArrayOps` が先の例で暗黙の変換により導入されたものだ。ここで問題となるのが、先の例でコンパイラがどうやってもう一つの暗黙の変換である `WrappedArray` に対して `intArrayOps` を優先させたのかということだ。結局の所、両方の変換も配列をインプットが指定した `reverse` メソッドをサポートする型へ変換するものだ。二つの暗黙の変換には優先順序が付けられているというのがこの問への答だ。`ArrayOps` 変換には、`WrappedArray` 変換よりも高い優先順位が与えられている。`ArrayOps` 変換は `Predef` オブジェクトで定義されているのに対し、`WrappedArray` 変換は +`Predef` が継承する `scala.LowPriorityImplicits` で定義されている。子クラスや子オブジェクトで定義される暗黙の変換は、親クラスで定義される暗黙の変換に対して優先される。よって、両方の変換が適用可能な場合は、`Predef` +で定義されるものが選ばれる。文字列まわりにも似た仕組みがある。 + +これで、配列において列との互換性および全ての列メソッドのサポートを実現しているかが分かったと思う。ジェネリック性についてはどうだろう。Java では型パラメータ `T` に対して `T[]` と書くことはできない。では、Scala の `Array[T]` はどのように実装されるのだろう。`Array[T]` のようなジェネリックな配列は実行時においては、Java の 8つあるプリミティブ型の +`byte[]`、`short[]`、`char[]`、`int[]`、`long[]`、`float[]`、`double[]`、`boolean[]` のどれか、もしくはオブジェクトの配列である可能性がある。 これらの型に共通の実行時の型は `AnyRef` (もしくは、それと等価な `java.lang.Object`) であるので、Scala のコンパイラは `Array[T]` を `AnyRef` にマップする。実行時に、型が `Array[T]` である配列の要素が読み込まれたり、更新された場合、実際の配列型を決定するための型判定手順があり、その後 Java 配列に対して正しい型演算が実行される。この型判定は配列演算を多少遅くする。ジェネリックな配列へのアクセスはプリミティブ型やオブジェクトの配列に比べて 3〜4倍遅いと思っていい。つまり、最高の性能を必要とするなら、ジェネリック配列ではなく具象配列を使ったほうがいいことを意味する。いくらジェネリック配列を実装しても、それを**作成する**方法がなければ意味が無い。これは更に難しい問題で、あなたにも少し手を借りる必要がある。この問題を説明するのに、配列を作成するジェネリックなメソッドの失敗例を見てほしい。 + + // これは間違っている! + def evenElems[T](xs: Vector[T]): Array[T] = { + val arr = new Array[T]((xs.length + 1) / 2) + for (i <- 0 until xs.length by 2) + arr(i / 2) = xs(i) + arr + } + +`evenElems` メソッドは、引数のベクトル `xs`内の偶数位置にある全ての要素から成る新しい配列を返す。`evenElems` の本文一行目にて、引数と同じ型を持つ戻り値の配列が定義されている。そのため、実際の型パラメータ `T` の実際の型により、これは `Array[Int]`、`Array[Boolean]`、もしくはその他の Java のプリミティブ型の配列か、参照型の配列であるかもしれない。これらの型は実行時に異なる実装を持つため、Scala ランタイムはどのようにして正しいものを選択するのだろう。実際のところ、型パラメータ `T` に対応する実際の型は実行時に消去されてしまうため、与えられた情報だけでは選択することができない。そのため、上記のコードをコンパイルしようとすると以下のエラーが発生する: + + error: cannot find class manifest for element type T + val arr = new Array[T]((arr.length + 1) / 2) + ^ + +あなたがコンパイラを手伝ってあげて、`evenElems` の型パタメータの実際の型が何であるかの実行時のヒントを提供することが必要とされている。この実行時のヒントは `scala.reflect.ClassManifest` +型の**クラスマニフェスト**という形をとる。クラスマニフェストとは、型の最上位クラスが何であるかを記述する型記述オブジェクトだ。型に関するあらゆる事を記述する `scala.reflect.Manifest` 型の完全マニフェストというものもある。配列の作成にはクラスマニフェストで十分だ。 + +Scala コンパイラは、指示を出すだけでクラスマニフェストを自動的に構築する。「指示を出す」とは、クラスマニフェストを以下のように暗黙のパラメータ (implicit parameter) として要求することを意味する: + + def evenElems[T](xs: Vector[T])(implicit m: ClassManifest[T]): Array[T] = ... + +**context bound** という、より短い別の構文を使うことで型がクラスマニフェストを連れてくることを要求できる。これは、型の後にコロン (:) とクラス名 `ClassManifest` を付けることを意味する: + + // これは動作する + def evenElems[T: ClassManifest](xs: Vector[T]): Array[T] = { + val arr = new Array[T]((xs.length + 1) / 2) + for (i <- 0 until xs.length by 2) + arr(i / 2) = xs(i) + arr + } + +2つの `evenElems` の改訂版は全く同じことを意味する。どちらの場合も、`Array[T]` が構築されるときにコンパイラは型パラメータ `T` のクラスマニフェスト、つまり `ClassManifest[T]` 型の暗黙の値 (implicit value)、を検索する。暗黙の値が見つかれば、正しい種類の配列を構築するのにマニフェストが使用される。見つからなければ、その前の例のようにエラーが発生する。 + +以下に `evenElems` を使った REPL のやりとりを示す。 + + scala> evenElems(Vector(1, 2, 3, 4, 5)) + res6: Array[Int] = Array(1, 3, 5) + scala> evenElems(Vector("this", "is", "a", "test", "run")) + res7: Array[java.lang.String] = Array(this, a, run) + +両者の場合とも、Scala コンパイラは要素型 (`Int`、そして `String`) のクラスマニフェストを自動的に構築して、`evenElems` メソッドの暗黙のパラメータに渡した。コンパイラは全ての具象型についてクラスマニフェストを構築できるが、引数そのものがクラスマニフェストを持たない型パラメータである場合はそれができない。以下に失敗例を示す: + + scala> def wrap[U](xs: Vector[U]) = evenElems(xs) + :6: error: No ClassManifest available for U. + def wrap[U](xs: Vector[U]) = evenElems(xs) + ^ + +何が起こったかというと、`evenElems` は型パラメータ `U` に関するクラスマニフェストを要求するが、見つからなかったのだ。当然この場合は、`U` に関する暗黙のクラスマニフェストを要求することで解決するため、以下は成功する: + + scala> def wrap[U: ClassManifest](xs: Vector[U]) = evenElems(xs) + wrap: [U](xs: Vector[U])(implicit evidence$1: ClassManifest[U])Array[U] + +この例から、`U` の定義の context bound 構文は `evidence$1` と呼ばれる `ClassManifest[U]` 型の暗黙のパラメータの略記法であることが分かる。 + +要約すると、ジェネリックな配列の作成はクラスマニフェストを必要とする。型パラメータ `T` の配列を作成する場合、`T` に関する暗黙のクラスマニフェストも提供する必要がある。その最も簡単な方法は、`[T: ClassManifest]` のように、型パラメータを context bound 構文で `ClassManifest` と共に定義することだ。 diff --git a/_ja/overviews/collections/concrete-immutable-collection-classes.md b/_ja/overviews/collections/concrete-immutable-collection-classes.md new file mode 100644 index 0000000000..508ca9a227 --- /dev/null +++ b/_ja/overviews/collections/concrete-immutable-collection-classes.md @@ -0,0 +1,196 @@ +--- +layout: multipage-overview +title: 具象不変コレクションクラス + +discourse: false + +partof: collections +overview-name: Collections + +num: 8 + +language: ja +--- + +Scala は様々な具象不変コレクションクラス (concrete immutable collection class) を提供する。これらはどのトレイトを実装するか(マップ、集合、列)、無限を扱えるか、様々な演算の速さなどの違いがある。ここに、Scala で最もよく使われる不変コレクション型を並べる。 + +## リスト + +リスト ([`List`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/List.html)) は有限の不変列だ。リストは最初の要素とリストの残りの部分に定数時間でアクセスでき、また、新たな要素をリストの先頭に追加する定数時間の cons 演算を持つ。他の多くの演算は線形時間で行われる。 + +リストは Scala プログラミングの働き者であり続けてきたので、あえてここで語るべきことは多くない。Scala 2.8 での大きな変更点は `List` クラスはそのサブクラスである `::` とそのサブオブジェクトである `Nil` とともに、論理的にふさわしい `scala.collection.immutable` パッケージで定義されるようになったことだ。`scala` パッケージには `List`、`Nil`、および `::` へのエイリアスがあるため、ユーザの立場から見ると、リストは今まで通り使うことができる。 + +もう一つの変更点は、リストは以前のような特殊扱いではなく、コレクションフレームワークにより緊密に統合されたことだ。例えば、`List` のコンパニオンオブジェクトにあった多くのメソッドは廃止予定になった。代わりに、それらは全てのコレクションが継承する[共通作成メソッド](creating-collections-from-scratch.html)に取って代わられた。 + +## ストリーム + +ストリーム ([`Stream`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Stream.html)) はリストに似ているが、要素は遅延評価される。そのため、ストリームは無限の長さをもつことができる。呼び出された要素のみが計算される。他の点においては、ストリームはリストと同じ性能特性をもつ。 + +リストは `::` 演算子によって構築されるが、ストリームはそれに似た `#::` 演算子によって構築される。以下は、整数の 1, 2, 3 からなる簡単なストリームの例だ: + + scala> val str = 1 #:: 2 #:: 3 #:: Stream.empty + str: scala.collection.immutable.Stream[Int] = Stream(1, ?) + +このストリームの head は 1 で、tail は 2 と 3 だ。上の例では tail が表示されていないが、それはまだ計算されていないからだ。ストリームは遅延評価されるため、`toString` は余計な評価を強いないように慎重に設計されているのだ。 + +以下に、もう少し複雑な例を示す。任意の二つの数から始まるフィボナッチ数列を計算するストリームだ。フィボナッチ数列とは、それぞれの要素がその前二つの要素の和である数列のことだ。 + + scala> def fibFrom(a: Int, b: Int): Stream[Int] = a #:: fibFrom(b, a + b) + fibFrom: (a: Int,b: Int)Stream[Int] + +この関数は嘘のように単純だ。数列の最初の要素は明らかに `a` で、残りは `b` そして `a + b` から始まるフィボナッチ数列だ。無限の再帰呼び出しに陥らずにこの数列を計算するのが難しい所だ。もしこの関数が `#::` の代わりに `::` を使っていたなら、全ての呼び出しはまた別の呼び出しを招くため、無限の再帰呼び出しに陥ってしまう。しかし、`#::` +を使っているため、右辺は呼び出されるまでは評価されないのだ。 + +2つの 1 から始まるフィボナッチ数列の最初の数要素を以下に示す: + + scala> val fibs = fibFrom(1, 1).take(7) + fibs: scala.collection.immutable.Stream[Int] = Stream(1, ?) + scala> fibs.toList + res9: List[Int] = List(1, 1, 2, 3, 5, 8, 11) + +## ベクトル + +リストはアルゴリズムが慎重にリストの先頭要素 (`head`) のみを処理する場合、非常に効率的だ。`head` の読み込み、追加、および削除は一定数時間で行われるのに対して、リストの後続の要素に対する読み込みや変更は、その要素の深さに依存した線形時間で実行される。 + +ベクトル ([`Vector`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Vector.html)) は、ランダムアクセス時の非効率性を解決するために Scala 2.8 から導入された新しいコレクション型だ。ベクトルはどの要素の読み込みも「事実上」定数時間で行う。リストの `head` の読み込みや配列の要素読み込みに比べると大きい定数だが、定数であることには変りない。この結果、ベクトルを使ったアルゴリズムは列の `head` のみを読み込むことに神経質にならなくていい。任意の場所の要素を読み込んだり、変更したりできるため、コードを書くのに便利だ。 + +ベクトルは、他の列と同じように作成され、変更される。 + + scala> val vec = scala.collection.immutable.Vector.empty + vec: scala.collection.immutable.Vector[Nothing] = Vector() + scala> val vec2 = vec :+ 1 :+ 2 + vec2: scala.collection.immutable.Vector[Int] = Vector(1, 2) + scala> val vec3 = 100 +: vec2 + vec3: scala.collection.immutable.Vector[Int] = Vector(100, 1, 2) + scala> vec3(0) + res1: Int = 100 + +ベクトルは分岐度の高い木構造で表される。全てのノードは32以下の要素か、32以下の他のノードを格納する。32個以下の要素を持つベクトルは単一のノードで表すことができる。ベクトルは、たった一つの間接呼び出しで、`32 * 32 = 1024`個までの要素を扱うことができる。木構造の根ノードから末端ノードまで 2ホップで 215個、3ホップで 220個、4ホップで +230個以下までの要素をベクトルは扱うことができる。よって、普通のサイズのベクトルの要素選択は 5回以内の配列選択で行うことができる。要素選択が「事実上定数時間」と言ったのは、こういうことだ。 + +ベクトルは不変であるため、ベクトルの変更無しにベクトル内の要素を変更することはできない。しかし、`updated` メソッドを使うことで一つの要素違いの新たなベクトルを作成することができる: + + scala> val vec = Vector(1, 2, 3) + vec: scala.collection.immutable.Vector[Int] = Vector(1, 2, 3) + scala> vec updated (2, 4) + res0: scala.collection.immutable.Vector[Int] = Vector(1, 2, 4) + scala> vec + res1: scala.collection.immutable.Vector[Int] = Vector(1, 2, 3) + +最後の行が示すように、`updated` の呼び出しは元のベクトル `vec` には一切影響しない。読み込みと同様に、ベクトルの関数型更新も「事実上定数時間」で実行される。ベクトルの真ん中にある要素を更新するには、その要素を格納するノードと、木構造の根ノードからを初めとする全ての親ノードをコピーすることによって行われる。これは関数型更新は、32以内の要素か部分木を格納する 1 〜 5個の ノードを作成することを意味する。これは、可変配列の in-place での上書きに比べると、ずっと時間のかかる計算であるが、ベクトル全体をコピーするよりはずっと安いものだ。 + +ベクトルは高速なランダム読み込みと高速な関数型更新の丁度いいバランスを取れているため、不変添字付き列 ([`immutable.IndexedSeq`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/IndexedSeq.html)) トレイトのデフォルトの実装となっている: + + scala> collection.immutable.IndexedSeq(1, 2, 3) + res2: scala.collection.immutable.IndexedSeq[Int] = Vector(1, 2, 3) + +## 不変スタック + +後入れ先出し (LIFO: last in first out) の列が必要ならば、スタック ([`Stack`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Stack.html)) がある。 `push` メソッドを使ってスタックに要素をプッシュ、`pop` を使ってポップ、そして`top` を使って削除することなく一番上の要素を読み込むことができる。これらの演算は、全て定数時間で行われる。 + +以下はスタックに対して行われる簡単な演算の例だ: + + scala> val stack = scala.collection.immutable.Stack.empty + stack: scala.collection.immutable.Stack[Nothing] = Stack() + scala> val hasOne = stack.push(1) + hasOne: scala.collection.immutable.Stack[Int] = Stack(1) + scala> stack + stack: scala.collection.immutable.Stack[Nothing] = Stack() + scala> hasOne.top + res20: Int = 1 + scala> hasOne.pop + res19: scala.collection.immutable.Stack[Int] = Stack() + +機能的にリストとかぶるため、不変スタックが Scala のプログラムで使われることは稀だ: 不変スタックの `push` はリストの `::` と同じで、`pop` はリストの `tail` と同じだ。 + +## 不変キュー + +キュー ([`Queue`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Queue.html)) はスタックに似ているが、後入れ先出し (LIFO: last in first out) ではなく、先入れ先出し (FIFO: +first in first out) だ。 + +以下に空の不変キューの作り方を示す: + + scala> val empty = scala.collection.immutable.Queue[Int]() + empty: scala.collection.immutable.Queue[Int] = Queue() + +`enqueue` を使って不変キューに要素を追加することができる: + + scala> val has1 = empty.enqueue(1) + has1: scala.collection.immutable.Queue[Int] = Queue(1) + +複数の要素をキューに追加するには、enqueue の引数にコレクションを渡す: + + scala> val has123 = has1.enqueue(List(2, 3)) + has123: scala.collection.immutable.Queue[Int] + = Queue(1, 2, 3) + +キューの先頭から要素を削除するには、`dequeue` を使う: + + scala> val (element, has23) = has123.dequeue + element: Int = 1 + has23: scala.collection.immutable.Queue[Int] = Queue(2, 3) + +`dequeue` は削除された要素と残りのキューのペアを返すことに注意してほしい。 + +## 範囲 + +範囲 ([`Range`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Range.html)) は順序付けされた等間隔の整数の列だ。例えば、「1、2、3」は範囲であり、「5、8、11、14」も範囲だ。Scala で範囲を作成するには、予め定義された `to` メソッドと `by` メソッドを使う。 + + scala> 1 to 3 + res2: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3) + scala> 5 to 14 by 3 + res3: scala.collection.immutable.Range = Range(5, 8, 11, 14) + +上限を含まない範囲を作成したい場合は、`to` の代わりに、便宜上用意された `until` メソッドを使う: + + scala> 1 until 3 + res2: scala.collection.immutable.Range = Range(1, 2) + +範囲は、開始値、終了値、ステップ値という、たった三つの数で定義できため定数空間で表すことができる。そのため、範囲の多くの演算は非常に高速だ。 + +## ハッシュトライ + +ハッシュトライは不変集合と不変マップを効率的に実装する標準的な方法だ。ハッシュトライは、[`immutable.HashMap`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/HashMap.html) クラスによりサポートされている。データ構造は、全てのノードに 32個の要素か 32個の部分木があるという意味でベクトルに似ている。しかし、キーの選択はハッシュコードにより行われる。たとえば、マップから任意のキーを検索する場合、まずキーのハッシュコードを計算する。その後、最初の部分木を選択するのにハッシュコードの下位 5ビットが使われ、次の 5ビットで次の部分木が選択される、という具合だ。ノード内の全ての要素が、その階層までで選ばれているビット範囲内でお互いと異なるハッシュコードを持った時点で選択は終了する。 + +ハッシュトライは、サイズ相応の高速な検索と、相応に効率的な関数型加算 `(+)` と減算 `(-)` の調度良いバランスが取れている。そのため、ハッシュトライは Scala の不変マップと不変集合のデフォルトの実装を支えている。実は、Scala は要素が 5個未満の不変集合と不変マップに関して、更なる最適化をしている。1 〜 4個の要素を持つ集合とセットは、要素 (マップの場合は、キー/値のペア) をフィールドとして持つ単一のオブジェクトとして格納する。空の不変集合と、空の不変マップは、ぞれぞれ単一のオブジェクトである。空の不変集合や不変マップは、空であり続けるため、データ構造を複製する必要はない。 + +## 赤黒木 + +赤黒木は、ノードが「赤」か「黒」に色付けされている平衡二分木の一種だ。他の平衡二分木と同様に演算は木のサイズのログ時間内に確実に完了する。 + +Scala は内部で赤黒木を使った不変集合と不変マップの実装を提供する。[`TreeSet`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/TreeSet.html) と [`TreeMap`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/TreeMap.html) クラスがそれだ。 + + scala> scala.collection.immutable.TreeSet.empty[Int] + res11: scala.collection.immutable.TreeSet[Int] = TreeSet() + scala> res11 + 1 + 3 + 3 + res12: scala.collection.immutable.TreeSet[Int] = TreeSet(1, 3) + +赤黒木は、全ての要素をソートされた順序で返す効率的なイテレータを提供するため、整列済み集合 (`SortedSet`) +の標準実装となっている。 + +## 不変ビット集合 + +ビット集合 ([`BitSet`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/BitSet.html)) は大きい整数のビットを小さな整数のコレクションを使って表す。例えば、3, 2, と 0 を格納するビット集合は二進法で整数の 1101、十進法で 13 を表す。 + +内部では、ビット集合は 64ビットの `Long` の配列を使っている。配列の最初の `Long` は 整数の 0〜63、二番目は 64〜127 という具合だ。そのため、ビット集合は最大値が数百以下の場合は非常にコンパクトだ。 + +ビット集合の演算はとても高速だ。所属判定は一定数時間で行われる。集合への要素の追加は、ビット集合の配列内の `Long` の数に比例するが、普通は小さい数だ。以下にビット集合の使用例を示す: + + scala> val bits = scala.collection.immutable.BitSet.empty + bits: scala.collection.immutable.BitSet = BitSet() + scala> val moreBits = bits + 3 + 4 + 4 + moreBits: scala.collection.immutable.BitSet = BitSet(3, 4) + scala> moreBits(3) + res26: Boolean = true + scala> moreBits(0) + res27: Boolean = false + +## リストマップ + +リストマップ ([`ListMap`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/ListMap.html)) は、キー/値ペアの連結リスト (linked list) により実装されたマップを表す。一般的に、リストマップの演算はリスト全体を総なめする必要がある可能性がある。そのため、リストマップの演算はマップのサイズに対して線形時間をとる。標準の不変マップの方が常に高速なので Scala のリストマップが使われることはほとんど無い。唯一性能の差が出る可能性としては、マップが何らかの理由でリストの最初の要素が他の要素に比べてずっと頻繁に読み込まれるように構築された場合だ。 + + scala> val map = scala.collection.immutable.ListMap(1->"one", 2->"two") + map: scala.collection.immutable.ListMap[Int,java.lang.String] = + Map(1 -> one, 2 -> two) + scala> map(2) + res30: String = "two" diff --git a/_ja/overviews/collections/concrete-mutable-collection-classes.md b/_ja/overviews/collections/concrete-mutable-collection-classes.md new file mode 100644 index 0000000000..3af7780441 --- /dev/null +++ b/_ja/overviews/collections/concrete-mutable-collection-classes.md @@ -0,0 +1,168 @@ +--- +layout: multipage-overview +title: 具象可変コレクションクラス + +discourse: false + +partof: collections +overview-name: Collections + +num: 9 + +language: ja +--- + +Scala が標準ライブラリで提供する不変コレクションで最もよく使われるものをこれまで見てきた。続いて、具象可変コレクションクラス (concrete mutable collection class) を見ていく。 + +## 配列バッファ + +配列バッファ ([`ArrayBuffer`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/ArrayBuffer.html)) は、配列とサイズを格納するバッファだ。配列バッファの演算のほとんどは、単に内部の配列にアクセスして変更するだけなので、配列の演算と同じ速さで実行される。また、配列バッファは効率的にデータをバッファの最後に追加できる。配列バッファへの要素の追加はならし定数時間 (amortized constant time) で実行される。よって、配列バッファは要素が常に最後に追加される場合、大きいコレクションを効率的に構築するのに便利だ。 + + scala> val buf = scala.collection.mutable.ArrayBuffer.empty[Int] + buf: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer() + scala> buf += 1 + res32: buf.type = ArrayBuffer(1) + scala> buf += 10 + res33: buf.type = ArrayBuffer(1, 10) + scala> buf.toArray + res34: Array[Int] = Array(1, 10) + +## リストバッファ + +リストバッファ ([`ListBuffer`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/ListBuffer.html)) は、内部に配列の代わりに連結リストを使うこと以外は配列バッファに似ている。バッファを構築後にリストに変換する予定なら、配列バッファの代わりにリストバッファを使うべきだ。 + + scala> val buf = scala.collection.mutable.ListBuffer.empty[Int] + buf: scala.collection.mutable.ListBuffer[Int] = ListBuffer() + scala> buf += 1 + res35: buf.type = ListBuffer(1) + scala> buf += 10 + res36: buf.type = ListBuffer(1, 10) + scala> buf.toList + res37: List[Int] = List(1, 10) + +## 文字列ビルダ + +配列バッファが配列を構築するのに便利で、リストバッファがリストを構築するのに便利なように、文字列ビルダ ([`StringBuilder`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/StringBuilder.html)) は文字列を構築するのに便利なものだ。文字列ビルダはあまりに頻繁に使われるため、デフォルトの名前空間に既にインポートされている。以下のように、単に `new StringBuilder` で文字列ビルダを作成することができる: + + scala> val buf = new StringBuilder + buf: StringBuilder = + scala> buf += 'a' + res38: buf.type = a + scala> buf ++= "bcdef" + res39: buf.type = abcdef + scala> buf.toString + res41: String = abcdef + +## 連結リスト + +連結リスト ([`LinkedList`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/LinkedList.html)) は、`next` ポインタにより連結されたノードから成る可変列だ。多くの言語では空の連結リストを表すのに `null` が選ばれるが、空の列も全ての列演算をサポートする必要があるため、Scala のコレクションでは `null` は都合が悪い。特に、`LinkedList.empty.isEmpty` は `true` を返すべきで、`NullPointerException` を発生させるべきではない。 代わりに、空の連結リストは `next` フィールドがノード自身を指すという特殊な方法で表現されている。不変リスト同様、連結リストは順列通りに探索するのに適している。また、連結リストは要素や他の連結リストを簡単に挿入できるように実装されている。 + +## 双方向リスト + +双方向リスト ([`DoubleLinkedList`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/DoubleLinkedList.html)) は、`next` の他に、現ノードの一つ前の要素を指す `prev` +というもう一つの可変フィールドがあることを除けば、単方向の連結リストに似ている。リンクが一つ増えたことで要素の削除が非常に高速になる。 + +## 可変リスト + +可変リスト ([`MutableList`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/MutableList.html)) は単方向連結リストとリスト終端の空ノードを参照するポインタから構成される。これにより、リストの最後への要素の追加が、終端ノードのためにリスト全体を探索しなくてよくなるため、定数時間の演算となる。[`MutableList`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/MutableList.html) は、現在 Scala の [`mutable.LinearSeq`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/LinearSeq.html) トレイトの標準実装だ。 + +## キュー +Scala は不変キューの他に可変キュー ([`mutable.Queue`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/Queue.html)) も提供する。可変キューは不変のものと同じように使えるが、`enqueue` の代わりに `+=` と `++=` 演算子を使って加算する。また、可変キューの `dequeue` は先頭の要素を削除して、それを返す。次に具体例で説明する: + + scala> val queue = new scala.collection.mutable.Queue[String] + queue: scala.collection.mutable.Queue[String] = Queue() + scala> queue += "a" + res10: queue.type = Queue(a) + scala> queue ++= List("b", "c") + res11: queue.type = Queue(a, b, c) + scala> queue + res12: scala.collection.mutable.Queue[String] = Queue(a, b, c) + scala> queue.dequeue + res13: String = a + scala> queue + res14: scala.collection.mutable.Queue[String] = Queue(b, c) + +## 配列シーケンス + +配列シーケンス ([`ArraySeq`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/ArraySeq.html)) は、内部で要素を `Array[Object]` に格納する固定長の可変列だ。 + +典型的には、配列の性能特性が欲しいが、要素の型が特定できず、実行時に `ClassManifest` も無く、ジェネリックな列を作成したい場合に [`ArraySeq`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/ArraySeq.html) を使う。この問題は後ほど[配列の節](arrays.html)で説明する。 + +## スタック + +既に不変スタックについては説明した。スタックには、[`mutable.Stack`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/Stack.html) クラスにより実装される可変バージョンもある。変更が上書き処理されるという違いの他は、不変バージョンと全く同じように動作する。 + + scala> val stack = new scala.collection.mutable.Stack[Int] + stack: scala.collection.mutable.Stack[Int] = Stack() + scala> stack.push(1) + res0: stack.type = Stack(1) + scala> stack + res1: scala.collection.mutable.Stack[Int] = Stack(1) + scala> stack.push(2) + res0: stack.type = Stack(1, 2) + scala> stack + res3: scala.collection.mutable.Stack[Int] = Stack(1, 2) + scala> stack.top + res8: Int = 2 + scala> stack + res9: scala.collection.mutable.Stack[Int] = Stack(1, 2) + scala> stack.pop + res10: Int = 2 + scala> stack + res11: scala.collection.mutable.Stack[Int] = Stack(1) + +## 配列スタック + +配列スタック ([`ArrayStack`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/ArrayStack.html) ) は、内部に必要に応じて大きさを変えた `Array` を使った、可変スタックの代替実装だ。配列スタックは、高速な添字読み込みを提供し、他の演算に関しても普通の可変スタックに比べて少し効率的だ。 + +## ハッシュテーブル + +ハッシュテーブルは、内部で要素を配列に格納し、要素の位置はハッシュコードにより決められる。ハッシュテーブルへの要素の追加は、既に配列の中に同じハッシュコードを持つ他の要素が無い限り、定数時間で行われる。そのため、ハッシュコードの分布が適度である限り非常に高速だ。結果として、Scala の可変マップと可変集合のデフォルトの実装はハッシュテーブルに基づいており、[`mutable.HashSet`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/HashSet.html) クラスと [`mutable.HashMap`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/HashMap.html) クラスから直接アクセスできる。 + +ハッシュ集合とハッシュマップは他の集合やマップと変りなく使うことができる。以下に具体例で説明する: + + scala> val map = scala.collection.mutable.HashMap.empty[Int,String] + map: scala.collection.mutable.HashMap[Int,String] = Map() + scala> map += (1 -> "make a web site") + res42: map.type = Map(1 -> make a web site) + scala> map += (3 -> "profit!") + res43: map.type = Map(1 -> make a web site, 3 -> profit!) + scala> map(1) + res44: String = make a web site + scala> map contains 2 + res46: Boolean = false + +ハッシュテーブルからの要素の取り出しは、特定の順序を保証していない。要素の取り出しは単に内部の配列を通して行われるため、順序はその時の配列の状態により決まる。要素の取り出しの順序を保証したい場合は、普通のハッシュマップではなく**連結**ハッシュマップを使うべきだ。連結ハッシュマップもしくは連結ハッシュ集合は、要素を追加した順序を保った連結リストを含む以外は普通のハッシュマップやハッシュ集合とほとんど同じだ。それにより、コレクションからの要素の取り出しは要素が追加された順序と常に一致する。 + +## ウィークハッシュマップ + +ウィークハッシュマップ([`WeakHashMap`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/WeakHashMap.html)) は、ガーベッジコレクタがマップから「弱キー」への参照を追跡しないという特殊なハッシュマップだ。他にキーを参照するものが無くなると、キーとその関連する値はマップから勝手にいなくなる。ウィークハッシュマップは、キーに対する時間のかかる計算結果を再利用するキャッシュのような用途に向いている。キーとその計算結果が普通のハッシュマップに格納された場合、そのマップは限りなく大きくなり続け、キーはガベージコレクタに永遠に回収されない。ウィークハッシュマップを使うことでこの問題を回避できる。キーオブジェクトが到達不可能になり次第、そのエントリーごとウィークハッシュマップから削除される。Scala のウィークハッシュマップは、Java による実装 `java.util.WeakHashMap` のラッパーである [`WeakHashMap`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/WeakHashMap.html) クラスにより実装されている。 + +## 並行マップ + +並行マップ ([`ConcurrentMap`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/ConcurrentMap.html)) は複数のスレッドから同時にアクセスすることできる。通常の[マップ](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Map.html)の演算の他に、以下のアトミックな演算を提供する: + +### ConcurrentMap クラスの演算 ### + +| 使用例 | 振る舞い| +| ------ | ------ | +| `m putIfAbsent(k, v)` |既に `k` がある場合を除き、キー/値ペア `k -> m` を `m` に追加する。| +| `m remove (k, v)` |`k` に関連付けられた値が `v` である場合、そのエントリを削除する。| +| `m replace (k, old, new)`|`k` に関連付けられた値が `old` である場合、それを `new` で上書きする。| +| `m replace (k, v)` |`k` に任意の関連付けられた値がある場合、それを `v` で上書きする。| + +[`ConcurrentMap`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/ConcurrentMap.html) は Scala コレクションライブラリ内のトレイトだ。現在そのトレイトを実装するのは Java の +`java.util.concurrent.ConcurrentMap` クラスだけで、それは [Java/Scala コレクションの標準変換](conversions-between-java-and-scala-collections.html)を使って Scala のマップに変換することができる。 + +## 可変ビット集合 + +可変ビット集合 ([`mutable.BitSet`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/BitSet.html)) は、変更が上書き処理される他は不変のものとほとんど同じだ。可変ビット集合は、変更しなかった `Long` をコピーしなくても済むぶん不変ビット集合に比べて少し効率的だ。 + + scala> val bits = scala.collection.mutable.BitSet.empty + bits: scala.collection.mutable.BitSet = BitSet() + scala> bits += 1 + res49: bits.type = BitSet(1) + scala> bits += 3 + res50: bits.type = BitSet(1, 3) + scala> bits + res51: scala.collection.mutable.BitSet = BitSet(1, 3) diff --git a/_ja/overviews/collections/conversions-between-java-and-scala-collections.md b/_ja/overviews/collections/conversions-between-java-and-scala-collections.md new file mode 100644 index 0000000000..49f2ab39a1 --- /dev/null +++ b/_ja/overviews/collections/conversions-between-java-and-scala-collections.md @@ -0,0 +1,61 @@ +--- +layout: multipage-overview +title: Java と Scala 間のコレクションの変換 + +discourse: false + +partof: collections +overview-name: Collections + +num: 17 + +language: ja +--- + +Scala と同様に、Java +にも豊富なコレクションライブラリがある。両者には多くの共通点がある。例えば、両方のライブラリともイテレータ、`Iterable`、集合、マップ、そして列を提供する。しかし、両者には重要な違いもある。特に、Scala では不変コレクションに要点を置き、コレクションを別のものに変換する演算も多く提供している。 + +時として、コレクションを一方のフレームワークから他方へと渡す必要がある。例えば、既存の Java のコレクションを Scala のコレクションであるかのようにアクセスしたいこともあるだろう。もしくは、Scala のコレクションを Java のコレクションを期待している Java メソッドに渡したいと思うかもしれない。Scala は [`JavaConversions`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/JavaConversions$.html) オブジェクトにより主要なコレクション間の暗黙の変換 (implicit conversion) +を提供するため、簡単に相互運用できる。特に以下の型に関しては、双方向変換を提供する。 + + Iterator <=> java.util.Iterator + Iterator <=> java.util.Enumeration + Iterable <=> java.lang.Iterable + Iterable <=> java.util.Collection + mutable.Buffer <=> java.util.List + mutable.Set <=> java.util.Set + mutable.Map <=> java.util.Map + mutable.ConcurrentMap <=> java.util.concurrent.ConcurrentMap + +このような変換を作動させるには、[`JavaConversions`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/JavaConversions$.html) オブジェクトからインポートするだけでいい: + + scala> import collection.JavaConversions._ + import collection.JavaConversions._ + +これで Scala コレクションとそれに対応する Java コレクションの自動変換が行われる。 + + scala> import collection.mutable._ + import collection.mutable._ + scala> val jul: java.util.List[Int] = ArrayBuffer(1, 2, 3) + jul: java.util.List[Int] = [1, 2, 3] + scala> val buf: Seq[Int] = jul + buf: scala.collection.mutable.Seq[Int] = ArrayBuffer(1, 2, 3) + scala> val m: java.util.Map[String, Int] = HashMap("abc" -> 1, "hello" -> 2) + m: java.util.Map[String,Int] = {hello=2, abc=1} + +内部では、このような変換は全ての演算を委譲する「ラッパー」オブジェクトを作ることで実現されている。そのため、Java と Scala の間でコレクションを変換してもコレクションはコピーされることはない。興味深い特性として、例えば Java 型から対応する Scala 型に変換して再び Java 型に逆変換するといった、ラウンドトリップを実行した場合、始めた時と同一のオブジェクトが返ってくるというものがある。 + +Scala コレクションの中には、Java 型に変換できるが、逆変換はできないというものもある。それらは以下の通り: + + Seq => java.util.List + mutable.Seq => java.utl.List + Set => java.util.Set + Map => java.util.Map + +Java は可変コレクションと不変コレクションを型で区別しないため、例えば `scala.immutable.List` からの変換は、上書き演算を呼び出すと `UnsupportedOperationException` を発生する `java.util.List` を返す。次に具体例で説明する: + + scala> jul = List(1, 2, 3) + jul: java.util.List[Int] = [1, 2, 3] + scala> jul.add(7) + java.lang.UnsupportedOperationException + at java.util.AbstractList.add(AbstractList.java:131) diff --git a/_ja/overviews/collections/creating-collections-from-scratch.md b/_ja/overviews/collections/creating-collections-from-scratch.md new file mode 100644 index 0000000000..4544c56457 --- /dev/null +++ b/_ja/overviews/collections/creating-collections-from-scratch.md @@ -0,0 +1,61 @@ +--- +layout: multipage-overview +title: コレクションの作成 + +discourse: false + +partof: collections +overview-name: Collections + +num: 16 + +language: ja +--- + +`List(1, 2, 3)` 構文によって 3つの整数から成るリストを作成でき、`Map('A' -> 1, 'C' -> 2)` 構文によって 2つの写像から成るマップを作成することができる。これは Scala コレクションの統一された機能だ。どのコレクションを取っても、その名前に括弧付けされた要素のリストを付け加えることができる。結果は渡された要素から成る新しいコレクションだ。以下に具体例で説明する: + + Traversable() // 空の traversable オブジェクト + List() // 空のリスト + List(1.0, 2.0) // 要素 1.0, 2.0 を含むリスト + Vector(1.0, 2.0) // 要素 1.0, 2.0 を含むベクトル + Iterator(1, 2, 3) // 3つの整数を返すイテレータ + Set(dog, cat, bird) // 3種類の動物の集合 + HashSet(dog, cat, bird) // 同じ動物のハッシュ集合 + Map('a' -> 7, 'b' -> 0) // 文字から整数へのマップ + +上の全ての行での呼び出しは内部では何らかのオブジェクトの `apply` メソッドを呼び出している。例えば、3行目は以下のように展開する: + + List.apply(1.0, 2.0) + +つまり、これは `List` クラスのコンパニオンオブジェクトの `apply` メソッドを呼び出している。このメソッドは任意の数の引数を取り、それを使ってリストを構築する。Scala ライブラリの全てのコレクションクラスには、コンパニオンオブジェクトがあり、そのような `apply` メソッドを定義する。コレクションクラスが `List`、`Stream`、や `Vector` のような具象実装を表しているのか、`Seq`、`Set`、や `Traversable` のような抽象基底クラスを表しているのかは関係ない。後者の場合は、`apply` の呼び出しは抽象基底クラスの何らかのデフォルト実装を作成するだけのことだ。用例: + + scala> List(1, 2, 3) + res17: List[Int] = List(1, 2, 3) + scala> Traversable(1, 2, 3) + res18: Traversable[Int] = List(1, 2, 3) + scala> mutable.Traversable(1, 2, 3) + res19: scala.collection.mutable.Traversable[Int] = ArrayBuffer(1, 2, 3) + +`apply` とは別に、全てのコレクションのコンパニオンオブジェクトは、空のコレクションを返す `empty` を定義する。よって、`List()` の代わりに `List.empty` と書いたり、`Map()` の代わりに `Map.empty` と書くことができる。 + +`Seq` を継承するクラスは、コンパニオンオブジェクトにおいて他の factory 演算を提供する。以下の表にこれらの演算をまとめた。要約すると、 + +* `concat` は任意の数の traversable を連結する。 +* `fill` と `tabulate` は単次元か任意の多次元の列を生成して、なんらかの式かテーブル化関数によりその列を初期化する。 +* `range` は一定のステップ値で整数の列を生成する。 +* `iterate` は開始要素に連続して関数を適用することによって得られる列を生成する。 + +### 列の factory 演算 + +| 使用例 | 振る舞い| +| ------ | ------ | +| `S.empty` | 空の列。 | +| `S(x, y, z)` | 要素 `x`, `y`, `z` からなる列。 | +| `S.concat(xs, ys, zs)` | `xs`, `ys`, `zs` の要素を連結することによって得られる列。 | +| `S.fill(n){e}` | 全ての要素が式 `e` によって計算された長さ `n` の列。 | +| `S.fill(m, n){e}` | 全ての要素が式 `e` によって計算された `m × n` の大きさの列の列。(より高次元なものもある) | +| `S.tabulate(n){f}` | 添字 `i` の位置の要素が `f(i)` によって計算された長さ `n` の列。 | +| `S.tabulate(m, n){f}` | 添字 `(i, j)` の位置の要素が `f(i, j)` によって計算された `m×n` の大きさの列の列。(より高次元なものもある)| +| `S.range(start, end)` | `start` ... `end-1` の整数の列。 | +| `S.range(start, end, step)`| `start` より始まり `end` 未満まで `step` づつ増加する整数の列。 | +| `S.iterate(x, n)(f)` | 要素 `x`、`f(x)`、`f(f(x))`、… からなる長さ `n` の列。 | diff --git a/_ja/overviews/collections/equality.md b/_ja/overviews/collections/equality.md new file mode 100644 index 0000000000..6f68fb5d90 --- /dev/null +++ b/_ja/overviews/collections/equality.md @@ -0,0 +1,34 @@ +--- +layout: multipage-overview +title: 等価性 + +discourse: false + +partof: collections +overview-name: Collections + +num: 13 + +language: ja +--- + +コレクションライブラリは等価性 (equality) とハッシング (hashing) に関して統一的な方法を取る。おおまかに言えば、まず、コレクションは集合、マップ、列の三種に大別される。別カテゴリのコレクションは常に不等だと判定される。例えば、`Set(1, 2, 3)` と `List(1, 2, 3)` は同じ要素を格納するが、不等だ。一方、同カテゴリ内ではコレクション内の要素が全く同じ要素から成る (列の場合は、同じ順序の同じ要素) 場合のみ等価だと判定される。例えば、`List(1, 2, 3) == Vector(1, 2, 3)` であり、`HashSet(1, 2) == TreeSet(2, 1)` だ。 + +コレクションが可変であるか不変であるかは、等価性の判定には関わらない。可変コレクションに関しては、等価性判定が実行された時点での要素の状態が用いられる。これは、可変コレクションが追加されたり削除されたりする要素によって、別のコレクションと等価であったり不等であったりすることを意味する。これはハッシュマップのキーとして可変コレクションを使用した場合、落とし穴となりうる。具体例としては: + + scala> import collection.mutable.{HashMap, ArrayBuffer} + import collection.mutable.{HashMap, ArrayBuffer} + scala> val buf = ArrayBuffer(1, 2, 3) + buf: scala.collection.mutable.ArrayBuffer[Int] = + ArrayBuffer(1, 2, 3) + scala> val map = HashMap(buf -> 3) + map: scala.collection.mutable.HashMap[scala.collection. + mutable.ArrayBuffer[Int],Int] = Map((ArrayBuffer(1, 2, 3),3)) + scala> map(buf) + res13: Int = 3 + scala> buf(0) += 1 + scala> map(buf) + java.util.NoSuchElementException: key not found: + ArrayBuffer(2, 2, 3) + +この例では、最後から二番目の行において配列 `xs` のハッシュコードが変わったため、最後の行の選択は恐らく失敗に終わる。ハッシュコードによる検索は `xs` が格納されていた元の位置とは別の場所を探しているからだ。 diff --git a/_ja/overviews/collections/introduction.md b/_ja/overviews/collections/introduction.md new file mode 100644 index 0000000000..e8fae90133 --- /dev/null +++ b/_ja/overviews/collections/introduction.md @@ -0,0 +1,52 @@ +--- +layout: multipage-overview +title: はじめに + +discourse: false + +partof: collections +overview-name: Collections + +num: 1 + +language: ja +--- + +**Martin Odersky, Lex Spoon 著**
      +**Eugene Yokota 訳** + +Scala 2.8 の変更点で最も重要なものは新しいコレクションフレームワークだと多くの人が思っている。確かに以前の Scala にもコレクションはあったが、Scala 2.8 になって一貫性があり、包括的な、統一コレクション型のフレームワークを提供することができた。(大部分において新しいフレームワークは旧版と互換性がある) + +コレクションの変更点は、一見すると微細なものだがプログラミングスタイルに重大な変化をもたらすことができる。コレクション内の要素ではなくコレクション全体を、プログラムを組む時の基本的なパーツとすることで、あたかも一つ上の階層でプログラミングをしているかのように感じるだろう。この新しいスタイルには慣れを必要とするが、幸い新しいコレクションの特長のお陰で楽に適合できるようになっている。 +新しいコレクションは簡単に使えて、短く書けて、安全、高速で、統一性がある。 + +**簡単に使える:** 基本的なボキャブラリは、演算 (operation) とよばれる 20〜50 のメソッドから成る。これを身につけてしまえば、二つの演算を組み合わせるだけで、ほとんどのコレクションの問題を解決することができる。複雑なループ構造や再帰に頭を悩ませる必要はない。 +永続的なコレクションと副作用のない演算は、既存のコレクションを間違って新しいデータで壊してしまう心配をする必要がないことを意味する。イテレータとコレクションの更新の間の干渉は完全になくなった。 + +**短く書ける:** 一つまたは複数のループが必要だった作業を単語一つで実現することができる。関数型の演算もライトウェイトな構文で表現でき、簡単に演算を組み合わせることでカスタム代数を扱っているかのように感じるはずだ。 + +**安全である:** これは実際に経験してみないと分からないだろう。 +静的型付きでかつ関数型という Scala のコレクションの特徴は、可能なエラーの圧倒的多数はコンパイル時に捕捉されることを意味する。 +その理由は、 + +
        +
      1. コレクションの演算はが大量に使用されているため、十分にテスト済みである。
      2. +
      3. コレクション演算を使うことで、インプットとアウトプットが関数のパラメータと結果という形で明示的になる。
      4. +
      5. これらの明示的なインプットとアウトプットは静的な型チェックの対象だ。
      6. +
      + +結論としては、誤用の大多数が型エラーとして表出するということだ。数百行のプログラムが初回の一発で実行できることは決して稀ではない。 + +**速い:** コレクション演算はライブラリの中で調整され最適化されている。その結果、コレクションを使用することは一般的にかなり効率的だ。手作業で丁寧に調整されたデータ構造と演算により多少高速化することができるかもしれないが、不適切な実装上の決断を途中でしてしまうとかなり遅くなってしまうということもありえる。さらに、現在コレクションはマルチコア上での並列実行に適応されている途中だ。並列コレクションは順次コレクションと同じ演算をサポートするため、新しい演算を習ったりコードを書き変えたりする必要はない。`par` メソッドを呼ぶだけで順次コレクションを並列に変えることができる。 + +**統一性がある:** コレクションは出来る限りどの型にも同じ演算を提供している。これにより、少ないボキャブラリの演算でも多くのことができる。例えば、文字列は概念的には文字の列 (sequence) だ。その結果、Scalaのコレクションでは、文字列は列の演算の全てをサポートする。配列に関しても同様だ。 + +**用例:** これは Scala のコレクションの多くの利点を示す一行コードだ。 + + val (minors, adults) = people partition (_.age < 18) + +この演算が何をしているかは一目瞭然だ: `people` のコレクションを年齢によって `minors` と `adult` に分割している。`partition` メソッドはコレクションの基底型である `TraversableLike` で定義されているため、このコードは配列を含むどのコレクションでも動作する。これによって生じる `minors` と `adult` のコレクションは、`people`コレクションと同じ型となる。 + +このコードは、従来の 1〜3個のループを用いたコレクション処理に比べて、より簡潔なものになっている (中間結果をどこかにバッファリングする必要があるため、配列を用いた場合はループ3個)。基本的なコレクションのボキャブラリを覚えてしまえば、明示的なループを書くよりも、このようなコードを書く方が簡単かつ安全だと思うようになるだろう。さらに、`partition` 演算は高速であり、将来的にはマルチコア上で並列コレクションにかけると更に速くなるだろう。(並列コレクションは開発ビルドに入っており、Scala 2.9 の一部としてリリースされる予定。) + +このガイドはユーザーの視点から Scala 2.8 のコレクションクラスの API について詳細に説明する。全ての基本的なクラスと、それらのメソッドをみていこう。 diff --git a/_ja/overviews/collections/iterators.md b/_ja/overviews/collections/iterators.md new file mode 100644 index 0000000000..4c2b700761 --- /dev/null +++ b/_ja/overviews/collections/iterators.md @@ -0,0 +1,180 @@ +--- +layout: multipage-overview +title: イテレータ + +discourse: false + +partof: collections +overview-name: Collections + +num: 15 + +language: ja +--- + +イテレータ ([`Iterator`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Iterator.html)) はコレクションではなく、コレクションから要素を1つづつアクセスするための方法だ。イテレータ `it` に対する基本的な演算として `next` と `hasNext` の2つがある。 `it.next()` を呼び出すことで、次の要素が返り、イテレータの内部状態が前進する。よって、同じイテレータに対して `next` を再び呼び出すと、前回返したものの次の要素が得られる。返す要素が無くなると、`next` の呼び出しは `NoSuchElementException` を発生させる。返す要素が残っているかは [`Iterator`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Iterator.html) の `hasNext` メソッドを使って調べることができる。 + +イテレータ `it` が返す全ての要素を渡り歩くのに最も率直な方法は while ループを使うことだ: + + while (it.hasNext) + println(it.next()) + +`Traversable`、`Iterable`、および `Seq` クラスのほとんどのメソッドに類似するものを Scala のイテレータは提供している。たとえば、与えられた手順をイテレータが返す全ての要素に対して実行する `foreach` メソッドを提供する。 `foreach` を使うことで、先ほどのループは以下のように短縮できる: + + it foreach println + +例にならって、`foreach`、`map`、`withFilter`、および `flatMap` の代替構文として for 式を使うことができるので、イテレータが返す全ての要素を表示するもう一つの方法として以下のように書ける: + + for (elem <- it) println(elem) + +イテレータの `foreach` メソッドと traversable の同メソッドには重大な違いがある。イテレータのそれを呼び出した場合、`foreach` はイテレータを終端に置いたままで終了するということだ。そのため、`next` を再び呼び出すと `NoSuchElementException` を発生して失敗する。それに比べ、コレクションに対して呼び出した場合、`foreach` +はコレクション内の要素の数を変更しない (渡された関数が要素を追加もしくは削除した場合は別の話だが、これは予想外の結果になることがあるので非推奨だ)。 + +`Iterator` と `Traversable` に共通の他の演算も同じ特性を持つ。例えば、イテレータは新たなイテレータを返す `map` メソッドを提供する: + + scala> val it = Iterator("a", "number", "of", "words") + it: Iterator[java.lang.String] = non-empty iterator + scala> it.map(_.length) + res1: Iterator[Int] = non-empty iterator + scala> res1 foreach println + 1 + 6 + 2 + 5 + scala> it.next() + java.util.NoSuchElementException: next on empty iterator + +上記の通り、`it.map` の呼び出しの後、イテレータ `it` は終端まで前進してしまっている。 + +次の具体例は、ある特性をもつイテレータ内の最初の要素を検索するのに使うことができる `dropWhile` だ。例えば、イテレータ内で二文字以上の最初の語句を検索するのに、このように書くことができる: + + scala> val it = Iterator("a", "number", "of", "words") + it: Iterator[java.lang.String] = non-empty iterator + scala> it dropWhile (_.length < 2) + res4: Iterator[java.lang.String] = non-empty iterator + scala> it.next() + res5: java.lang.String = number + +`dropWhile` を呼び出すことで `it` が変更された事に注意してほしい。イテレータは二番目の語句「number」を指している。実際に、`it` と `dropWhile` の返した戻り値である `res4` 同じ要素の列を返す。 + +同じイテレータを再利用するための標準演算が一つだけある。以下の + + val (it1, it2) = it.duplicate + +への呼び出しはイテレータ `it` と全く同じ要素を返すイテレータを**2つ**返す。この2つのイテレータは独立して作動するため、片方を前進しても他方は影響を受けない。一方、元のイテレータ `it` は `duplicate` により終端まで前進したため、使いものにならない。 + +要約すると、イテレータは**メソッドを呼び出した後、絶対にアクセスしなければ**コレクションのように振る舞う。Scala +コレクションライブラリは、[`Traversable`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Traversable.html) と [`Iterator`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Iterator.html) に共通の親クラスである [`TraversableOnce`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/TraversableOnce.html) を提供することで、明示的にこれを示す。名前が示す通り、 `TraversableOnce` は `foreach` を用いて一度だけ探索することができるが、探索後のそのオブジェクトの状態は指定されていない。`TraversableOnce` オブジェクトが `Iterator` ならば、探索後はその終端にあるし、もし `Traversable` ならば、そのオブジェクトは今まで通り存在する。 `TraversableOnce` のよく使われる事例としては、イテレータか `Traversable` を受け取ることができるメソッドの引数の型だ。その例として、 `Traversable` クラスの追加メソッド `++` がある。`TraversableOnce` パラメータを受け取るため、イテレータか `Traversable` なコレクションの要素を追加することができる。 + +イテレータの全演算は次の表にまとめられている。 + +### Iterator クラスの演算 + +| 使用例 | 振る舞い| +| ------ | ------ | +| **抽象メソッド:** | | +| `it.next()` | イテレータの次の要素を返し、前進させる。 | +| `it.hasNext` | `it` が次の要素を返せる場合、`true` を返す。 | +| **他のイテレータ:** | | +| `it.buffered` | `it` が返す全ての要素を返すバッファ付きイテレータ。 | +| `it grouped size` | `it` が返す要素を固定サイズの「かたまり」にして返すイテレータ。 | +| `xs sliding size` | `it` が返す要素を固定サイズの「窓」をスライドさせて返すイテレータ。 | +| **複製:** | | +| `it.duplicate` | `it` が返す全ての要素を独立して返すイテレータのペア。 | +| **加算:** | | +| `it ++ jt` | イテレータ `it` が返す全ての要素に続いてイテレータ `jt` の全ての要素を返すイテレータ。 | +| `it padTo (len, x)` | 全体で `len`個の要素が返るように、イテレータ `it` の全ての要素に続いて `x` を返すイテレータ。 | +| **map 演算:** | | +| `it map f` | `it` が返す全ての要素に関数 `f` を適用することによって得られるイテレータ。 | +| `it flatMap f` | `it` が返す全ての要素に対してイテレータ値を返す関数 `f` を適用し、その結果を連結したイテレータ。 | +| `it collect f` | `it` が返す全ての要素に対して部分関数 `f` が定義されている場合のみ適用し、その結果を集めたイテレータ。 | +| **変換演算:** | | +| `it.toArray` | `it` が返す要素を配列に集める。 | +| `it.toList` | `it` が返す要素をリストに集める。 | +| `it.toIterable` | `it` が返す要素を iterable に集める。 | +| `it.toSeq` | `it` が返す要素を列に集める。 | +| `it.toIndexedSeq` | `it` が返す要素を添字付き列に集める。 | +| `it.toStream` | `it` が返す要素をストリームに集める。 | +| `it.toSet` | `it` が返す要素を集合に集める。 | +| `it.toMap` | `it` が返すキー/値ペアをマップに集める。 | +| **コピー演算:** | | +| `it copyToBuffer buf` | `it` が返す要素をバッファ `buf` にコピーする。 | +| `it copyToArray(arr, s, n)`| `it` が返す最大 `n` 個の要素を配列 `arr` の添字 `s` より始まる位置にコピーする。最後の2つの引数は省略可能だ。 | +| **サイズ演算:** | | +| `it.isEmpty` | イテレータが空であるかどうかを調べる (`hasNext` の逆)。 | +| `it.nonEmpty` | イテレータに要素が含まれているかを調べる (`hasNext` の別名)。 | +| `it.size` | `it` が返す要素の数。注意: この演算の後、`it` は終端まで前進する! | +| `it.length` | `it.size` に同じ。 | +| `it.hasDefiniteSize` | `it` が有限数の要素を返すことが明らかな場合 true を返す (デフォルトでは `isEmpty` に同じ)。 | +| **要素取得演算・添字検索演算:**| | +| `it find p` | `it` が返す要素の中で条件関数 `p` を満たす最初の要素のオプション値、または条件を満たす要素が無い場合 `None`。注意: イテレータは探しだされた要素の次の要素、それが無い場合は終端まで前進する。 | +| `it indexOf x` | `it` が返す要素の中で `x` と等しい最初の要素の添字。注意: イテレータはこの要素の次の位置まで前進する。 | +| `it indexWhere p` | `it` が返す要素の中で条件関数 `p` を満たす最初の要素の添字、注意: イテレータはこの要素の次の位置まで前進する。 | +| **部分イテレータ演算:** | | +| `it take n` | `it` が返す最初の `n`個の要素を返すイテレータ。注意: `it` は、`n`個目の要素の次の位置、または`n`個以下の要素を含む場合は終端まで前進する。 | +| `it drop n` | `it` の `(n+1)`番目の要素から始まるイテレータ。注意: `it` も同じ位置まで前進する。| +| `it slice (m,n)` | `it` が返す要素の内、`m`番目から始まり `n`番目の一つ前で終わる切片を返すイテレータ。 | +| `it takeWhile p` | `it` が返す要素を最初から次々とみて、条件関数 `p` を満たす限り返していったイテレータ。 | +| `it dropWhile p` | `it` が返す要素を最初から次々とみて、条件関数 `p` を満たす限り飛ばしていき、残りを返すイテレータ。 | +| `it filter p` | `it` が返すの要素で条件関数 `p` を満たすものを返すイテレータ。 | +| `it withFilter p` | `it filter p` に同じ。イテレータが for 式で使えるように用意されている。 | +| `it filterNot p` |`it` が返すの要素で条件関数 `p` を満たさないものを返すイテレータ。 | +| **分割演算:** | | +| `it partition p` | `it` を2つのイテレータから成るペアに分割する。片方のイテレータは `it` が返す要素のうち条件関数 `p` を満たすものを返し、もう一方は `it` が返す要素のうち `p` を満たさないものを返す。 | +| **要素条件演算:** | | +| `it forall p` | `it` が返す全ての要素に条件関数 `p` が当てはまるかを示す boolean 値。 | +| `it exists p` | `it` が返す要素の中に条件関数 `p` を満たすものがあるかどうかを示す boolean 値。 | +| `it count p` | `it` が返す要素の中にで条件関数 `p` 満たすものの数。 | +| **fold 演算:** | | +| `(z /: it)(op)` | `z` から始めて、左から右へと `it` が返す隣接する要素に二項演算 `op` を次々と適用したもの。 | +| `(it :\ z)(op)` | `z` から始めて、右から左へと `it` が返す隣接する要素に二項演算 `op` を次々と適用したもの。 | +| `it.foldLeft(z)(op)` | `(z /: it)(op)` に同じ。 | +| `it.foldRight(z)(op)` | `(it :\ z)(op)` に同じ。 | +| `it reduceLeft op` | 左から右へと、空ではないイテレータ `it` が返す隣接する要素に二項演算 `op` を次々と適用したもの。 | +| `it reduceRight op` | 右から左へと、空ではないイテレータ `it` が返す隣接する要素に二項演算 `op` を次々と適用したもの。 | +| **特定 fold 演算:** | | +| `it.sum` | イテレータ `it` が返す数値要素の値の和。 | +| `it.product` | イテレータ `it` が返す数値要素の値の積。 | +| `it.min` | イテレータ `it` が返す順序付けされたの値の最小値。 | +| `it.max` | イテレータ `it` が返す順序付けされたの値の最大値。 | +| **zip 演算:** | | +| `it zip jt` | イテレータ `it` と `jt` が返す要素から対応したものペアにして返すイテレータ。 | +| `it zipAll (jt, x, y)` | イテレータ `it` と `jt` が返す要素から対応したものペアにして返すイテレータで、もし片方が短い場合は `x` か `y` を使って長いほうに合わせる。 | +| `it.zipWithIndex` | `it` が返す要素とその添字をペアにしたイテレータ。 | +| **更新演算:** | | +| `it patch (i, jt, r)` | `it` の、`i` から始まる `r`個の要素をパッチイテレータ `ji` が返す要素に置換したイテレータ。 | +| **比較演算:** | | +| `it sameElements jt` | イテレータ `it` と `jt` が同じ要素を同じ順序で返すかを調べる。注意: この演算の後、`it` の `jt` 少なくともどちらか一方は終端まで前進している。 | +| **文字列演算:** | | +| `it addString (b, start, sep, end)`| `it` が返す要素を `sep` で区切った後、`start` と `end` で挟んだ文字列を `StringBuilder` `b` に追加する。 `start`、`sep`、`end` は全て省略可能。 | +| `it mkString (start, sep, end)` | `it` が返す要素を `sep` で区切った後、`start` と `end` で挟んだ文字列に変換する。 `start`、`sep`、`end` は全て省略可能。 | + +### バッファ付きイテレータ + +イテレータを前進させずに次に返る要素を検査できるような「先読み」できるイテレータが必要になることがたまにある。例えば、一連の文字列を返すイテレータがあるとして、その最初の空白文字列を飛ばすという作業を考える。以下のように書こうと思うかもしれない。 + + def skipEmptyWordsNOT(it: Iterator[String]) = + while (it.next().isEmpty) {} + +しかし、このコードを慎重に見ると間違っていることが分かるはずだ。コードは確かに先頭の空白文字列の続きを読み飛ばすが、`it` は最初の非空白文字列も追い越してしまっているのだ。 + +この問題はバッファ付きイテレータを使うことで解決できる。[`BufferedIterator`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/BufferedIterator.html) トレイトは、`head` というメソッドを追加で提供する [`Iterator`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Iterator.html) の子トレイトだ。バッファ付きイテレータに対して `head` を呼び出すことで、イテレータを前進させずに最初の要素を返すことができる。バッファ付きイテレータを使うと、空白文字列を読み飛ばすのは以下のように書ける。 + + def skipEmptyWords(it: BufferedIterator[String]) = + while (it.head.isEmpty) { it.next() } + +`buffered` メソッドを呼ぶことで全てのイテレータはバッファ付きイテレータに変換できる。次に具体例で説明する: + + scala> val it = Iterator(1, 2, 3, 4) + it: Iterator[Int] = non-empty iterator + scala> val bit = it.buffered + bit: java.lang.Object with scala.collection. + BufferedIterator[Int] = non-empty iterator + scala> bit.head + res10: Int = 1 + scala> bit.next() + res11: Int = 1 + scala> bit.next() + res11: Int = 2 + +バッファ付きイテレータに対して `head` を呼び出してもイテレータ `bit` は前進しないことに注意してほしい。よって、後続の `bit.next()` の呼び出しは `bit.head` と同じ値を返す。 diff --git a/_ja/overviews/collections/maps.md b/_ja/overviews/collections/maps.md new file mode 100644 index 0000000000..a3af008987 --- /dev/null +++ b/_ja/overviews/collections/maps.md @@ -0,0 +1,165 @@ +--- +layout: multipage-overview +title: マップ + +discourse: false + +partof: collections +overview-name: Collections + +num: 7 + +language: ja +--- + +マップ ([`Map`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Map.html)) はキーと値により構成されるペアの [`Iterable`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Iterable.html) の一種で、**写像** (mapping) や**関連** (association) とも呼ばれる。Scala の [`Predef`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/Predef$.html) クラスは、ペアの `(key, value)` を `key -> value` と書けるような暗黙の変換を提供する。例えば、`Map("x" -> 24, "y" -> 25, "z" -> 26)` は `Map(("x", 24), ("y", 25), ("z", 26))` と全く同じことを意味するがより可読性が高い。 + +マップの基本的な演算は集合のものと似ている。それらは、以下の表にまとめられており、以下のカテゴリーに分類できる: + +* **検索演算** には `apply`、`get`、`getOrElse`、`contains`、および `isDefinedAt` がある。これらはマップをキーから値への部分関数に変える。マップの最も基本的な検索メソッドは `def get(key): Option[Value]` だ。"`m get key`" という演算はマップが `key` に関連する値があるかを調べる。もしあれば、マップはその関連する値を `Some` に包んで返す。`key` がマップ中に定義されていなければ `get` は `None` を返す。マップはまた、任意のキーに関連する値を `Option` に包まずに直接返す `apply` メソッドも定義する。マップにキーが定義されていない場合は、例外が発生する。 +* **加算と更新演算**である `+`、`++`、`updated` は、マップに新しい対応関係を追加するか、既存の対応関係を更新する。 +
    • 減算である --- は、対応関係をマップから削除する。
    • +* **サブコレクション取得演算**である `keys`、`keySet`、`keysIterator`、`values`、`valuesIterator` は、マップのキーや値を様々な形で別に返す。 +* **変換演算**である `filterKeys` と `mapValues` は、既存のマップの対応関係をフィルターしたり変換することで新たなマップを生成する。 + +### Map トレイトの演算 ### + +| 使用例 | 振る舞い| +| ------ | ------ | +| **検索演算:** | | +| `ms get k` |マップ `ms` 内のキー `k` に関連付けられた値のオプション値、もしくは、キーが見つからない場合、`None`。| +| `ms(k)` |(展開した場合、`ms apply k`) マップ `ms` 内のキー `k` に関連付けられた値、もしくは、キーが見つからない場合は例外。| +| `ms getOrElse (k, d)` |マップ `ms` 内のキー `k` に関連付けられた値、もしくは、キーが見つからない場合、デフォルト値 `d`。| +| `ms contains k` |`ms` がキー `k` への写像を含むかを調べる。| +| `ms isDefinedAt k` |`contains` に同じ。 | +| **加算と更新演算:**| | +| `ms + (k -> v)` |`ms` 内の全ての写像と、キー `k` から値 `v` への写像 `k -> v` を含むマップ。| +| `ms + (k -> v, l -> w)` |`ms` 内の全ての写像と、渡されたキーと値のペアを含むマップ。| +| `ms ++ kvs` |`ms` 内の全ての写像と、`kvs`内の全てのキーと値のペアを含むマップ。| +| `ms updated (k, v)` |`ms + (k -> v)` に同じ。| +| **減算:** | | +| `ms - k` |キー `k` からの写像を除く、`ms` 内の全ての写像。| +| `ms - (k, 1, m)` |渡されたキーからの写像を除く、`ms` 内の全ての写像。| +| `ms -- ks` |`ks`内のキーからの写像を除く、`ms` 内の全ての写像。| +| **サブコレクション取得演算:** | | +| `ms.keys` |`ms`内の全てのキーを含む iterable。| +| `ms.keySet` |`ms`内の全てのキーを含む集合。| +| `ms.keyIterator` |`ms`内の全てのキーを返すイテレータ。| +| `ms.values` |`ms`内のキーに関連付けられた全ての値を含む iterable。| +| `ms.valuesIterator` |`ms`内のキーに関連付けられた全ての値を返すイテレータ。| +| **変換演算:** | | +| `ms filterKeys p` |キーが条件関数 `p` を満たす `ms`内の写像のみを含むマップのビュー。| +| `ms mapValues f` |`ms`内のキーに関連付けられた全ての値に関数 `f` を適用して得られるマップのビュー。| + +可変マップ ([`mutable.Map`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/Map.html)) は他にも以下の表にまとめた演算をサポートする。 + +### mutable.Map トレイトの演算 ### + +| 使用例 | 振る舞い| +| ------ | ------ | +| **加算と更新演算:** | | +| `ms(k) = v` |(展開した場合、`ms.update(x, v)`)。マップ `ms` に副作用としてキー `k` から値 `v` への写像を加え、既に `k` からの写像がある場合は上書きする。| +| `ms += (k -> v)` |マップ `ms` に副作用としてキー `k` から値 `v` への写像を加え、`ms`自身を返す。| +| `ms += (k -> v, l -> w)` |マップ `ms` に副作用として渡された写像を加え、`ms`自身を返す。| +| `ms ++= kvs` |マップ `ms` に副作用として `kvs`内の全ての写像を加え、`ms`自身を返す。| +| `ms put (k, v)` |マップ `ms` にキー `k` から値 `v` への写像を加え、以前の `k` からの写像のオプション値を返す。| +| `ms getOrElseUpdate (k, d)`|マップ `ms`内にキー `k` が定義されている場合は、関連付けられた値を返す。定義されていない場合は、`ms` に写像 `k -> d` を加え、`d` を返す。| +| **減算:**| | +| `ms -= k` |マップ `ms` から副作用としてキー `k` からの写像を削除して、`ms`自身を返す。| +| `ms -= (k, l, m)` |マップ `ms` から副作用として渡されたキーからの写像を削除して、`ms`自身を返す。| +| `ms --= ks` |マップ `ms` から副作用として `ks`内の全てのキーからの写像を削除して、`ms`自身を返す。| +| `ms remove k` |マップ `ms` からキー `k` からの写像を削除して、以前の `k` からの写像のオプション値を返す。| +| `ms retain p` |`ms`内の写像でキーが条件関数 `p` を満たすものだけを残す。| +| `ms.clear()` |`ms` から全ての写像を削除する。| +| **変換演算:** | | +| `ms transform f` |マップ `ms`内の全ての関連付けされた値を関数 `f` を使って変換する。| +| **クローン演算:** | | +| `ms.clone` |`ms` と同じ写像を持つ新しい可変マップを返す。| + +マップの加算と減算は、集合のそれにならう。集合と同様、非破壊的な演算である `+`、`-`、と `updated` を提供するが、加算マップをコピーする必要があるため、これらはあまり使われることがない。そのかわり、可変マップは通常 `m(key) = value` か `m += (key -> value)` という2種類の更新演算を使って上書き更新される。さらに前に `key` から関連付けされていた値を +`Option`値で返すか、マップに `key` が無ければ `None` を返すというバリアントである `m put (key, value)` もある。 + +`getOrElseUpdate` はキャッシュとして振る舞うマップにアクセスするのに役立つ。例えば、関数 `f` により呼び出される時間のかかる計算があるとする: + + scala> def f(x: String) = { + println("taking my time."); sleep(100) + x.reverse } + f: (x: String)String + +さらに、`f` には副作用を伴わず、同じ引数で何回呼び出しても同じ戻り値が返ってくると仮定する。この場合、引数と以前の `f` 計算結果の対応関係をマップに格納して、引数がマップに無いときだけ `f` の結果を計算すれば時間を節約できる。この時、マップは関数 `f` の計算の**キャッシュ**であると言える。 + + val cache = collection.mutable.Map[String, String]() + cache: scala.collection.mutable.Map[String,String] = Map() + +これにより、より効率的な、キャッシュするバージョンの関数 `f` を作成することができる: + + scala> def cachedF(s: String) = cache.getOrElseUpdate(s, f(s)) + cachedF: (s: String)String + scala> cachedF("abc") + taking my time. + res3: String = cba + scala> cachedF("abc") + res4: String = cba + +`getOrElseUpdate` の第二引数は「名前渡し」(by-name) であるため、上の `f("abc")` は `getOrElseUpdate` が必要とする場合、つまり第一引数が `cache` に無い場合においてのみ計算されることに注意してほしい。 `cachedF` をより率直に、普通の map 演算を用いて実装することもできるが、コードは少し長くなる: + + def cachedF(arg: String) = cache get arg match { + case Some(result) => result + case None => + val result = f(x) + cache(arg) = result + result + } + +### 同期集合と同期マップ ### + +`SynchronizedMap` トレイトを好みのマップ実装にミックスインすることでスレッドセーフな可変マップを得ることができる。例えば、以下のコードが示すように、`HashMap` に `SynchronizedMap` をミックスインすることができる。この例は `Map` と `SynchronizedMap` の二つのトレイト、そして `HashMap` という一つのクラスを `scala.collection.mutable` パッケージからインポートすることから始まる。例の残りは `makeMap` というメソッドを宣言するシングルトンオブジェクト `MapMaker` を定義する。`makeMap` メソッドは戻り値の型を文字列をキーとして文字列を値とする可変マップだと宣言する。 + + import scala.collection.mutable.{Map, + SynchronizedMap, HashMap} + object MapMaker { + def makeMap: Map[String, String] = { + new HashMap[String, String] with + SynchronizedMap[String, String] { + override def default(key: String) = + "Why do you want to know?" + } + } + } + +`makeMap` 本文の第1ステートメントは `SynchronizedMap` トレイトをミックスインする新しい可変 `HashMap` を作成する: + + new HashMap[String, String] with + SynchronizedMap[String, String] + +このコードを与えられると、Scala コンパイラは `SynchronizedMap` をミックスインする `HashMap` の合成的な子クラスを生成し、そのインスタンスを作成する (そして、それを戻り値として返す)。この合成クラスは、以下のコードのため、`default` という名前のメソッドをオーバーライドする。: + + override def default(key: String) = + "何故知りたい?" + +通常は、ある特定のキーに対する値をマップに問い合わせて、そのキーからの写像が無い場合は`NoSuchElementException` が発生する。新たなマップのクラスを定義して `default` メソッドをオーバーライドした場合は、しかしながら、存在しないキーに対する問い合わせに対して、この新しいマップは `default` が返す値を返す。そのため、同期マップのコードでコンパイラに生成された `HashMap` の合成の子クラスは、存在しないキーに対する問い合わせに `"何故知りたい?"` と少々意地の悪い答えを返す。 + +`makeMap` メソッドが返す可変マップは `SynchronizedMap` トレイトをミックスインするため、複数のスレッドから同時に使うことができる。マップへのそれぞれのアクセスは同期化される。以下は、インタープリタ中で一つのスレッドから使用した例だ: + + scala> val capital = MapMaker.makeMap + capital: scala.collection.mutable.Map[String,String] = Map() + scala> capital ++ List("US" -> "Washington", + "France" -> "Paris", "Japan" -> "Tokyo") + res0: scala.collection.mutable.Map[String,String] = + Map(France -> Paris, US -> Washington, Japan -> Tokyo) + scala> capital("Japan") + res1: String = Tokyo + scala> capital("New Zealand") + res2: String = Why do you want to know? + scala> capital += ("New Zealand" -> "Wellington") + scala> capital("New Zealand") + res3: String = Wellington + +同期集合も同期マップと同じ要領で作成することができる。例えば、このように `SynchronizedSet` トレイトをミックスインすることで同期 `HashSet` を作ることができる: + + import scala.collection.mutable + val synchroSet = + new mutable.HashSet[Int] with + mutable.SynchronizedSet[Int] + +最後に、同期コレクションを使うことを考えているならば、`java.util.concurrent` の並行コレクションを使うことも考慮した方がいいだろう。 diff --git a/_ja/overviews/collections/migrating-from-scala-27.md b/_ja/overviews/collections/migrating-from-scala-27.md new file mode 100644 index 0000000000..f8464de499 --- /dev/null +++ b/_ja/overviews/collections/migrating-from-scala-27.md @@ -0,0 +1,46 @@ +--- +layout: multipage-overview +title: Scala 2.7 からの移行 + +discourse: false + +partof: collections +overview-name: Collections + +num: 18 + +language: ja +--- + +既存の Scala アプリケーションの新しいコレクションへの移植はほぼ自動的であるはずだ。問題となり得ることはいくつかしかない。 + +一般的論として、Scala 2.7 コレクションの古い機能はそのまま残っているはずだ。機能の中には廃止予定となったものもあり、それは今後のリリースで撤廃されるということだ。Scala 2.8 でそのような機能を使ったコードをコンパイルすると**廃止予定警告** (deprecation warning) が発生する。その意味や性能特性を変えて 2.8 に残った演算もあり、その場合は廃止予定にするのは無理だった。このような場合は 2.8 でコンパイルすると**移行警告** (migration warning) が出される。コードをどう変えればいいのかも提案してくれる完全な廃止予定警告と移行警告を得るには、`-deprecation` と `-Xmigration` フラグを `scalac` に渡す (`-Xmigration` は `X` で始まるため、拡張オプションであることに注意)。同じオプションを `scala` REPL に渡すことで対話セッション上で警告を得ることができる。具体例としては: + + >scala -deprecation -Xmigration + Welcome to Scala version 2.8.0.final + Type in expressions to have them evaluated. + Type :help for more information. + scala> val xs = List((1, 2), (3, 4)) + xs: List[(Int, Int)] = List((1,2), (3,4)) + scala> List.unzip(xs) + :7: warning: method unzip in object List is deprecated: use xs.unzip instead of List.unzip(xs) + List.unzip(xs) + ^ + res0: (List[Int], List[Int]) = (List(1, 3),List(2, 4)) + scala> xs.unzip + res1: (List[Int], List[Int]) = (List(1, 3),List(2, 4)) + scala> val m = xs.toMap + m: scala.collection.immutable.Map[Int,Int] = Map((1,2), (3,4)) + scala> m.keys + :8: warning: method keys in trait MapLike has changed semantics: + As of 2.8, keys returns Iterable[A] rather than Iterator[A]. + m.keys + ^ + res2: Iterable[Int] = Set(1, 3) + +旧ライブラリより完全に置き換えられ、廃止予定警告を出すのが無理だったものが 2つある。 + +1. 以前の `scala.collection.jcl` パッケージは撤廃された。 このパッケージは Scala 上で Java コレクションライブラリの設計を真似しようとしたが、それは多くの対称性を壊してしまった。Java コレクションが欲しい人の多くは `jcl` を飛ばして `java.util` を直接使用していた。Scala 2.8 は、`jcl` パッケージの代わりに、[`JavaConversions`](conversions-between-java-and-scala-collections.html) オブジェクトにて両方のライブラリ間の自動変換機構を提供する。 +2. 投射 (projection) は一般化され、きれいにされ、現在はビューとして提供される。投射はほとんど使われていなかったようなので、この変更に影響を受けるコードは少ないはずだ。 + +よって、`jcl` か投射を使っている場合は多少コードの書き換えが必要になるかもしれない。 diff --git a/_ja/overviews/collections/overview.md b/_ja/overviews/collections/overview.md new file mode 100644 index 0000000000..0261e1d9a4 --- /dev/null +++ b/_ja/overviews/collections/overview.md @@ -0,0 +1,104 @@ +--- +layout: multipage-overview +title: 可変コレクションおよび不変コレクション + +discourse: false + +partof: collections +overview-name: Collections + +num: 2 + +language: ja +--- + +Scala のコレクションは、体系的に可変および不変コレクションを区別している。**可変** (mutable) コレクションは上書きしたり拡張することができる。これは副作用としてコレクションの要素を変更、追加、または削除することができることを意味する。一方、**不変** (immutable) コレクションは変わることが無い。追加、削除、または更新を模倣した演算は提供されるが、全ての場合において演算は新しいコレクションを返し、古いコレクションは変わることがない。 + +コレクションクラスの全ては `scala.collection` パッケージもしくは `mutable`、`immutable`、`generic` のどれかのサブパッケージに定義されている。クライアントコードに必要なコレクションのクラスのほどんどには可変性に関して異なる特性を持つ 3つの形態が定義されおり、ぞれぞれ `scala.collection`、`scala.collection.immutable`、か `scala.collection.mutable` のパッケージに存在する。 + +`scala.collection.immutable` パッケージのコレクションは、誰にとっても不変であることが保証されている。 +そのようなコレクションは作成後には一切変更されることがない。したがって、異なる時点で何回同じコレクションの値にアクセスしても常に同じ要素を持つコレクションが得られることに依存できる。 + +`scala.collection.mutable` パッケージのコレクションは、コレクションを上書き変更する演算がある。 +だから可変コレクションを扱うということは、どのコードが、何時どのコレクションを変更したのかということを理解する必要があることを意味する。 + +`scala.collection` パッケージのコレクションは、可変か不変かのどちらでもありうる。例えば [`collection.IndexedSeq[T]`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/IndexedSeq.html) +は、[`collection.immutable.IndexedSeq[T]`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/IndexedSeq.html) と [`collection.mutable.IndexedSeq[T]`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/IndexedSeq.html) 両方の親クラスだ。一般的に、`scala.collection`パッケージの基底コレクションは不変コレクションと同じインターフェイスを定義し、`scala.collection.mutable` パッケージ内の可変コレクションは、副作用を伴う変更演算を不変インターフェイスに加える。 + +基底コレクションと不変コレクションの違いは、不変なコレクションのクライアントは、他の誰もコレクションを変更しないという保証があるのに対し、基底コレクションのクライアントは自分ではコレクションを変更しなかったという約束しかできない。たとえ静的な型がコレクションを変更するような演算を提供していなくても、実行時の型は他のクライアントが手を加えることができる可変コレクションである可能性がある。 + +デフォルトでは Scala は常に不変コレクションを選ぶ。たとえば、`scala` パッケージのデフォルトのバインディングにより、なんの接頭辞や import もなくただ `Set` と書くと不変な集合 (set) が返ってき、`Iterable` と書くと不変で反復可能 (iterable)なコレクションが返ってくる。可変なデフォルト実装を取得するには、`collection.mutable.Set` +または `collection.mutable.Iterable` と明示的に記述する必要がある。 + +可変と不変の両方のバージョンのコレクションを使用する場合に便利な慣例は `collection.mutable` パッケージだけをインポートすることだ。 + + import scala.collection.mutable + +これにより、接頭辞なしの `Set` は不変なコレクションを参照するのに対し、`mutable.Set` は可変版を参照する。 + +コレクション階層内の最後のパッケージは `collection.generic` だ。 +このパッケージには、コレクションを実装するための基本的なパーツが含まれている。 +コレクションクラスがいくつかの演算を `generic` 内のクラスに委譲することはよくあるが、 フレームワークのユーザーが `generic` 内のクラスが必要になることは普通はありえない。 + +利便性と後方互換性のために、いつくかの重要な型は `scala` パッケージ内に別名を定義してあるため、インポート無しで単純な名前でコレクションを使うことができる。[`List`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/List.html) 型が良い例で、以下の名前でもアクセスすることができる + + scala.collection.immutable.List // 定義元 + scala.List // scala パッケージのエイリアス経由 + List // scala._ パッケージは + // 常に自動的にインポートされるため + +エイリアスされているその他の型は次のとおり: +[`Traversable`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Traversable.html)、[`Iterable`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Iterable.html)、[`Seq`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Seq.html)、[`IndexedSeq`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/IndexedSeq.html)、[`Iterator`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Iterator.html)、[`Stream`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Stream.html)、[`Vector`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Vector.html)、[`StringBuilder`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/StringBuilder.html)、[`Range`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Range.html)。 + +次の図は `scala.collection` パッケージ内の全てのコレクションを示す。 +これらはすべて、高レベルの抽象クラスやトレイトで一般に可変と不変の両方の実装を持っている。 + +[]({{ site.baseurl }}/resources/images/collections.png) + +次の図は `scala.collection.immutable` パッケージ内の全てのコレクションを示す。 + +[]({{ site.baseurl }}/resources/images/collections.immutable.png) + +そして、次の図は `scala.collection.mutable` パッケージ内の全てのコレクションを示す。 + +[]({{ site.baseurl }}/resources/images/collections.mutable.png) + +(以上三つ全ての図は decodified.com の Matthias によって生成された。) + +## コレクションAPIの概要 + +最も重要なコレクションクラスは上の図に示されている。 +これらの全てのクラスに共通な部分が沢山ある。 +例えば、全てのコレクションは、クラス名を書いた後で要素を書くという統一された構文で作成することができる: + + Traversable(1, 2, 3) + Iterable("x", "y", "z") + Map("x" -> 24, "y" -> 25, "z" -> 26) + Set(Color.red, Color.green, Color.blue) + SortedSet("hello", "world") + Buffer(x, y, z) + IndexedSeq(1.0, 2.0) + LinearSeq(a, b, c) + +特定のコレクションの実装にもこの原則が適用される: + + List(1, 2, 3) + HashMap("x" -> 24, "y" -> 25, "z" -> 26) + +これらのコレクションは、`toString` を呼び出すと上の表記方法で表示される。 + +すべてのコレクションが `Traversable` によって提供される API をサポートするが、理にかなうところでは型を特殊化している。 +たとえば、`Traversable` クラスの `map` メソッドは別の `Traversable` を戻り値として返すが、結果の型はサブクラスでオーバーライドされる。 +たとえば、`List` が `map` を呼び出しても再び `List` が返ってき、`Set` が `map` を呼び出すと `Set` が返ってくる、といういう具合だ。 + + scala> List(1, 2, 3) map (_ + 1) + res0: List[Int] = List(2, 3, 4) + scala> Set(1, 2, 3) map (_ * 2) + res0: Set[Int] = Set(2, 4, 6) + +コレクションライブラリ中のあらゆる所で実装されているこの振る舞いは**戻り値同型の原則** と呼ばれる。 + +コレクションの階層のクラスのほとんどは基底、不変、可変の3種類とも存在する。 +唯一の例外は、可変コレクションにのみ存在する `Buffer` トレイトだ。 + +これより、これらのクラスを一つづつ見ていく。 diff --git a/_ja/overviews/collections/performance-characteristics.md b/_ja/overviews/collections/performance-characteristics.md new file mode 100644 index 0000000000..d8fecb85ba --- /dev/null +++ b/_ja/overviews/collections/performance-characteristics.md @@ -0,0 +1,87 @@ +--- +layout: multipage-overview +title: 性能特性 + +discourse: false + +partof: collections +overview-name: Collections + +num: 12 + +language: ja +--- + +これまでの説明で異なるコレクションが異なる性能特性 (performance characteristics) を持つことが分かった。性能特性はコレクション型を比較する第一の基準としてよく使われる。以下の 2つの表にコレクションの主な演算の性能特性をまとめた。 + +列型の性能特性: + +| | head | tail | apply | 更新 | 先頭に追加 | 最後に追加 | 挿入 | +| -------- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | +| **不変** | | | | | | | | +| `List` | 定数 | 定数 | 線形 | 線形 | 定数 | 線形 | - | +| `Stream` | 定数 | 定数 | 線形 | 線形 | 定数 | 線形 | - | +| `Vector` |実質定数|実質定数|実質定数|実質定数| 実質定数 | 実質定数 | - | +| `Stack` | 定数 | 定数 | 線形 | 線形 | 定数 | 線形 | 線形 | +| `Queue` |ならし定数|ならし定数|線形| 線形 | 線形 | 定数 | - | +| `Range` | 定数 | 定数 | 定数 | - | - | - | - | +| `String` | 定数 | 線形 | 定数 | 線形 | 線形 | 線形 | - | +| **可変** | | | | | | | | +| `ArrayBuffer` | 定数 | 線形 | 定数 | 定数 | 線形 | ならし定数 | 線形 | +| `ListBuffer` | 定数 | 線形 | 線形 | 線形 | 定数 | 定数 | 線形 | +|`StringBuilder`| 定数 | 線形 | 定数 | 定数 | 線形 | ならし定数 | 線形 | +| `MutableList` | 定数 | 線形 | 線形 | 線形 | 定数 | 定数 | 線形 | +| `Queue` | 定数 | 線形 | 線形 | 線形 | 定数 | 定数 | 線形 | +| `ArraySeq` | 定数 | 線形 | 定数 | 定数 | - | - | - | +| `Stack` | 定数 | 線形 | 線形 | 線形 | 定数 | 線形 | 線形 | +| `ArrayStack` | 定数 | 線形 | 定数 | 定数 | ならし定数| 線形 | 線形 | +| `Array` | 定数 | 線形 | 定数 | 定数 | - | - | - | + +集合とマップ型の性能特性: + +| | 検索 | 追加 | 削除 | 最小 | +| -------- | ---- | ---- | ---- | ---- | +| **不変** | | | | | +| `HashSet`/`HashMap`| 実質定数| 実質定数| 実質定数 | 線形 | +| `TreeSet`/`TreeMap`| Log | Log | Log | Log | +| `BitSet` | 定数 | 線形 | 線形 | 実質定数1| +| `ListMap` | 線形 | 線形 | 線形 | 線形 | +| **可変** | | | | | +| `HashSet`/`HashMap`|実質定数 |実質定数|実質定数| 線形 | +| `WeakHashMap` |実質定数 |実質定数|実質定数| 線形 | +| `BitSet` | 定数 |ならし定数| 定数 |実質定数1| +| `TreeSet` | Log | Log | Log | Log | + +脚注: 1 ビットが密にパックされていることを前提にしている。 + +表の値を以下に説明する: + +| | | +| --- | ---- | +| **定数** | 演算は (高速な) 定数時間で完了する。| +| **実質定数** | 演算は実質定数時間で完了するが、ベクトルの最大長やハッシュキーの分布など何らかの前提に依存する。| +| **ならし定数** | 演算は「ならし定数時間」で完了する。演算の呼び出しの中には定数時間よりも長くかかるものもあるが、多くの演算の実行時間の平均を取った場合定数時間となる。 | +| **Log** | 演算はコレクションのサイズの対数に比例した時間で完了する。 | +| **線形** | 演算は線形時間、つまりコレクションのサイズに比例した時間で完了する。 | +| **-** | 演算はサポートされていない。 | + +最初の表は、不変と可変両方の列型の以下の演算の性能特性をまとめる: + +| | | +| --- | ---- | +| **head** | 列の最初の要素を選択する。 | +| **tail** | 最初の要素以外の全ての要素から成る新たな列を生成する。 | +| **apply** | 添字によるアクセス。 | +| **更新** | 不変列のときは (`updated` による) 関数型の更新、可変列のときは (`update` による) 副作用としての上書き更新。 | +| **先頭に追加**| 列の先頭への要素の追加。不変列のときは新たな列を生成する。可変列のときは現在の列を上書きする。 | +| **最後に追加** | 列の最後に要素を追加する。不変列のときは新たな列を生成する。可変列のときは現在の列を上書きする。 | +| **挿入** | 要素を列の任意の位置に挿入する。この演算は可変列にのみサポートされている。 | + +次の表は、不変と可変両方の集合とマップの以下の演算の性能特性をまとめる: + +| | | +| --- | ---- | +| **検索** | 要素が集合に含まれるかを調べるか、マップからキーに関連付けられた値を選択する。 | +| **追加** | 集合に新たな要素を追加するか、マップにキー/値ペアを追加する。 | +| **削除** | 集合から要素を削除するか、マップからキーを削除する。 | +| **最小** | 集合の最小要素かマップの最小キー。 | diff --git a/_ja/overviews/collections/seqs.md b/_ja/overviews/collections/seqs.md new file mode 100644 index 0000000000..3f9156e2d7 --- /dev/null +++ b/_ja/overviews/collections/seqs.md @@ -0,0 +1,114 @@ +--- +layout: multipage-overview +title: 列トレイト Seq、IndexedSeq、および LinearSeq + +discourse: false + +partof: collections +overview-name: Collections + +num: 5 + +language: ja +--- + +列 ([`Seq`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Seq.html)) トレイトは、長さ (`length`) があり、それぞれの要素に `0` から数えられた固定された添字 (index) がある `Iterable` の一種だ。 + +以下の表にまとめられた列の演算は以下のカテゴリーに分けることができる: + +* **添字と長さの演算** `apply`、 `isDefinedAt`、 `length`、 `indices`、および `lengthCompare`。`Seq` では `apply` メソッドは添字の意味で使われるため、`Seq[T]`型の列は `Int` を引数 (添字) としてをとり、`T`型の要素を返す部分関数だ。つまり、`Seq[T]` は `PartialFunction[Int, T]` を継承する。列内の要素はゼロから列の長さ (`length`) − 1 まで添字付けられている。列の `length` メソッドは一般コレクションにおける `size` メソッドの別名だ。`lengthCompare` メソッドは、たとえどちらかの列が無限の長さを持っていても、二つの列の長さを比較することができる。 +* **添字検索演算**である `indexOf`、 `lastIndexOf`、 `indexofSlice`、 `lastIndexOfSlice`、 `indexWhere`、 `lastIndexWhere`、 `segmentLength`、 `prefixLength` は、渡された値もしくは条件関数に合致する要素の添字を返す。 +* **加算**である `+:`、`:+`、`padTo` は、列の先頭か最後に要素を追加した新しい列を返す。 +* **更新演算**である `updated`、`patch` は、元の列に何らかの要素を上書きした列を返す。 +* **並べ替え演算**である `sorted`、`sortWith`、`sortBy` は、列内の要素を何らかの基準に基づいて並べ替える。 +* **逆転演算**である `reverse`、`reverseIterator`、`reverseMap` は、列内の要素を逆順に返すか処理する。 +* **比較演算**である `startsWith`、 `endsWith`、 `contains`、 `containsSlice`、 `corresponds` は、二つの列を関連付けるか、列の中から要素を検索する。 +* **集合演算**である `intersect`、 `diff`、 `union`、 `distinct` は、二つの列間で集合演算のようなものを行うか、列内の要素の重複を削除する。 + +列が可変の場合は、追加で副作用のある `update` メソッドを提供し、列内の要素を上書きすることができる。 +Scala の他の構文の例にならって、`seq(idx) = elem` は `seq.update(idx, elem)` の略記法であるため、`update` によって便利な代入構文がただで手に入る。`update` と `updated` の違いに注意してほしい。 `update` は列内の要素を上書きし、可変列でのみ使用可能だ。 +`updated` は全ての列で使用可能であり、元の列は変更せずに常に新しい列を返す。 + +### Seq トレイトの演算 + +| 使用例 | 振る舞い | +| ------ | ------ | +| **添字と長さの演算:** | | +| `xs(i)` |(展開した場合、`xs apply i`)。`xs` の添字 `i` の位置の要素。| +| `xs isDefinedAt i` |`xs.indices` に `i` が含まれているか調べる。 | +| `xs.length` |列の長さ (`size` と同様)。 | +| `xs.lengthCompare ys` |`xs` が `ys` より短い場合は `-1`、長い場合は `+1`、同じ長さの場合は `0` を返す。いずれかの列が無限でも正常に作動する。| +| `xs.indices` |0 から `xs.length - 1` までの `xs` の添字の範囲。 | +| **添字検索演算:** | | +| `xs indexOf x` |`xs`内で `x` と等しい最初の要素の添字 (数種の別形がある)。| +| `xs lastIndexOf x` |`xs`内で `x` と等しい最後の要素の添字 (数種の別形がある)。| +| `xs indexOfSlice ys` |`xs` の添字で、それと後続の要素が、列 `ys` と同値になる最初のもの。| +| `xs lastIndexOfSlice ys` |`xs` の添字で、それと後続の要素が、列 `ys` と同値になる最後のもの。| +| `xs indexWhere p` |`xs`内で条件関数 `p` を満たす最初の要素の添字 (数種の別形がある)。| +| `xs segmentLength (p, i)`|全ての要素が途切れなく条件関数 `p` を満たし、`xs(i)` から始まる、最長の `xs` の切片の長さ。| +| `xs prefixLength p` |全ての要素が途切れなく条件関数 `p` を満たす、最長の `xs` の先頭切片の長さ。| +| **加算:** | | +| `x +: xs` |`xs` の要素の先頭に `x` を追加した、新しい列。 | +| `xs :+ x` |`xs` の要素の最後に `x` を追加した、新しい列。 | +| `xs padTo (len, x)` |`xs` の長さが `len` になるまで最後に値 `x` を追加していった列。| +| **更新演算:** | | +| `xs patch (i, ys, r)` |`xs`内の、`i` から始まる `r`個の要素をパッチ `ys`内の要素と置換した列。| +| `xs updated (i, x)` |`xs`の添字 `i` の要素を `x` に置換したコピー。 | +| `xs(i) = x` |(展開した場合、`xs.update(i, x)`、ただし可変列でのみ使用可能)。`xs`の添字 `i` の位置の要素を `x` と上書きする。| +| **並べ替え演算:** | | +| `xs.sorted` |`xs` の要素型の標準的な順序付けを用いて、`xs` の要素を並べ替えることによって得られる新しい列。| +| `xs sortWith lt` |比較関数 `lt` 用いて `xs` の要素を並べ替えることによって得られる新しい列。| +| `xs sortBy f` |`xs` の要素を並べ替えることによって得られる新しい列。二つの要素の比較は、両者を関数 `f` に適用してその結果を比較することによって行われる。| +| **逆転演算:** | | +| `xs.reverse` |`xs`内の要素を逆順にした列。 | +| `xs.reverseIterator` |`xs`内の全ての要素を逆順に返すイテレータ。 | +| `xs reverseMap f` |`xs`内の要素に逆順に関数 `f` を `map` して得られる列。| +| **比較演算:** | | +| `xs startsWith ys` |`xs` が列 `ys` から始まるかを調べる (数種の別形がある)。| +| `xs endsWith ys` |`xs` が列 `ys` で終わるかを調べる (数種の別形がある)。| +| `xs contains x` |`xs` が `x` と等しい要素を含むかを調べる。 | +| `xs containsSlice ys` |`xs` が `ys` と等しい連続した切片を含むかを調べる。 | +| `(xs corresponds ys)(p)` |`xs` と `ys` の対応した要素が、二項条件関数の `p` を満たすかを調べる。| +| **集合演算:** | | +| `xs intersect ys` |列 `xs` と `ys` の積集合で、`xs` における要素の順序を保ったもの。| +| `xs diff ys` |列 `xs` と `ys` の差集合で、`xs` における要素の順序を保ったもの。| +| `xs union ys` |和集合; `xs ++ ys` に同じ| +| `xs.distinct` |`xs` の部分列で要素の重複を一切含まないもの。 | + +[`Seq`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Seq.html) トレイトには [`LinearSeq`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/IndexedSeq.html) と [`IndexedSeq`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/IndexedSeq.html) +という二つの子トレイトがある。 +これらは新しい演算を定義しないが、それぞれ異なった性能特性をもつ。 +線形列 (linear sequence) は効率的な `head` と `tail` 演算を持ち、一方添字付き列 (indexed sequence) は効率的な`apply`、`length`、および (可変の場合) `update` 演算を持つ。 +よく使われる線形列の例に `scala.collection.immutable.List` と `scala.collection.immutable.Stream` がある。よく使われる添字付き列の例としては `scala.Array` と `scala.collection.mutable.ArrayBuffer` がある。 +`Vector` は添字付き列と線形列の間の興味深い折衷案だ。 +事実上定数時間のオーバーヘッドで添字アクセスと線形アクセスを提供するからだ。 +そのため、ベクトルは添字アクセスと線形アクセスの両方を混合して使用してるアクセスパターンにおける良い基盤となる。 +ベクトルに関しては、また[後ほど詳しくみていく](concrete-immutable-collection-classes.html#vectors)。 + +### バッファ ### + +可変列に分類されるものの中で重要なものに `Buffer` がある。バッファは既存の要素を上書きできるだけでなく、要素を挿入したり、削除したり、効率的にバッファの最後に新しい要素を追加したりできる。バッファがサポートする新しいメソッドの中で主要なものは、要素を最後に追加する `+=` と `++=`、先頭に追加する `+=:` と `++=:`、要素を挿入する `insert` と `insertAll`、そして要素を削除する `remove` と `-=` だ。以下の表にこれらの演算をまとめた。 + +よく使われるバッファの実装に `ListBuffer` と `ArrayBuffer` がある。名前が示すとおり、`ListBuffer` は `List` に支えられており、要素を効率的に `List` に変換できる。一方、`ArrayBuffer` は配列に支えられており、これも素早く配列に変換できる。 + +#### Buffer クラスの演算 #### + +| 使用例 | 振る舞い| +| ------ | ------ | +| **加算:** | | +| `buf += x` |バッファの最後に要素 `x` を追加し、`buf` 自身を戻り値として返す。| +| `buf += (x, y, z)` |渡された要素をバッファの最後に追加する。| +| `buf ++= xs` |`xs`内の全ての要素をバッファの最後に追加する。| +| `x +=: buf` |バッファの先頭に要素 `x` を追加する。| +| `xs ++=: buf` |`xs`内の全ての要素をバッファの先頭に追加する。| +| `buf insert (i, x)` |バッファの添字 `i` の位置に要素 `x` を挿入する。| +| `buf insertAll (i, xs)` |`xs`内の全ての要素をバッファの添字 `i` の位置に挿入する。| +| **減算:** | | +| `buf -= x` |バッファから要素 `x` を削除する。| +| `buf remove i` |バッファの添字 `i` の位置の要素を削除する。| +| `buf remove (i, n)` |バッファの添字 `i` の位置から始まる `n`個の要素を削除する。| +| `buf trimStart n` |バッファの先頭の要素 `n`個を削除する。| +| `buf trimEnd n` |バッファの最後の要素 `n`個を削除する。| +| `buf.clear()` |バッファの全ての要素を削除する。| +| **クローン演算:** | | +| `buf.clone` |`buf` と同じ要素を持った新しいバッファ。| diff --git a/_ja/overviews/collections/sets.md b/_ja/overviews/collections/sets.md new file mode 100644 index 0000000000..d84225e5c0 --- /dev/null +++ b/_ja/overviews/collections/sets.md @@ -0,0 +1,153 @@ +--- +layout: multipage-overview +title: 集合 + +discourse: false + +partof: collections +overview-name: Collections + +num: 6 + +language: ja +--- + +集合 ([`Set`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Set.html)) は要素の重複の無い `Iterable` だ。集合一般に定義される演算は次の表にまとめてあり、可変集合に関してはその次の表も見てほしい。これらの演算は以下のカテゴリーに分類される: + +* **条件演算**である `contains`、`apply`、`subsetOf`。`contains` メソッドは集合が任意の要素を含むかを調べる。集合での `apply` メソッドは `contains` と同じであるため、`set(elem)` は `set contains elem` と同じだ。これは集合が要素を含んでいれば `true` を返す関数として使えることを意味する。 + +例えば、 + + + val fruit = Set("apple", "orange", "peach", "banana") + fruit: scala.collection.immutable.Set[java.lang.String] = + Set(apple, orange, peach, banana) + scala> fruit("peach") + res0: Boolean = true + scala> fruit("potato") + res1: Boolean = false + + +* **加算**である `+` と `++` は、単一もしくは複数の要素を集合に追加し、新たな集合を返す。 +* **減算**である `-` と `--` は、単一もしくは複数の要素を集合から削除し、新たな集合を返す。 +* **集合演算**である和集合、積集合、および差集合。これらの演算には文字形とシンボル形の二つの形がある。文字バージョンは `intersect`、`union`、および `diff` で、シンボルバージョンは `&`、`|`、と `&~` だ。`Set` が `Traversable` から継承する `++` は `union` と `|` の更なる別名だと考えることができるが、`++` は `Traversable` の引数を取るが、`union` と `|` は集合を取る。 + +### Set トレイトの演算 ### + +| 使用例 | 振る舞い| +| ------ | ------ | +| **条件演算:** | | +| `xs contains x` |`xs` が `x` を含むかを調べる。| +| `xs(x)` |`xs contains x` に同じ。 | +| `xs subsetOf ys` |`xs` が `ys` の部分集合であるかを調べる。| +| **加算:** | | +| `xs + x` |`xs`内の全ての要素および `x` を含んだ集合。| +| `xs + (x, y, z)` |`xs`内の全ての要素および渡された要素を含んだ集合。| +| `xs ++ ys` |`xs`内の全ての要素と `ys`内の全ての要素を含んだ集合。| +| **減算:** | | +| `xs - x` |`x` を除き、`xs`内の全ての要素を含んだ集合。| +| `xs - (x, y, z)` |渡された要素を除き、`xs`内の全ての要素を含んだ集合。| +| `xs -- ys` |`ys`内の要素を除き、`xs`内の全ての要素を含んだ集合。| +| `xs.empty` |`xs` と同じクラスの空集合。| +| **集合演算:** | | +| `xs & ys` |`xs` と `ys` の積集合。| +| `xs intersect ys` |`xs & ys` に同じ| +| `xs | ys` |`xs` と `ys` の和集合。| +| `xs union ys` |`xs | ys` に同じ| +| `xs &~ ys` |`xs` と `ys` の差集合。| +| `xs diff ys` |`xs &~ ys` に同じ| + +可変集合は、この表にまとめてあるとおり、加算、減算、更新演算などの新たなメソッドを追加する。 + +### mutable.Set トレイトの演算 ### + +| 使用例 | 振る舞い| +| ------ | ------ | +| **加算:** | | +| `xs += x` |集合 `xs` に副作用として要素 `x` を加え、`xs`自身を返す。| +| `xs += (x, y, z)` |集合 `xs` に副作用として渡された要素を加え、`xs`自身を返す。| +| `xs ++= ys` |集合 `xs` に副作用として `ys`内の全ての要素を加え、`xs`自身を返す。| +| `xs add x` |集合 `xs` に要素 `x` を加え、以前に集合に含まれていなければ `true` を返し、既に含まれていれば `false` を返す。| +| **減算:** | | +| `xs -= x` |集合 `xs` から副作用として要素 `x` を削除して、`xs`自身を返す。| +| `xs -= (x, y, z)` |集合 `xs` から副作用として渡された要素を削除して、`xs`自身を返す。| +| `xs --= ys` |集合 `xs` から副作用として `ys`内の全ての要素を削除して、`xs`自身を返す。| +| `xs remove x` |集合 `xs` から要素 `x` を削除、以前に集合に含まれていれば `true` を返し、含まれていなければ `false` を返す。| +| `xs retain p` |`xs`内の要素で条件関数 `p` を満たすものだけを残す。| +| `xs.clear()` |`xs` から全ての要素を削除する。| +| **更新演算:** | | +| `xs(x) = b` |(展開した場合、`xs.update(x, b)`)。ブーリアン値の引数 `b` が `true` ならば `xs` に `x` を加え、それ以外なら `xs` から `x` を削除する。| +| **クローン演算:** | | +| `xs.clone` |`xs` と同じ要素を持つ新しい可変集合。| + +不変集合と同様に、可変集合も要素追加のための `+` と `++` 演算、および要素削除のための `-` と `--` 演算を提供する。しかし、これらは集合をコピーする必要があるため可変集合ではあまり使われることがない。可変集合はより効率的な `+=` と `-=` という更新方法を提供する。`s += elem` という演算は、集合 `s` に副作用として `elem` を集合に加え、変化した集合そのものを戻り値として返す。同様に、`s -= elem` は集合から `elem` を削除して、変化した集合を戻り値として返す。`+=` と `-=` の他にも、traversable やイテレータの全ての要素を追加または削除する一括演算である `++=` と `--=` がある。 + +メソッド名として `+=` や `-=` が選ばれていることによって、非常に似たコードが可変集合と不変集合のどちらでも動くことを意味する。不変集合 `s` を使った次の REPL のやりとりを見てほしい: + + scala> var s = Set(1, 2, 3) + s: scala.collection.immutable.Set[Int] = Set(1, 2, 3) + scala> s += 4 + scala> s -= 2 + scala> s + res2: scala.collection.immutable.Set[Int] = Set(1, 3, 4) + +ここでは `immutable.Set`型の `var` に対して `+=` と `-=` を使った。`s += 4` のようなステートメントは、`s = s + 4` の略だ。つまり、これは集合 `s` に対して追加メソッドの `+` を呼び出して、結果を変数`s` に代入しなおしてる。次に、可変集合でのやりとりを見てほしい。 + + scala> val s = collection.mutable.Set(1, 2, 3) + s: scala.collection.mutable.Set[Int] = Set(1, 2, 3) + scala> s += 4 + res3: s.type = Set(1, 4, 2, 3) + scala> s -= 2 + res4: s.type = Set(1, 4, 3) + +結果は前回のやりとりと非常に似通ったものになった: `Set(1, 2, 3)` から始めて、最後に `Set(1, 3, 4)` +を得た。ステートメントは前回と同じに見えるが、実際には違うことを行っている。`s` `+=` `4` は今度は可変集合 `s` の `+=` メソッドを呼び出し、その場で集合を上書きしているのだ。同様に、`s -= 2` は同じ集合の `-=` メソッドを呼び出している。 + +この2つのやりとりの比較から重要な原則を導き出せる。`val` に格納された可変コレクションと、`var` に格納された不変コレクションは、大抵の場合にお互いを置換できるということだ。これはコレクションに対して上書きで更新されたのか新たなコレクションが作成されたのかを第三者が観測できるような別名の参照がない限り成り立つ原則だ。 + +可変集合は `+=` と `-=` の別形として `add` と `remove` を提供する。違いは `add` と `remove` は集合に対して演算の効果があったかどうかを示す `Boolean` の戻り値を返すことだ。 + +現在の可変集合のデフォルトの実装では要素を格納するのにハッシュテーブルを使っている。不変集合のデフォルトの実装は集合の要素数に応じて方法を変えている。空集合はシングルトンで表される。サイズが4つまでの集合は全要素をフィールドとして持つオブジェクトとして表される。それを超えたサイズの不変集合は[ハッシュトライ](#hash-tries)として表される。 + +このような設計方針のため、(例えば 4以下の) 小さいサイズの集合を使う場合は、通常の場合、可変集合に比べて不変集合の方が、よりコンパクトで効率的だ。集合のサイズが小さいと思われる場合は、不変集合を試してみてはいかがだろうか。 + +集合のサブトレイトとして `SortedSet` と `BitSet` の2つがある。 + +### 整列済み集合 ### + +整列済み集合は ([`SortedSet`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/SortedSet.html)) +は (集合の作成時に指定された) 任意の順序で要素を (`iterator` や `foreach` を使って) 返す事ができる集合だ。[`SortedSet`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/SortedSet.html) クラスのデフォルトの表現は、左の子ツリー内の全ての要素が右の子ツリーの全ての要素よりも小さいという恒常条件を満たす順序付けされた二分木だ。これにより、通りがけ順 (in-order) で探索するだけで、木の全ての要素を昇順に返すことができる。Scala の[`immutable.TreeSet`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/TreeSet.html) クラスは **赤黒木** を使ってこの恒常条件を実装している。また、この木構造は、**平衡木**であり、ルートから全て葉のまでの長さの違いは最大で1要素しかない。 + +空の [`TreeSet`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/TreeSet.html) を作成するには、まず順序付けを指定する: + + scala> val myOrdering = Ordering.fromLessThan[String](_ > _) + myOrdering: scala.math.Ordering[String] = ... + +次に、その順序付けの空の木集合を作成するには: + + scala> TreeSet.empty(myOrdering) + res1: scala.collection.immutable.TreeSet[String] = TreeSet() + +順序付けの引数を省略して、空集合の要素型を指定することもできる。その場合は、要素型のデフォルトの順序付けが使われる。 + + scala> TreeSet.empty[String] + res2: scala.collection.immutable.TreeSet[String] = TreeSet() + +(例えば連結やフィルターによって) 新たな木集合を作成した場合、それは元の集合と同じ順序付けを保つ。たとえば、 + + scala> res2 + ("one", "two", "three", "four") + res3: scala.collection.immutable.TreeSet[String] = TreeSet(four, one, three, two) + +整列済み集合は要素の範囲もサポートする。例えば、`range` メソッドは開始要素以上、終了要素未満の全ての要素を返す。また、`from` メソッドは開始要素以上の全ての要素を、集合の順序付けで返す。両方のメソッドの戻り値もまた整列済み集合だ。用例: + + scala> res3 range ("one", "two") + res4: scala.collection.immutable.TreeSet[String] = TreeSet(one, three) + scala> res3 from "three" + res5: scala.collection.immutable.TreeSet[String] = TreeSet(three, two) + + +### ビット集合 ### + +ビット集合 ([`BitSet`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/BitSet.html)) は非負整数の要素の集合で、何ワードかのパックされたビットにより実装されている。[`BitSet`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/BitSet.html) クラスは、内部で `Long` の配列を用いている。最初の `Long` は第0 〜 63 の要素を受け持ち、次のは第64 〜 127 の要素という具合だ。全ての `Long` の、それぞれの 64ビットは、対応する要素が集合に含まれる場合は 1 にセットされ、含まれない場合は 0 になる。このため、ビット集合のサイズは格納されている整数の最大値に依存する。`N` がその最大の整数値の場合、集合のサイズは `N/64` `Long` ワード、または `N/8` バイト、にステータス情報のためのバイトを追加したものだ。 + +このため、たくさんの小さい要素を含む場合、ビット集合は他の集合に比べてコンパクトである。ビット集合のもう一つの利点は `contains` を使った所属判定や、`+=` や `-=` を使った要素の追加や削除が非常に効率的であることだ。 diff --git a/_ja/overviews/collections/strings.md b/_ja/overviews/collections/strings.md new file mode 100644 index 0000000000..e605c099f9 --- /dev/null +++ b/_ja/overviews/collections/strings.md @@ -0,0 +1,31 @@ +--- +layout: multipage-overview +title: 文字列 + +discourse: false + +partof: collections +overview-name: Collections + +num: 11 + +language: ja +--- + +配列と同様、文字列 (`String`) は直接には列ではないが、列に変換することができ、また、文字列は全ての列演算をサポートする。以下に文字列に対して呼び出すことができる演算の具体例を示す。 + + scala> val str = "hello" + str: java.lang.String = hello + scala> str.reverse + res6: String = olleh + scala> str.map(_.toUpper) + res7: String = HELLO + scala> str drop 3 + res8: String = lo + scala> str slice (1, 4) + res9: String = ell + scala> val s: Seq[Char] = str + s: Seq[Char] = WrappedString(h, e, l, l, o) + +これらの演算は二つの暗黙の変換により実現されている。例えば、上記の最後の行で文字列が `Seq` に変換されている所では優先度の低い `String` から `WrappedString` への変換が自動的に導入されている (`WrappedString` は +`immutable.IndexedSeq` の子クラスだ)。一方、`reverse`、`map`、`drop`、および `slice` メソッドの呼び出しでは優先度の高い `String` から `StringOps` への変換が自動的に導入されており、これは全ての不変列のメソッドを文字列に追加する。 diff --git a/_ja/overviews/collections/trait-iterable.md b/_ja/overviews/collections/trait-iterable.md new file mode 100644 index 0000000000..96d986e9f7 --- /dev/null +++ b/_ja/overviews/collections/trait-iterable.md @@ -0,0 +1,76 @@ +--- +layout: multipage-overview +title: Iterable トレイト + +discourse: false + +partof: collections +overview-name: Collections + +num: 4 + +language: ja +--- + +反復可能 ([`Iterable`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Iterable.html) トレイトはコレクション階層の上から2番目に位置する。このトレイトの全メソッドは、コレクション内の要素を1つづつ返す抽象メソッド `iterator` に基づいている。`Iterable` では、`Traversable` トレイトの `foreach` メソッドも `iterator`に基づいて実装されている。以下が実際の実装だ: + + def foreach[U](f: Elem => U): Unit = { + val it = iterator + while (it.hasNext) f(it.next()) + } + +多くの `Iterable` のサブクラスは、より効率的な実装を提供するため、上の `foreach` の標準実装をオーバーライドしている。 `foreach` は `Traversable` の全ての演算の基となっているため、効率的であることが重要なのだ。 + +`Iterable` にはイテレータを返すもう2つのメソッドがある: `grouped` と `sliding` だ。これらのイテレータは単一の要素を返すのではなく、元のコレクションの部分列を返す。これらのメソッドに渡された引数がこの部分列の最大サイズとなる。`grouped` メソッドは要素を n 個づつの「かたまり」にして返すのに対し、 `sliding` は n 個の要素から成る「窓」をスライドさせて返す。この二つのメソッドの違いは REPL でのやりとりを見れば明らかになるはずだ。: + + scala> val xs = List(1, 2, 3, 4, 5) + xs: List[Int] = List(1, 2, 3, 4, 5) + scala> val git = xs grouped 3 + git: Iterator[List[Int]] = non-empty iterator + scala> git.next() + res3: List[Int] = List(1, 2, 3) + scala> git.next() + res4: List[Int] = List(4, 5) + scala> val sit = xs sliding 3 + sit: Iterator[List[Int]] = non-empty iterator + scala> sit.next() + res5: List[Int] = List(1, 2, 3) + scala> sit.next() + res6: List[Int] = List(2, 3, 4) + scala> sit.next() + res7: List[Int] = List(3, 4, 5) + +`Iterable` トレイトは、 `Traversable` からのメソッドの他に、イテレータがあることで効率的に実装することができる他のメソッドを追加する。それらのメソッドを以下の表にまとめる。 + +### Iterable トレイトの演算 + +| 使用例 | 振る舞い | +| ------ | ------ | +| **抽象メソッド:** | | +| `xs.iterator` |`xs`内の全ての要素を `foreach` が走査するのと同じ順序で返すイテレータ。| +| **他のイテレータ:**   | | +| `xs grouped size` |このコレクション内の要素を固定サイズの「かたまり」にして返すイテレータ。| +| `xs sliding size` |このコレクション内の要素を固定サイズの「窓」をスライドさせて返すイテレータ。| +| **サブコレクション取得演算:** | | +| `xs takeRight n` |`xs` の最後の `n` 個の要素から成るコレクション (順序が定義されていない場合は、任意の `n` 個の要素から成るコレクション)。| +| `xs dropRight n` |コレクションから `xs` `takeRight` `n` を除いた残りの部分。| +| **zip 演算:** | | +| `xs zip ys` |`xs` と `ys` のそれぞれから対応する要素をペアにした `Iterable`。| +| `xs zipAll (ys, x, y)` |`xs` と `ys` のそれぞれから対応する要素をペアにした `Iterable` で、もし片方が短い場合は `x` か `y` を使って長いほうに合わせる。| +| `xs.zipWithIndex` |`xs`内の要素とその添字をペアにした `Iterable`。| +| **比較演算:**    | | +| `xs sameElements ys` |`xs` と `ys` が同じ要素を同じ順序で格納しているかを調べる。| + +継承階層では `Iterable` 直下に [`Seq`](https://www.scala-lang.org/api/current/scala/collection/Seq.html)、[`Set`](https://www.scala-lang.org/api/current/scala/collection/Set.html)、[`Map`](https://www.scala-lang.org/api/current/scala/collection/Map.html``) という三つのトレイトがある。 +この三つのトレイトに共通することは `apply` メソッドと `isDefinedAt` メソッドを持ったトレイト [` +PartialFunction`](https://www.scala-lang.org/api/current/scala/PartialFunction.html) を実装しているということだ。 +しかし、`PartialFunction` の実装方法は三者三様である。 + +列は `apply` を位置的な添字として用いられており、要素は常に `0` +から数えられる。だから、`Seq(1, 2, 3)(1)` は `2` を返す。 +集合では `apply` は所属を調べるのに用いられる。 +例えば、`Set('a', 'b', 'c')('b')` は `true` を返し、`Set()('a')` は `false` を返す。 +最後に、マップでは `apply` は要素の選択に用いられている。 +例えば、`Map('a' -> 1, 'b' -> 10, 'c' -> 100)('b')` は `10` を返す。 + +次に、この3つのコレクションをより詳しく説明しよう。 diff --git a/_ja/overviews/collections/trait-traversable.md b/_ja/overviews/collections/trait-traversable.md new file mode 100644 index 0000000000..b95d158a63 --- /dev/null +++ b/_ja/overviews/collections/trait-traversable.md @@ -0,0 +1,121 @@ +--- +layout: multipage-overview +title: Traversable トレイト + +discourse: false + +partof: collections +overview-name: Collections + +num: 3 + +language: ja +--- + +走査可能 ([`Traversable`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Traversable.html))トレイトはコレクション階層の最上位に位置する。訳注: 木構造などでノードを一つづつ走査することを traverse と言う。また、-able で終わるトレイトは名詞としても使われるため、「走査可能なもの」という意味だ。 `Traversable` の抽象的な演算は `foreach` のみだ: + + def foreach[U](f: Elem => U) + +`Traverable` を実装するコレクションクラスは、このメソッドを定義するだけでいい。逆に言うと、その他全てのメソッドは `Traversable` から継承することができる。 + +`foreach` メソッドは、コレクション中の全ての要素を走査して、渡された演算 `f` を各々の要素に適用することを意図している。 +この演算の型は `Elem => U` であり、`Elem` はコレクションの要素の型で、`U` は任意の戻り値型だ。 `f` の呼び出しはそれに伴う副作用のためだけに行われ、`f` の戻り値の全ては `foreach` によって破棄される。 + +`Traversable` が定義する全ての具象メソッド (concrete method) を次の表に列挙した。これらのメソッドは次のカテゴリに分類される: + +* **加算**である `++` は、2つの `Traversable` を連結するか、あるイテレータが返す全ての要素を `Traversable` に追加する。 +* **map 演算**である`map`、`flatMap`、及び `collect` はコレクションの要素に何らかの関数を適用して新しいコレクションを生成する。 +* **変換演算**である `toArray`、`toList`、`toIterable`、`toSeq`、`toIndexedSeq`、`toStream`、`toSet`、`toMap` は `Traversable` なコレクションを別のより特定のものに変える。実行時のコレクション型が既に要求されているコレクション型と一致する場合、これらの全ての変換は引数をそのまま返す。例えば、リストに `toList` を適用した場合、リストそのものを返す。 +* **コピー演算** `copyToBuffer` と `copyToArray`。名前のとおり、これらの演算はコレクションの要素をバッファまたは配列にコピーする。 +* **サイズ演算** `isEmpty`、`nonEmpty`、`size`、および `hasDefiniteSize`。 `Traversable` なコレクションは有限または無限のサイズを取りうる。無限の `Traversable` コレクションの例としては自然数のストリームである `Stream.from(0)` がある。 +`hasDefiniteSize` メソッドはコレクションが無限である可能性があるかを示す。`hasDefiniteSize` が `true` を返す場合、コレクション確実に有限だ。`false` を返す場合、コレクションはまだ完全に展開されていないことを示し、それは無限か有限のどちらである可能性もある。 +* **要素取得演算** `head`、`last`、`headOption`、`lastOption`、および `find`。これらはコレクションの最初または最後の要素、または他の条件に一致する最初の要素を選択する。しかし、全てのコレクションにおいて「最初」と「最後」の意味が明確に定義されているわけではないことに注意してほしい。たとえば、ハッシュ集合はハッシュキーの並びで要素を格納するかもしれないが、ハッシュキーは実行するたびに変わる可能性がある。その場合、ハッシュ集合の「最初」の要素はプログラムを実行するたびに異なるかもしれない。 +あるコレクションから常に同じ順序で要素を得られる場合、そのコレクションは**順序付け** (ordered) されているという。 +ほとんどのコレクションは順序付けされているが、(ハッシュ集合など)いくつかののコレクションは順序付けされていない ― +順序付けを省くことで多少効率が上がるのだ。順序付けは再現性のあるテストを書くのに不可欠であり、デバッグの役に立つ。 +そのため Scala のコレクションは、全てのコレクション型に対して順序付けされた選択肢を用意してある。 +例えば、`HashSet` に代わる順次付けされたものは `LinkedHashSet` だ。 +* **サブコレクション取得演算** `tail`、`init`、`slice`、`take`、`drop`、`takeWhile`、`dropWhile`、`filter`、`filterNot`、`withFilter`。 +これら全ての演算は添字の範囲や何らかの条件関数によって識別されたサブコレクションを返す。 +* **分割演算**である `splitAt`、`span`、`partition`、`groupBy` の全てはコレクションの要素をいくつかのサブコレクションに分割する。 +* **要素条件演算**である`exists`、`forall`、`count` は与えられた条件関数を使ってコレクションをテストする。 +* **fold 演算**である `foldLeft`、`foldRight`、`/:`、`:\`、`reduceLeft`、`reduceRight` は次々と二項演算を隣接する要素に適用していく。 +* **特定 fold 演算**である `sum`、`product`、`min`、`max` は特定の型(numeric か comparable)のコレクションでのみ動作する。 +* **文字列演算**である `mkString`、`addString`、`stringPrefix` はコレクションを文字列に変換する方法を提供する。 +* **ビュー演算**はオーバーロードされた二つの `view` メソッドによって構成される。 + ビューは遅延評価されたコレクションだ。ビューについての詳細は[後ほど](views.html)。 + +### Traversableトレイトの演算 + +| 使用例 | 振る舞い | +| ------ | ------ | +| **抽象メソッド:** | | +| `xs foreach f` |`xs` 内の全ての要素に対して関数 `f` を実行する。 | +| **加算:** | | +| `xs ++ ys` |`xs` と `ys` の両方の要素から成るコレクション。 `ys` は [`TraversableOnce`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/TraversableOnce.html) なコレクション、つまり [`Traversable`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Traversable.html) または [`Iterator`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Iterator.html) だ。| +| **map 演算:** | | +| `xs map f` |`xs` 内の全ての要素に関数 `f` を適用することによって得られるコレクション。| +| `xs flatMap f` |`xs` 内の全ての要素に対してコレクション値を返す関数 `f` を適用し、その結果を連結したコレクション。| +| `xs collect f` |`xs` 内の全ての要素に対して部分関数 `f` が定義されている場合のみ適応し、その結果を集めたコレクション。| +| **変換演算:** | | +| `xs.toArray` |コレクションを配列に変換する。 | +| `xs.toList` |コレクションをリストに変換する。 | +| `xs.toIterable` |コレクションを `Iterable` に変換する。 | +| `xs.toSeq` |コレクションを列に変換する。 | +| `xs.toIndexedSeq` |コレクションを添字付き列に変換する。 | +| `xs.toStream` |コレクションを遅延評価されたストリームに変換する。 | +| `xs.toSet` |コレクションを集合に変換する。 | +| `xs.toMap` |キー/値のペアを持つコレクションをマップに変換する。コレクションが要素としてのペアを持たない場合、この演算を呼び出すと静的型エラーがおこる。| +| **コピー演算:** | | +| `xs copyToBuffer buf` |コレクション内の全ての要素をバッファ `buf` にコピーする。| +| `xs copyToArray(arr, s, n)`|最大 `n` 個のコレクションの要素を配列 `arr` の添字 `s` より始まる位置にコピーする。最後の2つの引数は省略可能だ。| +| **サイズ演算:** | | +| `xs.isEmpty` |コレクションが空であるかどうかを調べる。 | +| `xs.nonEmpty` |コレクションに要素が含まれているかを調べる。 | +| `xs.size` |コレクション内の要素の数。 | +| `xs.hasDefiniteSize` |`xs` が有限のサイズであることが明らかな場合 true を返す。| +| **要素取得演算:** | | +| `xs.head` |コレクションの最初の要素 (順序が定義されていない場合は、任意の要素)。| +| `xs.headOption` |`xs` の最初の要素のオプション値、または `xs` が空の場合 `None`。| +| `xs.last` |コレクションの最後の要素 (順序が定義されていない場合は、任意の要素)。| +| `xs.lastOption` |`xs` の最後の要素のオプション値、または `xs` が空の場合 `None`。| +| `xs find p` |`xs` の中で条件関数 `p` を満たす最初の要素のオプション値、または条件を満たす要素が無い場合 `None`。| +| **サブコレクション取得演算:** | | +| `xs.tail` |コレクションから `xs.head` を除いた残りの部分。 | +| `xs.init` |コレクションから `xs.last` を除いた残りの部分。 | +| `xs slice (from, to)` |`xs` の一部の添字範囲内 (`from` 以上 `to` 未満) にある要素から成るコレクション。 | +| `xs take n` |`xs` の最初の `n` 個の要素から成るコレクション (順序が定義されていない場合は、任意の `n` 個の要素から成るコレクション)。| +| `xs drop n` |コレクションから `xs take n` を除いた残りの部分。 | +| `xs takeWhile p` |`xs` 内の要素を最初から次々とみて、条件関数 `p` を満たす限りつないでいったコレクション。| +| `xs dropWhile p` |`xs` 内の要素を最初から次々とみて、条件関数 `p`を満たす限り除いていったコレクション。| +| `xs filter p` |`xs` 内の要素で条件関数 `p` を満たすものから成るコレクション。| +| `xs withFilter p` |このコレクションを非正格 (non-strict) に filter したもの。後続の `map`, `flatMap`, `foreach`, および `withFilter` への呼び出しは `xs` の要素のうち条件関数 `p` が true に評価されるもののみに適用される。| +| `xs filterNot p` |`xs` 内の要素で条件関数 `p` を満たさないものから成るコレクション。| +| **分割演算:** | | +| `xs splitAt n` |`xs` を `n` の位置で二分して `(xs take n, xs drop n)` と同値のコレクションのペアを返す。| +| `xs span p` |`xs` を条件関数 `p` に応じて二分して `(xs takeWhile p, xs.dropWhile p)` と同値のペアを返す。| +| `xs partition p` |`xs` を条件関数 `p` を満たすコレクションと満たさないものに二分して `(xs filter p, xs.filterNot p)` と同値のペアを返す。| +| `xs groupBy f` |`xs` を判別関数 `f` に応じてコレクションのマップに分割する。| +| **要素条件演算:** | | +| `xs forall p` |`xs` 内の全ての要素に条件関数 `p` が当てはまるかを示す Boolean 値。| +| `xs exists p` |`xs` 内に条件関数 `p` を満たす要素があるかどうかを示す Boolean 値。| +| `xs count p` |`xs` 内の要素で条件関数 `p` 満たすものの数。| +| **fold 演算:** | | +| `(z /: xs)(op)` |`z` から始めて、左から右へと `xs` 内の隣接する要素に二項演算 `op` を次々と適用したもの。| +| `(xs :\ z)(op)` |`z` から始めて、右から左へと `xs` 内の隣接する要素に二項演算 `op` を次々と適用したもの。| +| `xs.foldLeft(z)(op)` |`(z /: xs)(op)` に同じ。 | +| `xs.foldRight(z)(op)` |`(xs :\ z)(op)` に同じ。 | +| `xs reduceLeft op` |左から右へと空ではないコレクション `xs` 内の隣接する要素に二項演算 `op` を次々と適用したもの。| +| `xs reduceRight op` |右から左へと空ではないコレクション `xs` 内の隣接する要素に二項演算 `op` を次々と適用したもの。| +| **特定 fold 演算:** | | +| `xs.sum` |コレクション `xs` 内の数値要素の値の和。 | +| `xs.product` |コレクション `xs` 内の数値要素の値の積。 | +| `xs.min` |コレクション `xs` 内の順序付けされたの値の最小値。 | +| `xs.max` |コレクション `xs` 内の順序付けされたの値の最大値。 | +| **文字列演算:** | | +| `xs addString (b, start, sep, end)`|`xs` 内の要素を `sep` で区切った後、`start` と `end` で挟んだ文字列を `StringBuilder b` に追加する。 `start`, `sep`, `end` は全て省略可能。| +| `xs mkString (start, sep, end)`|`xs` 内の要素を `sep` で区切った後、`start` と `end` で挟んだ文字列に変換する。 `start`, `sep`, `end` は全て省略可能。| +| `xs.stringPrefix` |`xs.toString` で返される文字列の先頭にあるコレクション名。| +| **ビュー演算:** | | +| `xs.view` |`xs` に対するビューを生成する。 | +| `xs view (from, to)` |`xs` の一部の添字範囲内を表すビューを生成する。 | diff --git a/_ja/overviews/collections/views.md b/_ja/overviews/collections/views.md new file mode 100644 index 0000000000..f3bf540cc4 --- /dev/null +++ b/_ja/overviews/collections/views.md @@ -0,0 +1,133 @@ +--- +layout: multipage-overview +title: ビュー + +discourse: false + +partof: collections +overview-name: Collections + +num: 14 + +language: ja +--- + +コレクションには新たなコレクションを構築するメソッドがたくさんある。例えば `map`、`filter`、`++` などがある。これらのメソッドは1つ以上のコレクションをレシーバとして取り、戻り値として別のコレクションを生成するため**変換演算子** +(transformer) と呼ばれる。 + +変換演算子を実装するには主に二つの方法がある。**正格** (strict) 法は変換演算子の戻り値として全ての要素を含む新たなコレクションを返す。非正格法、もしくは**遅延** (lazy) 法と呼ばれる方法は、結果のコレクションの代理のみを構築して返し、実際の要素は必用に応じて構築される。 + +非正格な変換演算子の具体例として、以下の遅延 map 演算の実装を見てほしい: + + def lazyMap[T, U](coll: Iterable[T], f: T => U) = new Iterable[U] { + def iterator = coll.iterator map f + } + +`lazyMap` は、渡されたコレクション `coll` の全要素を総なめすることなく新しい `Iterable` を構築していることに注意してほしい。代わりに、渡された関数の `f` が新しいコレクションの `iterator` に必要に応じて適用される。 + +全ての変換演算子を遅延実装している `Stream` を除いて、Scala のコレクションは全ての変換演算子をデフォルトで正格法で実装している。しかし、コレクションのビューにより、体系的に全てのコレクションを遅延したものに変え、また逆に戻すことができる。**ビュー** (view) は特殊なコレクションの一種で、何らかのコレクションに基づいているが全ての変換演算子を遅延実装してる。 + +あるコレクションからそのビューへと移行するには、そのコレクションに対して `view` メソッドを呼び出す。`xs` というコレクションがあるとすると、`xs.view` は変換演算子が遅延実装されている以外は同一のコレクションだ。ビューから正格なコレクションに戻るには `force` メソッドを使う。 + +具体例を見てみよう。2つの関数を続けて写像したい `Int`型のベクトルがあるとする: + + scala> val v = Vector(1 to 10: _*) + v: scala.collection.immutable.Vector[Int] = + Vector(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + scala> v map (_ + 1) map (_ * 2) + res5: scala.collection.immutable.Vector[Int] = + Vector(4, 6, 8, 10, 12, 14, 16, 18, 20, 22) + +最後のステートメントにおいて、式 `v map (_ + 1)` は新たなベクトルを構築し、それは第2の `map (_ * 2)` +の呼び出しによって3つ目のベクトルに変換される。多くの状況において、最初の `map` への呼び出しで中間結果のためだけのベクトルが構築されるのは無駄にしかならない。上記の例では、`(_ + 1)` と `(_ * 2)` の2つの関数を合成して `map` を1回だけ実行したほうが速くなるだろう。両方の関数が1ヶ所にあるならば手で合成することも可能だろう。しかし、しばしばデータ構造への連続した変換はプログラム内の別々のモジュールによって実行される。もしそうならば、これらの変換を融合することはモジュール性を犠牲してしまう。中間結果を回避する、より汎用性のある方法は、ベクトルをまずビューに変え全ての変換をビューに適用した後、ビューからベクトルに逆変換することだ: + + scala> (v.view map (_ + 1) map (_ * 2)).force + res12: Seq[Int] = Vector(4, 6, 8, 10, 12, 14, 16, 18, 20, 22) + +同じ演算の手順をもう1度ひとつひとつ見てみよう: + + scala> val vv = v.view + vv: scala.collection.SeqView[Int,Vector[Int]] = + SeqView(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + +`v.view` を適用することで遅延評価される `Seq` である `SeqView` が得られる。`SeqView` には2つの型パラメータがある。第1の `Int` はビューの要素の型を示す。第2の `Vector[Int]` は `view` +を逆変換する時の型コンストラクタを示す。 + +最初の `map` を適用することでビューは以下を返す: + + scala> vv map (_ + 1) + res13: scala.collection.SeqView[Int,Seq[_]] = SeqViewM(...) + +`map` の戻り値は、`SeqViewM(...)` と表示された値だ。この値は本質的に、ベクトル `v` に対して関数 `(_ + 1)` を使って `map` を適用する必要があるということを記録するラッパーだ。しかし、ビューが強制実行 (`force`) されるまでは map 演算は適用されない。`SeqView` の後ろに付けられた「M」は、ビューが `map` 演算を表すことを示す。他の文字は別の遅延された演算を示す。例えば、「S」は遅延した `slice` 演算を示し、「R」は遅延した `reverse` 演算を示す。先ほどの結果に、次の `map` を適用しよう。 + + scala> res13 map (_ * 2) + res14: scala.collection.SeqView[Int,Seq[_]] = SeqViewMM(...) + +今度は2回の map 演算を含む `SeqView` が得られるため、「M」も二回表示される: `SeqViewMM(...)`。 最後に、先ほどの結果を逆変換すると: + + scala> res14.force + res15: Seq[Int] = Vector(4, 6, 8, 10, 12, 14, 16, 18, 20, 22) + +格納されていた両方の関数は強制実行 (`force`) の一部として適用され、新しいベクトルが構築される。これにより、中間結果のデータ構造は必要なくなった。 + +1つ注意してほしいのは、最終結果の静的型が `Vector` ではなく `Seq` であるということだ。 型をさかのぼってみると、最初の遅延 map が適用された時点で既に戻り値の静的型が `SeqViewM[Int, Seq[_]]` 型であったことが分かる。つまり、ビューが特定の列型である `Vector` に適応されたという「知識」が失われたということだ。ビューの実装には多量のコードを要するものがあるため、Scala コレクションライブラリは汎用コレクション型にのみビューを提供するが、特定の実装にはビューは提供されない。(配列はこの例外で、配列に遅延演算を適用しても戻り値は再び静的型の `Array` を返す。) + +ビューを使うことを考慮する二つの理由がある。第一は、パフォーマンスだ。コレクションをビューに切り替えることで中間結果の構築を避けれることを既に説明した。このような節約は時として大切な結果をもたらす。もう一つの具体例として、単語のリストから回文を探す問題を考える。回文とは前後のどちらから読んでも同じ語句だ。必要な定義を以下に示す: + + def isPalindrome(x: String) = x == x.reverse + def findPalidrome(s: Seq[String]) = s find isPalindrome + +ここで非常に長い `words` という列があり、列の最初の百万語の中から単一の回文を探したいと仮定する。`findPalidrome` の定義を再利用できるだろうか。当然、以下のように書くことはできる: + + findPalindrome(words take 1000000) + +これは、列の中から最初の百万語を選択するのと、単一の回文を探すという二つの側面をきれいに分担する。しかし、たとえ列の最初の語が回文であったとしても、百万語から成る中間結果の列を常に構築してしまうという欠点がある。つまり、999999語が中間結果にコピーされ、その後検査もされないということが起こりえる。ここで多くのプログラマは諦めて、先頭 n個の部分列に対して検索を行う特殊なバージョンの回文検索を書いてしまう。ビューがあれば、あなたはそんな事をしなくても済む。こう書けばいいからだ: + + findPalindrome(words.view take 1000000) + +関心事の分業を保ちつつも、これは百万要素の列の代わりに軽量なビューオブジェクトのみを構築する。これにより、パフォーマンスとモジュール性の択一をしなくても済む。 + +次の事例として、可変列に対するビューを見てみたい。可変列のビューにいくつかの変換関数を適用することで、元の列内の要素を選択的に更新する制限枠として機能することができる。ここに配列 `arr` があると仮定する: + + scala> val arr = (0 to 9).toArray + arr: Array[Int] = Array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9) + +その配列への制限枠を作るのに、`arr` のビューのスライスを作ることができる: + + scala> val subarr = arr.view.slice(3, 6) + subarr: scala.collection.mutable.IndexedSeqView[ + Int,Array[Int]] = IndexedSeqViewS(...) + +これにより、配列 `arr` の 3〜5 の位置を参照するビュー `subarr` が得られる。ビューは要素をコピーせず、参照のみを提供する。ここで、列の要素を変更するメソッドがあると仮定する。例えば、以下の `negate` メソッドは、与えれれた整数列の全ての要素の符号を反転する: + + scala> def negate(xs: collection.mutable.Seq[Int]) = + for (i <- 0 until xs.length) xs(i) = -xs(i) + negate: (xs: scala.collection.mutable.Seq[Int])Unit + +配列 `arr` の 3〜5 の位置の要素の符号を反転したいとする。これに `negate` が使えるだろうか。 ビューを使えば、簡単にできる: + + scala> negate(subarr) + scala> arr + res4: Array[Int] = Array(0, 1, 2, -3, -4, -5, 6, 7, 8, 9) + +何が起こったかと言うと、`negate` は `subarr`内の全ての要素を変更したが、実際にはそれは `arr` +の要素のスライスだったというわけだ。ここでも、ビューがモジュール性を保つのに役立っているのが分かるだろう。上記のコードは、メソッドをどの添字の範囲に適用するのかという問題と、どのメソッドを適用するのかという問題を見事に切り離している。 + +これだけ粋なビューの使用例を見た後だと、なぜ正格コレクションがあるのか疑問に思うかもしれない。理由の一つとして、性能を比較すると遅延コレクションが常に正格コレクションに勝るとは限らないというものがある。コレクションのサイズが小さい場合、ビュー内でクロージャを作成し適用するためのオーバーヘッドが、中間結果のためのデータ構造を回避することによる利得を上回ってしまうことが多いのだ。恐らくより重要な理由として、遅延した演算が副作用を伴う場合、ビューの評価が非常に混乱したものとなってしまうというものがある。 + +Scala 2.8 以前で多くのユーザが陥った失敗例を以下に示す。これらのバージョンでは `Range` 型が遅延評価されたため、実質的にビューのように振舞った。多くの人が以下のようにして複数の actor を作成しようとした: + + val actors = for (i <- 1 to 10) yield actor { ... } + +`actor` メソッドと後続の中括弧に囲まれたコードは actor を作成し始動するべきだが、驚いた事にどの actor も実行されていなかった。なぜ何も起こらなかったのかは、for 式が `map` の適用と等価であることを思い出せば説明がつく: + + val actors = (1 to 10) map (i => actor { ... }) + +`(1 to 10)` により生成された範囲はビューのように振舞ったため、`map` の戻り値もビューとなった。つまり、どの要素も計算されず、結果的にどの actor も作成されなかったのだ! 範囲の式全体を強制実行すれば actor は作成されただろうが、actor +を作動させるためにそれが必要なことは全く明白では無い。 + +このような不意打ちを避けるため、Scala 2.8 ではより規則的なルールを採用する。ストリームを除く、全てのコレクションは正格評価される。正格コレクションから遅延コレクションに移行する唯一の方法は `view` メソッドによる。戻る唯一の方法は `force` による。よって、Scala 2.8 では先程の `actors` の定義は期待した通りに 10個の actor を作成し始動する。以前のような、予期しない振る舞いをさせるには、明示的に `view` メソッドを呼び出す必要がある: + + val actors = for (i <- (1 to 10).view) yield actor { ... } + +まとめると、ビューは効率性の問題とモジュール性の問題を仲裁する強力な道具だ。しかし、遅延評価の面倒に巻き込まれないためには、ビューの使用は二つのシナリオに限るべきだ。一つは、ビューの適用を副作用を伴わない純粋関数型のコードに限ること。もしくは、明示的に全ての変更が行われる可変コレクションに適用することだ。避けたほうがいいのは、ビューと新たなコレクションを作成しつつ副作用を伴う演算を混合することだ。 diff --git a/_ja/overviews/core/futures.md b/_ja/overviews/core/futures.md new file mode 100644 index 0000000000..11b88dcda3 --- /dev/null +++ b/_ja/overviews/core/futures.md @@ -0,0 +1,732 @@ +--- +layout: singlepage-overview +title: Future と Promise + +partof: futures + +language: ja + +discourse: false +--- + +**Philipp Haller, Aleksandar Prokopec, Heather Miller, Viktor Klang, Roland Kuhn, Vojin Jovanovic 著**
      +**Eugene Yokota 訳** + +## 概要 + +**Future** は並列に実行される複数の演算を取り扱うのに便利な方法を提供する。それは効率的でノンブロッキングな方法だ。 +大まかな考え方はシンプルなもので、`Future` はまだ存在しない計算結果に対するプレースホルダのようなものだ。 +一般的に、`Future` の結果は並行に計算され後で集計することができる。 +このように並行なタスクを合成することで、より速く、非同期で、ノンブロッキングな並列コードとなることが多い。 + +デフォルトでは、Future も Promise もノンブロッキングであり、典型的なブロッキング演算の代わりにコールバックを使う。 +コールバックの使用を概念的にも構文的にも単純化するために、Scala は Future をノンブロッキングに合成する `flatMap`、`foreach`、`filter` といったコンビネータを提供する。 +ブロックすることは可能で、(推奨されないが) 絶対に必要だという場面においては Future をブロックすることもできる。 + + + +## Future + +`Future` は、ある時点において利用可能となる可能性のある値を保持するオブジェクトだ。 +この値は、なんらかの計算結果であることが多い。 +その計算が例外とともに失敗する可能性があるため、`Future` は計算が例外を投げる場合を想定して例外を保持することもできる。 +ある `Future` が値もしくは例外を持つとき、`Future` は**完了**したという。 +`Future` が値とともに完了した場合、`Future` はその値とともに**成功**したという。 +`Future` が例外とともに完了した場合、`Future` はその例外とともに**失敗**したという。 + +`Future` には 1回だけ代入することができるという重要な特性がある。 +一度 Future オブジェクトが値もしくは例外を持つと、実質不変となり、それが上書きされることは絶対に無い。 + +Future オブジェクトを作る最も簡単な方法は、非同期の計算を始めてその結果を持つ `Future` を返す +`future` メソッドを呼び出すことだ。 +計算結果は `Future` が完了すると利用可能になる。 + +ここで注意して欲しいのは `Future[T]` は Future オブジェクトの型であり、 +`future` はなんらかの非同期な計算を作成しスケジュールして、その計算結果とともに完了する +Future オブジェクトを返すメソッドだということだ。 + +具体例で説明しよう。 +ある人気ソーシャルネットワークの API を想定して、与えられたユーザの友達のリストを取得できるものとする。 +まず新しいセッションを開いて、ある特定のユーザの友達リストを申請する: + + import scala.concurrent._ + import ExecutionContext.Implicits.global + + val session = socialNetwork.createSessionFor("user", credentials) + val f: Future[List[Friend]] = Future { + session.getFriends() + } + +上の例では、まず `scala.concurrent` パッケージの内容をインポートすることで +`Future` 型と `future` が見えるようにしている。 +2つ目のインポートは追って説明する。 + +次に、仮想の `createSessionFor` メソッドを呼んでサーバにリクエストを送るセッション変数を初期化している。 + +ユーザの友達リストを取得するには、ネットワークごしにリクエストを送信する必要があり、それは長い時間がかかる可能性がある。 +これは `getFriends` メソッドで例示されている。 +応答が返ってくるまでの間に CPU を有効に使うには、プログラムの残りをブロックするべきではない。 +つまり、この計算は非同期にスケジュールされるべきだ。 +ここで使われている `future` メソッドはまさにそれを行い、与えれたブロックを並行に実行する。 +この場合は、リクエストを送信し応答を待ち続ける。 + +サーバが応答すると Future `f` 内において友達リストが利用可能となる。 + +試みが失敗すると、例外が発生するかもしれない。 +以下の例では、`session` 変数の初期化が不正なため、`future` ブロック内の計算が +`NullPointerException` を投げる。この Future `f` は、この例外とともに失敗する: + + val session = null + val f: Future[List[Friend]] = Future { + session.getFriends + } + +上の `import ExecutionContext.Implicits.global` +という一文はデフォルトのグローバルな実行コンテキスト (execution context) をインポートする。 +実行コンテキストは渡されたタスクを実行し、スレッドプールのようなものだと考えていい。 +これらは、非同期計算がいつどのように実行されるかを取り扱うため、`future` メソッドに欠かせないものだ。 +独自の実行コンテキストを定義して `future` +とともに使うことができるが、今のところは上記のようにデフォルトの実行コンテキストをインポートできるということが分かれば十分だ。 + +この例ではネットワークごしにリクエストを送信して応答を待つという仮想のソーシャルネットワーク API を考えてみた。 +すぐに試してみることができる非同期の計算の例も考えてみよう。 +テキストファイルがあったとして、その中である特定のキーワードが最初に出てきた位置を知りたいとする。 +この計算はファイルの内容をディスクから読み込むのにブロッキングする可能性があるため、他の計算と並行実行するのは理にかなっている。 + + val firstOccurence: Future[Int] = Future { + val source = scala.io.Source.fromFile("myText.txt") + source.toSeq.indexOfSlice("myKeyword") + } + +### コールバック + +これで非同期な計算を始めて新しい Future オブジェクトを作る方法は分かったけども、計算結果が利用可能となったときにそれを使って何かをする方法をまだみていない。 +多くの場合、計算の副作用だけじゃなくて、その結果に興味がある。 + +Future の実装の多くは、Future の結果を知りたくなったクライアントは Future が完了するまで自分の計算をブロックすることを強要する。そうしてやっと Future の計算結果を得られた後に自分の計算を続行できるようになる。 +後でみるように、この方式も Scala の Future API で可能となっているが、性能という観点から見ると Future +にコールバックを登録することで完全にノンブロッキングで行う方が好ましいと言える。 +このコールバックは Future が完了すると非同期に呼び出される。 +コールバックの登録時に Future が既に完了している場合は、コールバックは非同期に実行されるか、もしくは同じスレッドで逐次的に実行される。 + +コールバックを登録する最も汎用的な方法は、`Try[T] => U` 型の関数を受け取る `onComplete` メソッドを使うことだ。 +このコールバックは、Future が成功すれば `Success[T]` 型の値に適用され、失敗すれば `Failure[T]` 型の値に適用される。 + +この `Try[T]` は、それが何らか型の値を潜在的に保持するモナドだという意味において +`Option[T]` や `Either[T, S]` に似ている。 +しかし、これは値か Throwable なオブジェクトを保持することに特化して設計されている。 +`Option[T]` が値 (つまり `Some[T]`) を持つか、何も持たない (つまり `None`) +のに対して、`Try[T]` は値を持つ場合は `Success[T]` で、それ以外の場合は `Failure[T]` で必ず例外を持つ。 +`Failure[T]` は、何故値が無いのかを説明できるため、`None` よりも多くの情報を持つ。 +同様に `Try[T]` を `Either[Throwable, T]`、つまり左値を `Throwable` に固定した特殊形だと考えることもできる。 + +ソーシャルネットワークの例に戻って、最近の自分の投稿した文のリストを取得して画面に表示したいとする。 +これは `List[String]` を返す `getRecentPosts` メソッドを呼ぶことで行われ、戻り値には最近の文のリストが入っている: + + val f: Future[List[String]] = Future { + session.getRecentPosts + } + + f onComplete { + case Success(posts) => for (post <- posts) println(post) + case Failure(t) => println("エラーが発生した: " + t.getMessage) + } + +`onComplete` メソッドは、Future 計算の失敗と成功の両方の結果を扱えるため、汎用性が高い。 +成功した結果のみ扱う場合は、(部分関数を受け取る) `onSuccess` コールバックを使う: + + val f: Future[List[String]] = Future { + session.getRecentPosts + } + + f onSuccess { + case posts => for (post <- posts) println(post) + } + +失敗した結果のみ扱う場合は、`onFailure` コールバックを使う: + + val f: Future[List[String]] = Future { + session.getRecentPosts + } + + f onFailure { + case t => println("エラーが発生した: " + t.getMessage) + } + + f onSuccess { + case posts => for (post <- posts) println(post) + } + +`onFalure` コールバックは Future が失敗した場合、つまりそれが例外を保持する場合のみ実行される。 + +部分関数は `isDefinedAt` メソッドを持つため、`onFailure` メソッドはコールバックが特定の `Throwable` に対して定義されている場合のみ発火される。 +以下の例では、登録された `onFailure` コールバックは発火されない: + + val f = Future { + 2 / 0 + } + + f onFailure { + case npe: NullPointerException => + println("これが表示されているとしたらビックリ。") + } + +キーワードの初出の位置を検索する例に戻ると、キーワードの位置を画面に表示したいかもしれない: + + val firstOccurence: Future[Int] = Future { + val source = scala.io.Source.fromFile("myText.txt") + source.toSeq.indexOfSlice("myKeyword") + } + + firstOccurence onSuccess { + case idx => println("キーワードの初出位置: " + idx) + } + + firstOccurence onFailure { + case t => println("ファイルの処理に失敗した: " + t.getMessage) + } + +`onComplete`、`onSuccess`、および `onFailure` メソッドは全て `Unit` 型を返すため、これらの呼び出しを連鎖させることができない。 +これは意図的な設計で、連鎖された呼び出しが登録されたコールバックの実行の順序を暗示しないようにしている +(同じ Future に登録されたコールバックは順不同に発火される)。 + +ここで、コールバックが実際のところ**いつ**呼ばれるのかに関して説明する必要がある。 +Future 内の値が利用可能となることを必要とするため、Future が完了した後でのみ呼び出されることができる。 +しかし、Future を完了したスレッドかコールバックを作成したスレッドのいずれかにより呼び出されるという保証は無い。 +かわりに、コールバックは Future オブジェクトが完了した後のいつかに何らかスレッドにより実行される。 +これをコールバックが実行されるのは **eventually** だという。 + +さらに、コールバックが実行される順序は、たとえ同じアプリケーションを複数回実行した間だけでも決定してない。 +実際、コールバックは逐次的に呼び出されるとは限らず、一度に並行実行されるかもしれない。 +そのため、以下の例における変数 `totalA` は計算されたテキスト内の正しい小文字と大文字の `a` の合計数を持たない場合がある。 + + @volatile var totalA = 0 + + val text = Future { + "na" * 16 + "BATMAN!!!" + } + + text onSuccess { + case txt => totalA += txt.count(_ == 'a') + } + + text onSuccess { + case txt => totalA += txt.count(_ == 'A') + } + +2つのコールバックが順次に実行された場合は、変数 `totalA` は期待される値 `18` を持つ。 +しかし、これらは並行して実行される可能性もあるため、その場合は `totalA` は +`+=` が atomic な演算ではないため、 +(つまり、読み込みと書き込みというステップから構成されており、それが他の読み込みと書き込みの間に挟まって実行される可能性がある) +`16` または `2` という値になってしまう可能性もある。 + +万全を期して、以下にコールバックの意味論を列挙する: + + +
        +
      1. Future に onComplete コールバックを登録することで、対応するクロージャが Future が完了した後に eventually に実行されることが保証される。
      2. + +
      3. onSuccessonFailure コールバックを登録することは onComplete と同じ意味論を持つ。ただし、クロージャがそれぞれ成功したか失敗した場合のみに呼ばれるという違いがある。
      4. + +
      5. 既に完了した Future にコールバックを登録することは (1 により) コールバックが eventually に実行されることとなる。
      6. + +
      7. Future に複数のコールバックが登録された場合は、それらが実行される順序は定義されない。それどころか、コールバックは並行に実行される可能性がある。しかし、ExecutionContext の実装によっては明確に定義された順序となる可能性もある。
      8. + +
      9. 例外を投げるコールバックがあったとしても、他のコールバックは実行される。
      10. + +
      11. 完了しないコールバックがあった場合 (つまりコールバックに無限ループがあった場合)他のコールバックは実行されない可能性がある。そのような場合はブロックする可能性のあるコールバックは blocking 構文を使うべきだ (以下参照)。
      12. + +
      13. コールバックの実行後はそれは Future オブジェクトから削除され、GC 対象となる。
      14. +
      + +### 関数型合成と for 内包表記 + +上でみたコールバック機構により Future の結果を後続の計算に連鎖することが可能となった。 +しかし、場合によっては不便だったり、コードが肥大化することもある。 +具体例で説明しよう。 +為替トレードサービスの API があって、米ドルを有利な場合のみ買いたいとする。 +まずコールバックを使ってこれを実現してみよう: + + val rateQuote = Future { + connection.getCurrentValue(USD) + } + + rateQuote onSuccess { case quote => + val purchase = Future { + if (isProfitable(quote)) connection.buy(amount, quote) + else throw new Exception("有益ではない") + } + + purchase onSuccess { + case _ => println(amount + " USD を購入した") + } + } + +まずは現在の為替相場を取得する `rateQuote` という `Future` を作る。 +この値がサーバから取得できて Future が成功した場合は、計算は +`onSuccess` コールバックに進み、ここで買うかどうかの決断をすることができる。 +ここでもう 1つの Future である `purchase` を作って、有利な場合のみ買う決定をしてリクエストを送信する。 +最後に、`purchase` が完了すると、通知メッセージを標準出力に表示する。 + +これは動作するが、2つの理由により不便だ。 +第一に、`onSuccess` を使わなくてはいけなくて、2つ目の Future である +`purchase` をその中に入れ子にする必要があることだ。 +例えば `purchase` が完了した後に別の貨幣を売却したいとする。 +それはまた `onSuccess` の中でこのパターンを繰り返すことになり、インデントしすぎで理解しづらく肥大化したコードとなる。 + +第二に、`purchase` は他のコードのスコープ外にあり、`onSuccess` +コールバック内においてのみ操作することができる。 +そのため、アプリケーションの他の部分は `purchase` を見ることができず、他の貨幣を売るために別の +`onSuccess` コールバックを登録することもできない。 + +これらの 2つの理由から Future はより自然な合成を行うコンビネータを提供する。 +基本的なコンビネータの 1つが `map` で、これは与えられた Future +とその値に対する投射関数から、元の Future が成功した場合に投射した値とともに完了する新しい Future を生成する。 +Future の投射はコレクションの投射と同様に考えることができる。 + +上の例を `map` コンビネータを使って書き換えてみよう: + + val rateQuote = Future { + connection.getCurrentValue(USD) + } + + val purchase = rateQuote map { quote => + if (isProfitable(quote)) connection.buy(amount, quote) + else throw new Exception("有益ではない") + } + + purchase onSuccess { + case _ => println(amount + " USD を購入した") + } + +`rateQuote` に対して `map` を使うことで `onSuccess` コールバックを一切使わないようになった。 +それと、より重要なのが入れ子が無くなったことだ。 +ここで他の貨幣を売却したいと思えば、`purchase` に再び `map` するだけでいい。 + +しかし、`isProfitable` が `false` を返して、例外が投げられた場合はどうなるだろう? +その場合は `purchase` は例外とともに失敗する。 +さらに、コネクションが壊れて `getCurrentValue` が例外を投げて `rateQuote` +が失敗した場合を想像してほしい。 +その場合は、投射する値が無いため `purchase` は自動的に `rateQuote` と同じ例外とともに失敗する。 + +結果として、もし元の Future が成功した場合は、返される Future は元の Future の値を投射したものとともに完了する。 +もし投射関数が例外を投げた場合は Future はその例外とともに完了する。 +もし元の Future が例外とともに失敗した場合は、返される Future も同じ例外を持つ。 +この例外を伝搬させる意味論は他のコンビネータにおいても同様だ。 + +Future の設計指針の 1つは for 内包表記から利用できるようにすることだった。 +このため、Future は `flatMap`、`filter` そして `foreach` コンビネータを持つ。 +`flatMap` メソッドは値を新しい Future `g` に投射する関数を受け取り、`g` +が完了したときに完了する新たな Future を返す。 + +米ドルをスイス・フラン (CHF) と交換したいとする。 +両方の貨幣の為替レートを取得して、両者の値に応じて購入を決定する必要がある。 +以下に for 内包表記を使った `flatMap` と `withFilter` の例をみてみよう: + + val usdQuote = Future { connection.getCurrentValue(USD) } + val chfQuote = Future { connection.getCurrentValue(CHF) } + + val purchase = for { + usd <- usdQuote + chf <- chfQuote + if isProfitable(usd, chf) + } yield connection.buy(amount, chf) + + purchase onSuccess { + case _ => println(amount + " CHF を購入した") + } + +この `purchase` は `usdQuote` と `chfQuote` が完了した場合のみ完了する。 +これら 2つの Future の値に依存するため、それよりも早く自分の計算を始めることができない。 + +上の for 内包表記は以下のように翻訳される: + + val purchase = usdQuote flatMap { + usd => + chfQuote + .withFilter(chf => isProfitable(usd, chf)) + .map(chf => connection.buy(amount, chf)) + } + +これは for 内包表記に比べて分かりづらいが、`flatMap` 演算をより良く理解するために解析してみよう。 +`flatMap` 演算は自身の値を別の Future へと投射する。 +この別の Future が完了すると、戻り値の Future もその値とともに完了する。 +上記の例では、`flatMap` は `usdQuote` Future の値を用いて `chfQuote` +の値をある特定の値のスイス・フランを購入するリクエストを送信する 3つ目の Future に投射している。 +結果の Future である `purchase` は、この 3つ目の Future が `map` から帰ってきた後にのみ完了する。 + +これは難解だが、幸いな事に `flatMap` 演算は使いやすく、また分かりやすい +for 内包表記以外の場合はあまり使われない。 + +`filter` コンビネータは、元の Future の値が条件関数を満たした場合のみその値を持つ新たな Future を作成する。 +それ以外の場合は新しい Future は `NoSuchElementException` とともに失敗する。 +Future に関しては、`filter` の呼び出しは `withFilter` の呼び出しと全く同様の効果がある。 + +`collect` と `filter` コンビネータの関係はコレクション API におけるこれらのメソッドの関係に似ている。 + +`foreach` コンビネータで注意しなければいけないのは値が利用可能となった場合に走査するのにブロックしないということだ。 +かわりに、`foreach` のための関数は Future が成功した場合のみ非同期に実行される。 +そのため、`foreach` は `onSuccess` コールバックと全く同じ意味を持つ。 + +`Future` トレイトは概念的に (計算結果と例外という) 2つの型の値を保持することができるため、例外処理のためのコンビネータが必要となる。 + +`rateQuote` に基いて何らかの額を買うとする。 +`connection.buy` メソッドは `amount` と期待する `quote` を受け取る。 +これは買われた額を返す。 +`quote` に変更があった場合は、何も買わずに `QuoteChangedException` を投げる。 +例外の代わりに `0` を持つ Future を作りたければ `recover` コンビネータを用いる: + + val purchase: Future[Int] = rateQuote map { + quote => connection.buy(amount, quote) + } recover { + case QuoteChangedException() => 0 + } + +`recover` コンビネータは元の Future が成功した場合は同一の結果を持つ新たな Future +を作成する。成功しなかった場合は、元の Future を失敗させた `Throwable` +に渡された部分関数が適用される。 +もしそれが `Throwable` を何らかの値に投射すれば、新しい Future はその値とともに成功する。 +もしその `Throwable` に関して部分関数が定義されていなければ、結果となる +Future は同じ `Throwable` とともに失敗する。 + +`recoverWith` コンビネータは元の Future が成功した場合は同一の結果を持つ新たな Future +を作成する。成功しなかった場合は、元の Future を失敗させた `Throwable` +に渡された部分関数が適用される。 +もしそれが `Throwable` を何らかの Future に投射すれば、新しい Future はその Future とともに成功する。 +`recover` に対する関係は `flatMap` と `map` の関係に似ている。 + +`fallbackTo` コンビネータは元の Future が成功した場合は同一の結果を持ち、成功しなかった場合は引数として渡された Future の成功した値を持つ新たな Future を作成する。 +この Future と引数の Future が両方失敗した場合は、新しい Future はこの Future の例外とともに失敗する。 +以下に米ドルの値を表示することを試みて、米ドルの取得に失敗した場合はスイス・フランの値を表示する具体例をみてみよう: + + val usdQuote = Future { + connection.getCurrentValue(USD) + } map { + usd => "値: " + usd + " USD" + } + val chfQuote = Future { + connection.getCurrentValue(CHF) + } map { + chf => "値: " + chf + "CHF" + } + + val anyQuote = usdQuote fallbackTo chfQuote + + anyQuote onSuccess { println(_) } + +`andThen` コンビネータは副作用の目的のためだけに用いられる。 +これは、成功したか失敗したかに関わらず現在の Future と全く同一の結果を返す新たな Future を作成する。 +現在の Future が完了すると、`andThen` に渡されたクロージャが呼び出され、新たな Future +はこの Future と同じ結果とともに完了する。 +これは複数の `andThen` 呼び出しが順序付けられていることを保証する。 +ソーシャルネットワークからの最近の投稿文を可変セットに保存して、全ての投稿文を画面に表示する以下の具体例をみてみよう: + + val allposts = mutable.Set[String]() + + Future { + session.getRecentPosts + } andThen { + case Success(posts) => allposts ++= posts + } andThen { + case _ => + clearAll() + for (post <- allposts) render(post) + } + +まとめると、Future に対する全てのコンビネータは元の Future に関連する新たな Future +を返すため、純粋関数型だといえる。 + +### 投射 + +例外として返ってきた結果に対して for 内包表記が使えるように Future は投射を持つ。 +元の Future が失敗した場合は、`failed` 投射は `Throwable` 型の値を持つ Future を返す。 +もし元の Future が成功した場合は、`failed` 投射は `NoSuchElementException` +とともに失敗する。以下は例外を画面に表示する具体例だ: + + val f = Future { + 2 / 0 + } + for (exc <- f.failed) println(exc) + +以下の例は画面に何も表示しない: + + val f = Future { + 4 / 2 + } + for (exc <- f.failed) println(exc) + + + + + + + +### Future の拡張 + +Future API にユーティリティメソッドを追加して拡張できるようにすることを予定している。 +これによって、外部フレームワークはより特化した使い方を提供できるようになる。 + +## ブロッキング + +前述のとおり、性能とデッドロックの回避という理由から Future をブロックしないことを強く推奨する。 +コールバックとコンビネータを使うことが Future の結果を利用するのに適した方法だ。 +しかし、状況によってはブロックすることが必要となるため、Future API と Promise API +においてサポートされている。 + +前にみた為替取引の例だと、アプリケーションの最後に全ての Future +が完了することを保証するためにブロックする必要がある。 +Future の結果に対してブロックする方法を以下に具体例で説明しよう: + + import scala.concurrent._ + import scala.concurrent.duration._ + + def main(args: Array[String]) { + val rateQuote = Future { + connection.getCurrentValue(USD) + } + + val purchase = rateQuote map { quote => + if (isProfitable(quote)) connection.buy(amount, quote) + else throw new Exception("有益ではない") + } + + Await.result(purchase, 0 nanos) + } + +Future が失敗した場合は、呼び出し元には Future が失敗した例外が送られてくる。 +これは `failed` 投射を含むため、元の Future が成功した場合は +`NoSuchElementException` が投げられることとなる。 + +代わりに、`Await.ready` を呼ぶことで Future が完了するまで待機するがその結果を取得しないことができる。 +同様に、このメソッドを呼んだ時に Future が失敗したとしても例外は投げられない。 + +`Future` トレイトは `ready()` と `result()` というメソッドを持つ `Awaitable` トレイトを実装する。 +これらのメソッドはクライアントからは直接呼ばれず、実行コンテキストからのみ呼ばれる。 + +`Awaitable` トレイトを実装することなくブロックする可能性のある第三者のコードを呼び出すために、以下のように +`blocking` 構文を使うことができる: + + blocking { + potentiallyBlockingCall() + } + +ブロックされたコードは例外を投げるかもしれない。その場合は、呼び出し元に例外が送られる。 + +## 例外処理 + +非同期の計算が処理されない例外を投げた場合、その計算が行われた Future は失敗する。 +失敗した Future は計算値のかわりに `Throwable` のインスタンスを格納する。 +`Future` は、`Throwable` に適用することができる `PartialFunction` を受け取る +`onFailure` コールバックメソッドを提供する。 +以下の特別な例外に対しては異なる処理が行われる: + +1. `scala.runtime.NonLocalReturnControl[_]`。この例外は戻り値に関連する値を保持する。 +典型的にはメソッド本文内の `return` 構文はこの例外を用いた `throw` へと翻訳される。 +この例外を保持するかわりに、関連する値が Future もしくは Promise に保存される。 + +2. `ExecutionException`。`InterruptedException`、`Error`、もしくは `scala.util.control.ControlThrowable` +が処理されないことで計算が失敗した場合に格納される。 +この場合は、処理されなかった例外は `ExecutionException` に保持される。 +これらの例外は非同期計算を実行するスレッド内で再び投げられる。 +この理由は、通常クライアント側で処理されないクリティカルもしくは制御フロー関連の例外が伝搬することを回避し、同時に Future の計算が失敗したことをクライアントに通知するためだ。 + +より正確な意味論の説明は [`NonFatal`](http://www.scala-lang.org/api/current/index.html#scala.util.control.NonFatal$) を参照。 + +## Promise + +これまでの所、`future` メソッドを用いた非同期計算により作成される `Future` オブジェクトのみをみてきた。 +しかし、Future は **Promise** を用いて作成することもできる。 + +Future がリードオンリーのまだ存在しない値に対するプレースホルダ・オブジェクトの一種だと定義されるのに対して、Promise +は書き込み可能で、1度だけ代入できるコンテナで Future を完了させるものだと考えることができる。 +つまり、Promise は `success` メソッドを用いて (約束を「完了させる」ことで) Future を値とともに成功させることができる。 +逆に、Promise は `failure` メソッドを用いて Future を例外とともに失敗させることもできる。 + +Promise の `p` は `p.future` によって返される Future を完了させる。 +この Future は Promise `p` に特定のものだ。実装によっては `p.future eq p` の場合もある。 + +ある計算が値を生産し、別の計算がそれを消費する Producer-Consumer の具体例を使って説明しよう。 +この値の受け渡しは Promise を使って実現している。 + + import scala.concurrent.{ Future, Promise } + import scala.concurrent.ExecutionContext.Implicits.global + + val p = Promise[T]() + val f = p.future + + val producer = Future { + val r = produceSomething() + p success r + continueDoingSomethingUnrelated() + } + + val consumer = Future { + startDoingSomething() + f onSuccess { + case r => doSomethingWithResult() + } + } + +ここでは、まず Promise を作って、その `future` メソッドを用いて完了される `Future` +を取得する。 +まず何らかの計算が実行され、`r` という結果となり、これを用いて Future `f` を完了させ、`p` という約束を果たす。 +ここで注意してほしいのは、`consumer` は `producer` が `continueDoingSomethingUnrelated()` を実行し終えてタスクが完了する前に結果を取得できることだ。 + +前述の通り、Promise は 1度だけ代入できるという意味論を持つ。 +そのため、完了させるのも 1回だけだ。 +既に完了 (もしくは失敗した) Promise に対して `success` を呼び出すと +`IllegalStateException` が投げられる。 + +以下は Promise を失敗させる具体例だ。 + + val p = Promise[T]() + val f = p.future + + val producer = Future { + val r = someComputation + if (isInvalid(r)) + p failure (new IllegalStateException) + else { + val q = doSomeMoreComputation(r) + p success q + } + } + +ここでは `producer` は中間結果 `r` を計算して、それが妥当であるか検証する。 +不正であれば、Promise `p` を例外を用いて完了させることで Promise を失敗させる。 +それ以外の場合は、`producer` は計算を続行して Promise `p` を妥当な結果用いて完了させることで、Future +`f` を完了させる。 + +Promise は潜在的な値である `Try[T]` (失敗した結果の `Failure[Throwable]` +もしくは成功した結果の `Success[T]`) +を受け取る `complete` メソッドを使って完了させることもできる。 + +`success` 同様に、既に完了した Promise に対して `failure` や `complete` を呼び出すと +`IllegalStateException` が投げられる。 + +これまでに説明した Promise の演算とモナディックで副作用を持たない演算を用いて合成した Future +を使って書いたプログラムの便利な特性としてそれらが決定的 (deterministic) であることが挙げられる。 +ここで決定的とは、プログラムで例外が投げられなければ、並列プログラムの実行スケジュールのいかんに関わらずプログラムの結果 +(Future から観測される値) は常に同じものとなるという意味だ。 + +場合によってはクライアントは Promise が既に完了していないときにのみ完了させたいこともある +(例えば、複数の HTTP がそれぞれ別の Future から実行されていて、クライアントは最初の戻ってきた +HTTP レスポンスにのみ興味がある場合で、これは最初に Promise を完了させる Future に対応する)。 +これらの理由のため、Promise には `tryComplete`、`trySuccess`、および `tryFailure` というメソッドがある。 +クライアントはこれらのメソッドを使った場合はプログラムの結果は決定的でなくなり実行スケジュールに依存することに注意するべきだ。 + +`completeWith` メソッドは別の Future を用いて Promise を完了させる。 +渡された Future が完了すると、その Promise も Future の値とともに完了する。 +以下のプログラムは `1` と表示する: + + val f = Future { 1 } + val p = Promise[Int]() + + p completeWith f + + p.future onSuccess { + case x => println(x) + } + +Promise を例外とともに失敗させる場合は、`Throwable` の 3つのサブタイプが特殊扱いされる。 +Promise を失敗させた `Throwable` が `scala.runtime.NonLocalReturnControl` +の場合は、Promise は関連する値によって完了させる。 +Promise を失敗させた `Throwable` が `Error`、`InterruptedException`、もしくは +`scala.util.control.ControlThrowable` の場合は、`Throwable` +は新たな `ExecutionException` の理由としてラッピングされ Promise が失敗させられる。 + +Promise、Future の `onComplete` メソッド、そして `future` +構文を使うことで前述の関数型合成に用いられるコンビネータの全てを実装することができる。 +例えば、2つの Future `f` と `g` を受け取って、(最初に成功した) `f` か `g` +のどちらかを返す `first` という新しいコンビネータを実装したいとする。 +以下のように書くことができる: + + def first[T](f: Future[T], g: Future[T]): Future[T] = { + val p = Promise[T]() + + f onSuccess { + case x => p.tryComplete(x) + } + + g onSuccess { + case x => p.tryComplete(x) + } + + p.future + } + + + + +## ユーティリティ + +並列アプリケーション内における時間の扱いを単純化するために `scala.concurrent` +は `Duration` という抽象体を導入する。 +`Duration` は既に他にもある一般的な時間の抽象化を目的としていない。 +並列ライブラリとともに使うことを目的とするため、`scala.concurrent` パッケージ内に置かれている。 + +`Duration` は時の長さを表す基底クラスで、それは有限でも無限でもありうる。 +有限の時間は `FiniteDuration` クラスによって表され `Long` の長さと `java.util.concurrent.TimeUnit` +によって構成される。 +無限時間も `Duration` を継承し、これは `Duration.Inf` と `Duration.MinusInf` という 2つのインスタンスのみが存在する。 +このライブラリは暗黙の変換のためのいくつかの `Duration` のサブクラスを提供するが、これらは使用されるべきではない。 + +抽象クラスの `Duration` は以下のメソッドを定義する: + +1. 時間の単位の変換 (`toNanos`、`toMicros`、`toMillis`、 +`toSeconds`、`toMinutes`、`toHours`、`toDays`、及び `toUnit(unit: TimeUnit)`)。 +2. 時間の比較 (`<`、`<=`、`>`、および `>=`)。 +3. 算術演算 (`+`、`-`、`*`、`/`、および `unary_-`)。 +4. この時間 `this` と引数として渡された時間の間の最小値と最大値 (`min`、`max`)。 +5. 時間が有限かの検査 (`isFinite`)。 + +`Duration` は以下の方法で作成することができる: + +1. `Int` もしくは `Long` 型からの暗黙の変換する。例: `val d = 100 millis`。 +2. `Long` の長さと `java.util.concurrent.TimeUnit` を渡す。例: `val d = Duration(100, MILLISECONDS)`。 +3. 時間の長さを表す文字列をパースする。例: `val d = Duration("1.2 µs")`。 + +`Duration` は `unapply` メソッドも提供するため、パータンマッチング構文の中から使うこともできる。以下に具体例をみる: + + import scala.concurrent.duration._ + import java.util.concurrent.TimeUnit._ + + // 作成 + val d1 = Duration(100, MILLISECONDS) // from Long and TimeUnit + val d2 = Duration(100, "millis") // from Long and String + val d3 = 100 millis // implicitly from Long, Int or Double + val d4 = Duration("1.2 µs") // from String + + // パターンマッチング + val Duration(length, unit) = 5 millis diff --git a/_ja/overviews/core/string-interpolation.md b/_ja/overviews/core/string-interpolation.md new file mode 100644 index 0000000000..23491ae1d7 --- /dev/null +++ b/_ja/overviews/core/string-interpolation.md @@ -0,0 +1,131 @@ +--- +layout: singlepage-overview +title: 文字列の補間 + +partof: string-interpolation + +language: ja + +discourse: false +--- + +**Josh Suereth 著**
      +**Eugene Yokota 訳** + +## はじめに + +Scala 2.10.0 より、Scala は文字列の補間 (string interpolation) というデータから文字列を作成する機構を提供する。 +文字列の補間を使ってユーザは加工文字列リテラル (processed string literal) 内に直接変数の参照を埋め込むことができる。具体例で説明しよう: + + val name = "James" + println(s"Hello, $name") // Hello, James + +上の例において、リテラル `s"Hello, $name"` は加工文字列リテラルだ。これはコンパイラがこのリテラルに対して何らかの追加処理を実行するということだ。加工文字リテラルは `"` の前にいくつかの文字を書くことで表記される。文字列の補間は [SIP-11](http://docs.scala-lang.org/sips/pending/string-interpolation.html) によって導入され、実装の詳細もそこに書かれている。 + +## 用例 + +Scala は `s`、`f`、そして `raw` という 3つの補間子 (interpolator) をあらかじめ提供する。 + +### `s` 補間子 + +文字列リテラルの先頭に `s` を追加することで文字列の中で直接変数が使えるようになる。先ほどの例で既にみた機能だ: + + val name = "James" + println(s"Hello, $name") // Hello, James + +この例では、 `s` で加工される文字列の中に `$name` が入れ子になっている。`s` 補間子は `name` 変数の値をこの位置に挿入しなければいけないと知っているため、結果として `Hello, James` という文字列となる。`s` 補間子を使って、スコープ内にあるどの名前でも文字列の中に埋め込むことができる。 + +文字列の補間は任意の式を受け取る事もできる。具体例でみると + + println(s"1 + 1 = ${1 + 1}") + +は文字列 `1 + 1 = 2` と表示する。`${}` を使って任意の式を埋め込むことができる。 + +### `f` 補間子 + +文字列リテラルの先頭に `f` を追加することで、他の言語での `printf` のような簡単な書式付き文字列を作ることができる。`f` 補間子を使った場合は、全ての変数参照は `%d` のように `printf` 形式の書式を指定する必要がある。具体例で説明しよう: + + val height = 1.9d + val name = "James" + println(f"$name%s is $height%2.2f meters tall") // James is 1.90 meters tall + +`f` 補間子は型安全だ。整数のみで動作する書式に `Double` を渡すとコンパイラはエラーを表示する。例えば: + + val height: Double = 1.9d + + scala> f"$height%4d" + :9: error: type mismatch; + found : Double + required: Int + f"$height%4d" + ^ + +`f` 補間子は Java にある文字列の書式付き出力ユーティリティを利用している。`%` 文字の後に書くことが許されている書式は [Formatter の javadoc](http://docs.oracle.com/javase/jp/1.5.0/api/java/util/Formatter.html#detail) に説明されている。変数の後に `%` 文字が無い場合は、`%s` 書式 (`String`) だと仮定される。 + +### `raw` 補間子 + +`raw` 補間子は `s` 補間子に似ているが、違いは文字列リテラル内でエスケープを実行しないことだ。以下の加工文字列をみてみよう: + + scala> s"a\nb" + res0: String = + a + b + +ここで、`s` 補間子は `\n` を改行文字に置換した。`raw` 補間子はそれを行わない。 + + scala> raw"a\nb" + res1: String = a\nb + +`raw` 補間子は `\n` のような式が改行になることを回避したい場合に便利だ。 + +3つのデフォルトの補間子の他にユーザは独自の補間子を定義することもできる。 + +## カスタム補間子 + +Scala では、全ての加工文字列リテラルは簡単なコード変換だ。コンパイラが以下のような形式の文字列リテラルを見つけると + + id"string content" + +これは [`StringContext`](http://www.scala-lang.org/api/current/index.html#scala.StringContext) のインスタンスへのメソッドの呼び出し (`id`) へと変換される。このメソッドは implicit スコープ内で提供することもできる。独自の文字列の補間を定義するには、`StringContext` に新しいメソッドを追加する implicit クラスを作るだけでいい。以下に具体例で説明する: + + // 注意: 実行時のインスタンス化を避けるために AnyVal を継承する。 + // これに関しては値クラスのガイドを参照。 + implicit class JsonHelper(val sc: StringContext) extends AnyVal { + def json(args: Any*): JSONObject = sys.error("TODO - IMPLEMENT") + } + + def giveMeSomeJson(x: JSONObject): Unit = ... + + giveMeSomeJson(json"{ name: $name, id: $id }") + +この例では、文字列の補間を使って JSON リテラル構文に挑戦している。この構文を使うには implicit クラスの `JsonHelper` がスコープにある必要があり、また `json` メソッドを実装する必要がある。注意して欲しいのは書式文字列リテラルが文字列ではなく、`JSONObject` を返す点だ。 + +コンパイラが `json"{ name: $name, id: $id }"` を見つけると、以下の式に書き換える: + + new StringContext("{ name: ", ", id: ", " }").json(name, id) + +さらに、implicit クラスは以下のように書き換える: + + new JsonHelper(new StringContext("{ name: ", ", id: ", " }")).json(name, id) + +そのため、`json` メソッドは生の String 部分と渡される式の値をみることができる。シンプルな (だけどバギーな) 実装を以下に示す: + + implicit class JsonHelper(val sc: StringContext) extends AnyVal { + def json(args: Any*): JSONObject = { + val strings = sc.parts.iterator + val expressions = args.iterator + var buf = new StringBuffer(strings.next) + while(strings.hasNext) { + buf append expressions.next + buf append strings.next + } + parseJson(buf) + } + } + +加工文字列の文字列部分は `StringContext` の `parts` メンバーとして提供される。 +式の値は `json` メソッドに `args` パラメータとして渡される。`json` メソッドは、それを受け取って大きな文字列を生成した後 JSON へとパースする。より洗練された実装は文字列を生成せずに生の文字列と式の値から直接 JSON を構築するだろう。 + +## 制約 + +文字列の補間は現在パターンマッチング文の中では動作しない。この機能は Scala 2.11 リリースを予定している。 diff --git a/_ja/overviews/core/value-classes.md b/_ja/overviews/core/value-classes.md new file mode 100644 index 0000000000..406a86e4e2 --- /dev/null +++ b/_ja/overviews/core/value-classes.md @@ -0,0 +1,271 @@ +--- +layout: singlepage-overview +title: 値クラスと汎用トレイト + +partof: value-classes + +language: ja + +discourse: false +--- + +**Mark Harrah 著**
      +**Eugene Yokota 訳** + +## はじめに + +**値クラス** (value class) は実行時のオブジェクトの割り当てを回避するための Scala の新しい機構だ。 +これは新たに定義付けされる `AnyVal` のサブクラスによって実現される。 +これは [SIP-15](http://docs.scala-lang.org/sips/pending/value-classes.html) にて提案された。 +以下に最小限の値クラスの定義を示す: + + class Wrapper(val underlying: Int) extends AnyVal + +これはただ1つの、public な `val` パラメータを持ち、これが内部での実行時のデータ構造となる。 +コンパイル時の型は `Wrapper` だが、実行時のデータ構造は `Int` だ。 +値クラスは `def` を定義することができるが、`val`、`var`、または入れ子の `trait`、`class`、`object` は許されない: + + class Wrapper(val underlying: Int) extends AnyVal { + def foo: Wrapper = new Wrapper(underlying * 19) + } + +値クラスは汎用トレイト (universal trait) のみを拡張することができる。また、他のクラスは値クラスを拡張することはできない。 +汎用トレイトは `Any` を拡張するトレイトで、メンバとして `def` のみを持ち、初期化を一切行わない。 +汎用トレイトによって値クラスはメソッドの基本的な継承ができるようになるが、これはメモリ割り当てのオーバーヘッドを伴うようにもなる。具体例で説明しよう: + + trait Printable extends Any { + def print(): Unit = println(this) + } + class Wrapper(val underlying: Int) extends AnyVal with Printable + + val w = new Wrapper(3) + w.print() // Wrapper のインスタンスをここでインスタンス化する必要がある + +以下の項で用例、メモリ割り当てが発生するかしないかの詳細、および値クラスの制約を具体例を使ってみていきたい。 + +## 拡張メソッド + +値クラスの使い方の1つに implicit クラス ([SIP-13](http://docs.scala-lang.org/sips/pending/implicit-classes.html)) と組み合わせてメモリ割り当てを必要としない拡張メソッドとして使うというものがある。implicit クラスは拡張メソッドを定義するより便利な構文を提供する一方、値クラスは実行時のオーバーヘッドを無くすことができる。この良い例が標準ライブラリの `RichInt` クラスだ。これは値クラスであるため、`RichInt` のメソッドを使うのに `RichInt` のインスタンスを作る必要はない。 + +`RichInt` から抜粋した以下のコードは、それが `Int` を拡張して `3.toHexString` という式が書けるようにしていることを示す: + + implicit class RichInt(val self: Int) extends AnyVal { + def toHexString: String = java.lang.Integer.toHexString(self) + } + +実行時には、この `3.toHexString` という式は、新しくインスタンス化されるオブジェクトへのメソッド呼び出しではなく、静的なオブジェクトへのメソッド呼び出しと同様のコード (`RichInt$.MODULE$.extension$toHexString(3)`) へと最適化される。 + +## 正当性 + +値クラスのもう1つの使い方として、実行時のメモリ割り当て無しにデータ型同様の型安全性を得るというものがある。 +例えば、距離を表すデータ型はこのようなコードになるかもしれない: + + class Meter(val value: Double) extends AnyVal { + def +(m: Meter): Meter = new Meter(value + m.value) + } + +以下のような 2つの距離を加算するコード + + val x = new Meter(3.4) + val y = new Meter(4.3) + val z = x + y + +は実際には `Meter` インスタンスを割り当てず、組み込みの `Double` 型のみが実行時に使われる。 + +注意: 実際には、case class や拡張メソッドを用いてよりきれいな構文を提供することができる。 + +## メモリ割り当てが必要になるとき + +JVM は値クラスをサポートしないため、Scala は場合によっては値クラスをインスタンス化する必要がある。 +完全な詳細は [SIP-15](http://docs.scala-lang.org/sips/pending/value-classes.html) を参照してほしい。 + +### メモリ割り当ての概要 + +以下の状況において値クラスのインスタンスはインスタンス化される: + + +
        +
      1. 値クラスが別の型として扱われるとき。
      2. +
      3. 値クラスが配列に代入されるとき。
      4. +
      5. パターンマッチングなどにおいて、実行時の型検査を行うとき。
      6. +
      + +### メモリ割り当ての詳細 + +値クラスの値が、汎用トレイトを含む別の型として扱われるとき、値クラスのインスタンスの実体がインスタンス化される必要がある。 +具体例としては、以下の `Meter` 値クラスをみてほしい: + + trait Distance extends Any + case class Meter(val value: Double) extends AnyVal with Distance + +`Distance` 型の値を受け取るメソッドは実体の `Meter` インスタンスが必要となる。 +以下の例では `Meter` クラスはインスタンス化される: + + def add(a: Distance, b: Distance): Distance = ... + add(Meter(3.4), Meter(4.3)) + +`add` のシグネチャが以下のようであれば + + def add(a: Meter, b: Meter): Meter = ... + +メモリ割り当ては必要無い。 +値クラスが型引数として使われる場合もこのルールがあてはまる。 +例えば、`identify` を呼び出すだけでも `Meter` インスタンスの実体が作成されることが必要となる: + + def identity[T](t: T): T = t + identity(Meter(5.0)) + +メモリ割り当てが必要となるもう1つの状況は、配列への代入だ。たとえその値クラスの配列だったとしてもだ。具体例で説明する: + + val m = Meter(5.0) + val array = Array[Meter](m) + +この配列は、内部表現の `Double` だけではなく `Meter` の実体を格納する。 + +最後に、パターンマッチングや `asInstaneOf` のような型検査は値クラスのインスタンスの実体を必要とする: + + case class P(val i: Int) extends AnyVal + + val p = new P(3) + p match { // new P instantiated here + case P(3) => println("Matched 3") + case P(x) => println("Not 3") + } + +## 制約 + +JVM が値クラスという概念をサポートしていないこともあり、値クラスには現在いくつかの制約がある。 +値クラスの実装とその制約の詳細に関しては [SIP-15](http://docs.scala-lang.org/sips/pending/value-classes.html) を参照。 + +### 制約の概要 + +値クラスは … + + +
        +
      1. … ただ1つの public で値クラス以外の型の val パラメータを持つプライマリコンストラクタのみを持つことができる。
      2. +
      3. … specialized な型パラメータを持つことができない。
      4. +
      5. … 入れ子のローカルクラス、トレイト、やオブジェクトを持つことがでない。
      6. +
      7. equalshashCode メソッドを定義することができない。
      8. +
      9. … トップレベルクラスか静的にアクセス可能なオブジェクトのメンバである必要がある。
      10. +
      11. def のみをメンバとして持つことができる。特に、lazy valvarval をメンバとして持つことができない。
      12. +
      13. … 他のクラスによって拡張されることができない。
      14. +
      + +### 制約の具体例 + +この項ではメモリ割り当ての項で取り扱わなかった制約の具体例を色々みていく。 + +コンストラクタのパラメータを複数持つことができない: + + class Complex(val real: Double, val imag: Double) extends AnyVal + +Scala コンパイラは以下のエラーメッセージを生成する: + + Complex.scala:1: error: value class needs to have exactly one public val parameter + class Complex(val real: Double, val imag: Double) extends AnyVal + ^ + +コンストラクタのパラメータは `val` である必要があるため、名前渡しのパラメータは使うことができない: + + NoByName.scala:1: error: `val' parameters may not be call-by-name + class NoByName(val x: => Int) extends AnyVal + ^ + +Scala はコンストラクタのパラメータとして `lazy val` を許さないため、それも使うことができない。 +複数のコンストラクタを持つことができない: + + class Secondary(val x: Int) extends AnyVal { + def this(y: Double) = this(y.toInt) + } + + Secondary.scala:2: error: value class may not have secondary constructors + def this(y: Double) = this(y.toInt) + ^ + +値クラスは `lazy val` や `val` のメンバ、入れ子の `class`、`trait`、や `object` を持つことができない: + + class NoLazyMember(val evaluate: () => Double) extends AnyVal { + val member: Int = 3 + lazy val x: Double = evaluate() + object NestedObject + class NestedClass + } + + Invalid.scala:2: error: this statement is not allowed in value class: private[this] val member: Int = 3 + val member: Int = 3 + ^ + Invalid.scala:3: error: this statement is not allowed in value class: lazy private[this] var x: Double = NoLazyMember.this.evaluate.apply() + lazy val x: Double = evaluate() + ^ + Invalid.scala:4: error: value class may not have nested module definitions + object NestedObject + ^ + Invalid.scala:5: error: value class may not have nested class definitions + class NestedClass + ^ + +以下のとおり、ローカルクラス、トレイト、オブジェクトも許されないことに注意: + + class NoLocalTemplates(val x: Int) extends AnyVal { + def aMethod = { + class Local + ... + } + } + +現在の実装の制約のため、値クラスを入れ子とすることができない: + + class Outer(val inner: Inner) extends AnyVal + class Inner(val value: Int) extends AnyVal + + Nested.scala:1: error: value class may not wrap another user-defined value class + class Outer(val inner: Inner) extends AnyVal + ^ + +また、構造的部分型はメソッドのパラメータや戻り型に値クラスを取ることができない: + + class Value(val x: Int) extends AnyVal + object Usage { + def anyValue(v: { def value: Value }): Value = + v.value + } + + Struct.scala:3: error: Result type in structural refinement may not refer to a user-defined value class + def anyValue(v: { def value: Value }): Value = + ^ + +値クラスは非汎用トレイトを拡張することができない。また、値クラスを拡張することもできない。 + + trait NotUniversal + class Value(val x: Int) extends AnyVal with notUniversal + class Extend(x: Int) extends Value(x) + + Extend.scala:2: error: illegal inheritance; superclass AnyVal + is not a subclass of the superclass Object + of the mixin trait NotUniversal + class Value(val x: Int) extends AnyVal with NotUniversal + ^ + Extend.scala:3: error: illegal inheritance from final class Value + class Extend(x: Int) extends Value(x) + ^ + +2つ目のエラーメッセージは、明示的には値クラスに `final` 修飾子が指定されなくても、それが暗に指定されていることを示している。 + +値クラスが 1つのパラメータしかサポートしないことによって生じるもう1つの制約は、値クラスがトップレベルであるか、静的にアクセス可能なオブジェクトのメンバである必要がある。 +これは、入れ子になった値クラスはそれを内包するクラスへの参照を2つ目のパラメータとして受け取る必要があるからだ。 +そのため、これは許されない: + + class Outer { + class Inner(val x: Int) extends AnyVal + } + + Outer.scala:2: error: value class may not be a member of another class + class Inner(val x: Int) extends AnyVal + ^ + +しかし、これは内包するオブジェクトがトップレベルであるため許される: + + object Outer { + class Inner(val x: Int) extends AnyVal + } diff --git a/_ja/overviews/index.md b/_ja/overviews/index.md new file mode 100644 index 0000000000..c0ac69f5ce --- /dev/null +++ b/_ja/overviews/index.md @@ -0,0 +1,67 @@ +--- +layout: inner-page-no-masthead +language: ja +title: ガイドと概要 +--- + +
      +

      コアライブラリ

      +
      + * Scala コレクションライブラリ + * [はじめに](/ja/overviews/collections/introduction.html) + * [可変コレクションおよび不変コレクション](/ja/overviews/collections/overview.html) + * [Traversable トレイト](/ja/overviews/collections/trait-traversable.html) + * [Iterable トレイト](/ja/overviews/collections/trait-iterable.html) + * [列トレイト Seq、IndexedSeq、および LinearSeq](/ja/overviews/collections/seqs.html) + * [集合](/ja/overviews/collections/sets.html) + * [マップ](/ja/overviews/collections/maps.html) + * [具象不変コレクションクラス](/ja/overviews/collections/concrete-immutable-collection-classes.html) + * [具象可変コレクションクラス](/ja/overviews/collections/concrete-mutable-collection-classes.html) + * [配列](/ja/overviews/collections/arrays.html) + * [文字列](/ja/overviews/collections/strings.html) + * [性能特性](/ja/overviews/collections/performance-characteristics.html) + * [等価性](/ja/overviews/collections/equality.html) + * [ビュー](/ja/overviews/collections/views.html) + * [イテレータ](/ja/overviews/collections/iterators.html) + * [コレクションの作成](/ja/overviews/collections/creating-collections-from-scratch.html) + * [Java と Scala 間のコレクションの変換](/ja/overviews/collections/conversions-between-java-and-scala-collections.html) + * [Scala 2.7 からの移行](/ja/overviews/collections/migrating-from-scala-27.html) + * [文字列の補間](/ja/overviews/core/string-interpolation.html) New in 2.10 + * [値クラスと汎用トレイト](/ja/overviews/core/value-classes.html) New in 2.10 + +
      +

      並列および並行プログラミング

      +
      + * [Future と Promise](/ja/overviews/core/futures.html) New in 2.10 + * Scala 並列コレクションライブラリ + * [概要](/ja/overviews/parallel-collections/overview.html) + * [具象並列コレクションクラス](/ja/overviews/parallel-collections/concrete-parallel-collections.html) + * [並列コレクションへの変換](/ja/overviews/parallel-collections/conversions.html) + * [並行トライ](/ja/overviews/parallel-collections/ctries.html) + * [並列コレクションライブラリのアーキテクチャ](/ja/overviews/parallel-collections/architecture.html) + * [カスタム並列コレクションの作成](/ja/overviews/parallel-collections/custom-parallel-collections.html) + * [並列コレクションの設定](/ja/overviews/parallel-collections/configuration.html) + * [性能の測定](/ja/overviews/parallel-collections/performance.html) + +
      +

      メタプログラミング

      +
      + * リフレクション Experimental + * [概要](/ja/overviews/reflection/overview.html) + * [環境、ユニバース、ミラー](/ja/overviews/reflection/environment-universes-mirrors.html) + * [シンボル、構文木、型](/ja/overviews/reflection/symbols-trees-types.html) + * [アノテーション、名前、スコープ、その他](/ja/overviews/reflection/annotations-names-scopes.html) + * [型タグとマニフェスト](/ja/overviews/reflection/typetags-manifests.html) + * [スレッドセーフティ](/ja/overviews/reflection/thread-safety.html) + * マクロ Experimental + * [ユースケース](/ja/overviews/macros/usecases.html) + * [blackbox vs whitebox](/ja/overviews/macros/blackbox-whitebox.html) + * [def マクロ](/ja/overviews/macros/overview.html) + * [準クォート](/ja/overviews/quasiquotes/intro.html) + * [マクロバンドル](/ja/overviews/macros/bundles.html) + * [implicit マクロ](/ja/overviews/macros/implicits.html) + * [抽出子マクロ](/ja/overviews/macros/extractors.html) + * [型プロバイダ](/ja/overviews/macros/typeproviders.html) + * [マクロアノテーション](/ja/overviews/macros/annotations.html) + * [マクロパラダイス](/ja/overviews/macros/paradise.html) + * [ロードマップ](/ja/overviews/macros/roadmap.html) diff --git a/_ja/overviews/macros/annotations.md b/_ja/overviews/macros/annotations.md new file mode 100644 index 0000000000..e609a667be --- /dev/null +++ b/_ja/overviews/macros/annotations.md @@ -0,0 +1,110 @@ +--- +layout: multipage-overview + +language: ja +discourse: false + +partof: macros +overview-name: Macros + +num: 9 + +title: マクロアノテーション +--- +MACRO PARADISE + +**Eugene Burmako 著**
      +**Eugene Yokota 訳** + +マクロアノテーションはマクロパラダイスプラグインからのみ利用可能だ (Scala 2.10.x、2.11.x、2.12.x 系列全て同様)。 +この機能が正式な Scala に入る可能性は、Scala 2.13 には残されているが、一切保証されていない。 +[マクロパラダイス](/ja/overviews/macros/paradise.html)ページの説明にしたがってコンパイラプラグインをダウンロードしてほしい。 + +## 一巡り + +マクロアノテーションは定義レベルでテキスト抽象化を実現する。Scala がマクロだと認識可能な定義であればトップレベルでも入れ子の定義でも、このアノテーションを付けることで (1つまたは複数の) メンバに展開させることができる。マクロパラダイスの以前のバージョンと比較して、2.0 のマクロパラダイスは以下の点を改善した: + +
        +
      1. クラスやオブジェクトだけではなく任意の定義に適用できるようになった。
      2. +
      3. クラスを展開してコンパニオンオブジェクトを変更もしくは新規に生成できるようになった。
      4. +
      + +これでコード生成に関して様々な可能性が広がったと言える。 + +この項では、役に立たないけども例としては便利な、注釈対象をログに書き込むこと以外は何もしないマクロを書いてみよう。 +最初のステップは、`StaticAnnotation` を継承して、`macroTransform` マクロを定義する。 +(この本文の `???` は 2.10.2 以降から使えるものだ。) + + import scala.reflect.macros.Context + import scala.language.experimental.macros + import scala.annotation.StaticAnnotation + + class identity extends StaticAnnotation { + def macroTransform(annottees: Any*) = macro ??? + } + +`macroTransform` マクロは型指定の無い (untyped) 注釈対象を受け取り (Scala には他に記法が無いためこのシグネチャの型は `Any` となる)、単数もしくは複数の結果を生成する (単数の結果はそのまま返せるが、複数の結果の場合はリフレクション API に他に良い記法が無いため `Block` にラッピングして返す)。 + +この時点で、一つの注釈対象に対して単一の結果は分かるが、複数対複数のマッピングがどのようになるのか疑問に思っている方もいるだろう。この過程はルールによって決定される: + +
        +
      1. あるクラスが注釈され、それにコンパニオンがある場合は、両者ともマクロに渡される。 (しかし、逆は真ではない。もしオブジェクトが注釈され、それにコンパニオンクラスがあってもオブジェクトのみが展開される)
      2. +
      3. あるクラス、メソッド、もしくは型のパラメータが注釈される場合は、そのオーナーも展開される。まずは注釈対象、次にオーナー、そして上記のルールに従ってコンパニオンが渡される。
      4. +
      5. 注釈対象は任意の数および種類の構文木に展開することができ、コンパイラはマクロの構文木を結果の構文木に透過的に置換する。
      6. +
      7. あるクラスが同じ名前を持つクラスとオブジェクトに展開する場合は、それらはコンパニオンとなる。これにより、コンパニオンが明示的に宣言されていないクラスにもコンパニオンオブジェクトを生成することができるようになる。
      8. +
      9. トップレベルでの展開は注釈対象の数を、種類、および名前を保持しなくてはいけない。唯一の例外はクラスがクラスと同名のオブジェクトに展開できることだ。その場合は、上記のルールによってそれらは自動的にコンパニオンとなる。
      10. +
      + +以下に、`identity` アノテーションマクロの実装例を示す。 +`@identity` が値か型パラメータに適用された場合のことも考慮に入れる必要があるため、ロジックは少し複雑になっている。コンパイラプラグイン側からは容易に標準ライブラリを変更できないため、このボイラープレートをヘルパー内でカプセル化できなかったため、解法がローテクになっていることは許してほしい。 +(ちなみに、このボイラープレートそのものも適切なアノテーションマクロによって抽象化できるはずなので、将来的にはそのようなマクロが提供できるかもしれない。) + + object identityMacro { + def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = { + import c.universe._ + val inputs = annottees.map(_.tree).toList + val (annottee, expandees) = inputs match { + case (param: ValDef) :: (rest @ (_ :: _)) => (param, rest) + case (param: TypeDef) :: (rest @ (_ :: _)) => (param, rest) + case _ => (EmptyTree, inputs) + } + println((annottee, expandees)) + val outputs = expandees + c.Expr[Any](Block(outputs, Literal(Constant(())))) + } + } + + + + + + + + + + + + + + + + + + + + + + + + +
      コード例表示
      @identity class C(<empty>, List(class C))
      @identity class D; object D(<empty>, List(class D, object D))
      class E; @identity object E(<empty>, List(object E))
      def twice[@identity T]
      +(@identity x: Int) = x * 2
      (type T, List(def twice))
      +(val x: Int, List(def twice))
      + +Scala マクロの精神に則り、マクロアノテーションは柔軟性のために可能な限り型指定を無くし (untyped; マクロ展開前に型検査を必須としないこと)、利便性のために可能な限り型付けた (typed; マクロ展開前に利用可能な型情報を取得すること)。注釈対象は型指定が無いため、後付けでシグネチャ (例えばクラスメンバのリストなど) を変更できる。しかし、Scala マクロを書くということはタイプチェッカと統合するということであり、マクロアノテーションもそれは同じだ。そのため、マクロ展開時には全ての型情報を得ることができる +(例えば、包囲するプログラムに対してリフレクションを使ったり、現行スコープ内から型検査を行ったり、implicit の検索を行うことができる)。 + +## blackbox vs whitebox + +マクロアノテーションは [whitebox](/ja/overviews/macros/blackbox-whitebox.html) である必要がある。 +マクロアノテーションを [blackbox](/ja/overviews/macros/blackbox-whitebox.html) だと宣言すると正しく動作しない。 diff --git a/_ja/overviews/macros/blackbox-whitebox.md b/_ja/overviews/macros/blackbox-whitebox.md new file mode 100644 index 0000000000..148fb846d8 --- /dev/null +++ b/_ja/overviews/macros/blackbox-whitebox.md @@ -0,0 +1,58 @@ +--- +layout: multipage-overview + +language: ja +discourse: false + +partof: macros +overview-name: Macros + +num: 2 + +title: blackbox vs whitebox +--- +EXPERIMENTAL + +**Eugene Burmako 著**
      +**Eugene Yokota 訳** + +Scala 2.11.x および Scala 2.12.x 系列では、マクロ機能が blackbox と whitebox という二つに分かれることになった。 +この blackbox/whitebox の分化は Scala 2.10.x では実装されていない。Scala 2.10.x 向けのマクロパラダイスでも、これは実装されていない。 + +## マクロの成功 + +マクロが正式な Scala 2.10 リリースの一部になったことで、研究分野でも業界でもプログラマは様々な問題に対して独創的なマクロの利用方法を考えだしていて、我々の当初の期待をはるかに上回る反応を得ている。 + +エコシステムにおけるマクロがあまりにも速く重要なものとなってきているため、Scala 2.10 が実験的機能としてリリースされた数カ月後の Scala 言語チームの会議の中では、Scala 2.12 までにはマクロを標準化して Scala 言語の一人前の機能とすることが決定された。 + +追記 Scala 2.12 までにマクロを安定化させるのはそう生易しいことでは無いことが分かった。この調査を受けて、Scala でメタプログラミングを行うための新たな基盤となる [scala.meta](http://scalameta.org) が生まれ、Scala 2.12 リリースと同時に最初のベータ版を出すことを目指している。これが将来の Scala に入ることになるかもしれない。今の所は、Scala 2.12 ではマクロやリフレクション関連の変更は予定されていない。全機能が Scala 2.10 と 2.11 同様に実験的機能扱いのままで、機能が削除されることもない。本稿が書かれた背景となった状況は変わったが、書かれた内容はまだ生きているのでこのまま読み進んでほしい。 + +マクロには多くの種類があるため、それぞれを注意深く調べて、どれを標準に入れるのかを厳選することを決めた。評価するときに必然として出てきた疑問がいくつかある。何故マクロ機能はこれほどまでに成功したのか? マクロが使われる理由は何か? + +理解しづらいメタプログラミングという概念が def マクロで表現されることで馴染みやすい型付けされたメソッド呼び出しという概念に乗っかることができるからではないか、というのが我々の仮説だ。これによって、ユーザが書くコードは無闇に肥大化したり、読み易さを犠牲とせずにより多くの意味を吸収できるようになった。 + +## blackbox と whitebox マクロ + +しかし、ときとして def マクロは「ただのメソッド呼び出し」という概念を超越することがある。例えば、展開されたマクロは、元のマクロの戻り値の型よりも特化された型を持つ式を返すことが可能だ。Scala 2.10 では、StackOverflow の [Static return type of Scala macros](http://stackoverflow.com/questions/13669974/static-return-type-of-scala-macros) で解説したように、そのような展開は特化された型を保持し続けることができる。 + +この興味深い機能がもたらす柔軟性によって、[偽装型プロバイダ](http://meta.plasm.us/posts/2013/07/11/fake-type-providers-part-2/)、[具現化の拡張](/sips/pending/source-locations.html)、[関数従属性の具現化](/ja/overviews/macros/implicits.html#fundep_materialization)、[抽出子マクロ](https://github.com/paulp/scala/commit/84a335916556cb0fe939d1c51f27d80d9cf980dc)などを可能とするけども、書かれたコードの明確さ (人にとってもマシンにとっても) が犠牲になるという側面がある。 + +普通のメソッド同様に振る舞うマクロと戻り値の型を細別化 (refine) するマクロという決定的な区別を明確にするために、blackbox マクロと whitebox マクロという概念を導入することにした。型シグネチャに忠実に従うマクロは、振る舞いを理解するのに実装を知る必要が無い (ブラックボックスとして扱うことができる) ため、**blackbox マクロ** (blackbox macro) と呼ぶ。 +Scala の型システムを使ってシグネチャを持つことができないマクロは **whitebox マクロ** (whitebox macro) と呼ぶ。(whitebox def マクロもシグネチャは持つが、これらのシグネチャは近似値でしかない) + +blackbox マクロと whitebox マクロの両方とも大切なことは認識しているけども、より簡単に説明したり、規格化したり、サポートしやすい blackbox マクロの方に我々としては多くの信頼を置いている。そのため、標準化されてるマクロには blackbox マクロのみが含まれる予定だ。将来的に whitebox マクロも予定の中に入るかもしれないけども、今のところは何とも言えない。 + +## 区別の成文化 + +まずは 2.11 リリースにおいて、def マクロのシグネチャにおいて blackbox マクロと whitebox マクロを区別して、マクロによって `scalac` が振る舞いを変えられることにすることで標準化への初手とする。これは準備段階の一手で、blackbox も whitebox も Scala 2.11 では実験的機能扱いのままだ。 + +この区別は `scala.reflect.macros.Context` を `scala.reflect.macros.blackbox.Context` と `scala.reflect.macros.whitebox.Context` によって置き換えることで表現する。もしマクロの実装が第一引数として `blackbox.Context` を受け取る定義ならば、それを使う def マクロは blackbox となり、 `whitebox.Context` の方も同様となる。当然のことながら互換性のために素の `Context` も方も存在し続けることになるけども、廃止勧告を出すことで blackbox か whitebox かを選ぶことを推奨していく方向となる。 + +blackbox def マクロは Scala 2.10 の def マクロと異なる扱いとなる。Scala の型検査において以下の制限が加わる: + +1. blackbox マクロが構文木 `x` に展開するとき、展開される式は型注釈 `(x: T)` でラップされる。この `T` は blackbox マクロの宣言された戻り値の型に、マクロ適用時に一貫性を持つように型引数やパス依存性を適用したものとなる。これによって、blackbox マクロを[型プロバイダ](http://meta.plasm.us/posts/2013/07/11/fake-type-providers-part-2/)のための手段としての利用は無効となる。 +1. Scala の型推論アルゴリズムが終わった後でも blackbox マクロの適用に未決定の型パラメータが残る場合、これらの型パラメータは普通のメソッドと同様に強制的に型推論が実行される。これによって blackbox マクロから型推論に影響を与えることが不可能となり、[関数従属性の具現化](/ja/overviews/macros/implicits.html#fundep_materialization)に使うことが無効となる。 +1. blackbox マクロの適用が implicit の候補として使われる場合、implicit 検索がそのマクロを選択するまでは展開は実行されない。これによって [implicit マクロの入手可能性を動的に計算する](/sips/pending/source-locations.html)ことが無効となる。 +1. blackbox マクロの適用がパターンマッチの抽出子として使われる場合、無条件でコンパイラエラーを発生するようにして、マクロで実装された[パターンマッチングのカスタマイズ](https://github.com/paulp/scala/commit/84a335916556cb0fe939d1c51f27d80d9cf980dc)を無効となる。 + +whitebox def マクロは Scala 2.10 での def マクロ同様に動作する。一切の制限が無いため、2.10 のマクロで出来ていたことの全てが 2.11 と 2.12 でも行えるはずだ。 diff --git a/_ja/overviews/macros/bundles.md b/_ja/overviews/macros/bundles.md new file mode 100644 index 0000000000..3748fadc9d --- /dev/null +++ b/_ja/overviews/macros/bundles.md @@ -0,0 +1,51 @@ +--- +layout: multipage-overview +language: ja + +discourse: false + +partof: macros +overview-name: Macros + +num: 4 + +title: マクロバンドル +--- +EXPERIMENTAL + +**Eugene Burmako 著**
      +**Eugene Yokota 訳** + +マクロバンドル (macro bundle) は、Scala 2.11.x および Scala 2.12.x 系列に含まれる機能だ。マクロバンドルは Scala 2.10.x では実装されていない。Scala 2.10.x 向けのマクロパラダイスでも、これは実装されていない。 + +## マクロバンドル + +Scala 2.10.x においてマクロ実装は関数として表されている。コンパイラがマクロ定義の適用を見つけると、マクロ実装を呼び出すという単純なものだ。しかし、実際に使ってみると以下の理由によりただの関数では不十分なことがあることが分かった: + +
        +
      1. 関数に制限されることで複雑なマクロのモジュール化がしづらくなる。マクロのロジックがマクロ実装外のヘルパートレイトに集中していて、マクロ実装がヘルパーをインスタンス化するだけのラッパーになってしまっているのは典型的な例だ。
      2. +
      3. さらに、マクロのパラメータがマクロのコンテキストにパス依存であるため、ヘルパーと実装をつなぐのに特殊なおまじないを必要とする。
      4. +
      + +マクロバンドルは、マクロ実装を +`c: scala.reflect.macros.blackbox.Context` か +`c: scala.reflect.macros.whitebox.Context`をコンストラクタのパラメータとして受け取るクラス内で実装することで、コンテキストをマクロ実装側のシグネチャで宣言しなくても済むようになり、モジュール化を簡単にする。 + + import scala.reflect.macros.blackbox.Context + + class Impl(val c: Context) { + def mono = c.literalUnit + def poly[T: c.WeakTypeTag] = c.literal(c.weakTypeOf[T].toString) + } + + object Macros { + def mono = macro Impl.mono + def poly[T] = macro Impl.poly[T] + } + +## blackbox vs whitebox + +マクロバンドルは、[blackbox](/ja/overviews/macros/blackbox-whitebox.html) と [whitebox](/ja/overviews/macros/blackbox-whitebox.html) +の両方のマクロの実装に使うことができる。マクロバンドルのコンストラクタのパラメータに +`scala.reflect.macros.blackbox.Context` の型を渡せば blackbox マクロになって、 +`scala.reflect.macros.whitebox.Context` ならば whitebox マクロになる。 diff --git a/_ja/overviews/macros/extractors.md b/_ja/overviews/macros/extractors.md new file mode 100644 index 0000000000..f8f0d4d683 --- /dev/null +++ b/_ja/overviews/macros/extractors.md @@ -0,0 +1,87 @@ +--- +layout: multipage-overview +language: ja + +discourse: false + +partof: macros +overview-name: Macros + +num: 6 + +title: 抽出子マクロ +--- +EXPERIMENTAL + +**Eugene Burmako 著**
      +**Eugene Yokota 訳** + +抽出子マクロ (extractor macro) は、Scala 2.11.x および Scala 2.12.x 系列に含まれる機能で、Scala 2.11.0-M5 にて Paul Phillips 氏によって導入された name-based extractor によって可能となった。抽出子マクロは Scala 2.10.x では実装されていない。Scala 2.10.x 向けのマクロパラダイスでも、これは実装されていない。 + +## パターン + +具体例で説明するために、以下のような `unapply` メソッドがあるとする +(話を簡単にするために、被検査体を具象型とするが、テストで示した通りこの抽出子を多相とすることも可能だ): + + def unapply(x: SomeType) = ??? + +呼び出しの対象は (`c.prefix`) と被検査体 (scrutinee; `x` に入るもの) の型を使って +呼び出しごとに unapply の抽出シグネチャを生成するマクロをこれで書くことができ、 +その結果のシグネチャはタイプチェッカに渡される。 + +例えば、以下はパターンマッチに被検査体をそのまま返すマクロの定義だ +(複数の被抽出体を表現するシグネチャを扱うためには [scala/scala#2848](https://github.com/scala/scala/pull/2848) を参照)。 + + def unapply(x: SomeType) = macro impl + def impl(c: Context)(x: c.Tree) = { + q""" + new { + class Match(x: SomeType) { + def isEmpty = false + def get = x + } + def unapply(x: SomeType) = new Match(x) + }.unapply($x) + """ + } + + +ドメインに特化したマッチングの論理を実装するマッチャーはいいとして、その他の所でボイラープレートがかなり多いが、 +typer とのよどみない会話をお膳立てするには全ての部分が必要であると思われる。 +まだ改善の余地はあると思うが、タイプチェッカに手を入れないことには不可能だと思う。 + +このパターンは構造的部分型を使っているが、不思議なことに生成されるコードはリフレクションを使った呼び出しが含まれていない +(これは `-Xlog-reflective-calls` をかけた後で自分でも生成されたコードを読んで二重に検査した)。 +これは謎だが、抽出子マクロに性能ペナルティを受けないということなので、良いニュースだ。 + +と言いたいところだが、残念ながら、値クラスはローカルでは宣言できないため、マッチャーを値クラスにすることはできなかった。 +しかしながら、この制限が将来的に無くなることを願って、無くなり次第タレコミが入るように小鳥を仕組んできた ([neg/t5903e](https://github.com/scala/scala/blob/00624a39ed84c3fd245dd9df7454d4cec4399e13/test/files/neg/t5903e/Macros_1.scala#L1))。 + +## 用例 + +このパターンが特に有用なのは文字列補間子のパターンマッチャーで、泥臭い方法を使わずに変幻自在のパターンマッチを実装できる。 +例えば、準クォートの `unapply` はこれでハードコードしなくてすむようになる: + + def doTypedApply(tree: Tree, fun0: Tree, args: List[Tree], ...) = { + ... + fun.tpe match { + case ExtractorType(unapply) if mode.inPatternMode => + // this hardcode in Typers.scala is no longer necessary + if (unapply == QuasiquoteClass_api_unapply) macroExpandUnapply(...) + else doTypedUnapply(tree, fun0, fun, args, mode, pt) + } + } + +実装の大まかな方針としては、`c.prefix` を分解する抽出子マクロを書いて、`StringContext` のパーツを解析して、上記のコードと同様のマッチャーを生成すればいい。 + +この用例や他の抽出子マクロの用例の実装は、 +[run/t5903a](https://github.com/scala/scala/tree/00624a39ed84c3fd245dd9df7454d4cec4399e13/test/files/run/t5903a)、 +[run/t5903b](https://github.com/scala/scala/tree/00624a39ed84c3fd245dd9df7454d4cec4399e13/test/files/run/t5903b)、 +[run/t5903c](https://github.com/scala/scala/tree/00624a39ed84c3fd245dd9df7454d4cec4399e13/test/files/run/t5903c)、 +[run/t5903d](https://github.com/scala/scala/tree/00624a39ed84c3fd245dd9df7454d4cec4399e13/test/files/run/t5903d) +などのテストケースを参照してほしい。 + +## blackbox vs whitebox + +抽出子マクロは [whitebox](/ja/overviews/macros/blackbox-whitebox.html) である必要がある。 +抽出子マクロを [blackbox](/ja/overviews/macros/blackbox-whitebox.html) だと宣言すると正しく動作しない。 diff --git a/_ja/overviews/macros/implicits.md b/_ja/overviews/macros/implicits.md new file mode 100644 index 0000000000..27f0cd430b --- /dev/null +++ b/_ja/overviews/macros/implicits.md @@ -0,0 +1,137 @@ +--- +layout: multipage-overview + +language: ja +discourse: false + +partof: macros +overview-name: Macros + +num: 5 + +title: implicit マクロ +--- +EXPERIMENTAL + +**Eugene Burmako 著**
      +**Eugene Yokota 訳** + +implicit マクロは Scala 2.10.0 以降にある実験的機能だが、クリティカルなバグ修正があったため 2.10.2 以降で完全に動作するようになった。 +2.10.x と 2.11 のどちらでも、implicit マクロを使うのにマクロパラダイスは必要ない。 + +implicit マクロの拡張の 1つである関数従属性の具現化 (fundep materialization) は、2.10.0 から 2.10.4 からは使えないが、 +[マクロパラダイス](/ja/overviews/macros/paradise.html)、2.10.5、と 2.11.x で実装された。 +2.10.0 から 2.10.4 において関数従属性の具現化を拡張するのにもマクロパラダイスが必要となるため、その関数従属性の具現化を使うためにはユーザのビルドにもマクロパラダイスを含めなければいけないことに注意してほしい。 +しかし、関数従属性の具現化が展開した後ならば、その結果のコードを参照するのにはコンパイル時でも実行時でもマクロパラダイスは必要ない。 +また、2.10.5 では関数従属性の具現化の展開にはマクロパラダイスは必要ないが、ユーザ側で -Yfundep-materialization というコンパイラフラグを有効にする必要がある。 + +## implicit マクロ + +### 型クラス + +以下の例ではデータの表示を抽象化する `Showable` という型クラスを定義する。 +`show` メソッドは、明示的なパラメータとしてターゲット、そして暗黙のパラメータとして `Showable` のインスタンスという 2つのパラメータを受け取る: + + trait Showable[T] { def show(x: T): String } + def show[T](x: T)(implicit s: Showable[T]) = s.show(x) + +このように宣言された後、`show` はターゲットのみを渡すことで呼び出すことができる。 +もう一つのパラメータは `scalac` が call site のスコープ内からターゲットの型に対応する型クラスのインスタンスを導き出そうとする。もしスコープ内にマッチする暗黙の値があれば、それが推論されコンパイルは成功する。見つからなければ、コンパイルエラーが発生する。 + + implicit object IntShowable extends Showable[Int] { + def show(x: Int) = x.toString + } + show(42) // "42" + show("42") // compilation error + +### 蔓延するボイラープレート + +特に Scala における型クラスにおいてよく知られている問題の一つとして似た型のインスタンス定義が往々にして非常に似通ったものになりやすく、ボイラープレートコードの蔓延につながることが挙げられる。 + +例えば、多くのオブジェクトの場合において整形表示はクラス名を表示した後フィールドを表示するという形になる。 +これは簡潔な方法だが、実際にやってみると簡潔に実装することが大変難しく、繰り返し似たコードを書くはめになる。 + + class C(x: Int) + implicit def cShowable = new Showable[C] { + def show(c: C) = "C(" + c.x + ")" + } + + class D(x: Int) + implicit def dShowable = new Showable[D] { + def show(d: D) = "D(" + d.x + ")" + } + +このユースケースに限ると実行時リフレクションを用いて実装することができるが、リフレクションは往々にして型消去のために不正確すぎるか、オーバーヘッドのために遅すぎることが多い。 + +Lars Hupel 氏が紹介した [`TypeClass` 型クラステクニック](http://typelevel.org/blog/2013/06/24/deriving-instances-1.html)のような型レベルプログラミングに基づいたジェネリックプログラミングという方法もあるが、やはりこれも手書きの型クラスのインスタンスに比べると性能が劣化するのが現状だ。 + +### implicit の具現化 + +implicit マクロを用いることで、型クラスのインスタンスを手書きで定義する必要を無くし、性能を落とさずにボイラプレートを一切無くすことができる。 + + trait Showable[T] { def show(x: T): String } + object Showable { + implicit def materializeShowable[T]: Showable[T] = macro ... + } + +複数のインスタンス定義を書く代わりに、プログラマは、`Showable` 型クラスのコンパニオンオブジェクト内に `materializeShowable` マクロを一度だけ定義する。これにより `Showable` のインスタンスが明示的に提供されなければ materializer が呼び出される。呼び出された materializer は `T` の型情報を取得して、適切な `Showable` 型クラスのインスタンスを生成する。 + +implicit マクロの長所は、それが既存の implicit 検索のインフラに自然と溶け込むことだ。 +Scala implicit の標準機能である複数のパラメータや重複したインスタンスなどもプログラマ側は特に何もせずに implicit マクロから使うことができる。例えば、整形表示可能な要素を持つリストのためのマクロを使わない整形表示を実装して、それをマクロベースの具現化に統合させるといったことも可能だ。 + + implicit def listShowable[T](implicit s: Showable[T]) = + new Showable[List[T]] { + def show(x: List[T]) = { x.map(s.show).mkString("List(", ", ", ")") + } + } + show(List(42)) // prints: List(42) + +この場合、必須のインスタンスである `Showable[Int]` は先に定義した具現化マクロによって生成される。つまり、マクロを implicit にすることで型クラスインスタンスの具現化を自動化すると同時にマクロを使わない implicit もシームレスに統合することができる。 + +  + +## 関数従属性の具現化 + +### 動機となった具体例 + +関数従属性 (functional dependency; fundep) の具現化が生まれるキッカケとなったのは Miles Sabin さんと氏の [shapeless](https://github.com/milessabin/shapeless) ライブラリだ。2.0.0 以前のバージョンの shapeless において Miles は型間の同型射 (isomorphism) を表す `Iso` トレイトを定義していた。例えば `Iso` を使ってケースクラスとタプル間を投射することができる (実際には shapeless は `Iso` を用いてケースクラスと HList の変換を行うが、話を簡略化するためにここではタプルを用いる)。 + + trait Iso[T, U] { + def to(t: T) : U + def from(u: U) : T + } + + case class Foo(i: Int, s: String, b: Boolean) + def conv[C, L](c: C)(implicit iso: Iso[C, L]): L = iso.from(c) + + val tp = conv(Foo(23, "foo", true)) + tp: (Int, String, Boolean) + tp == (23, "foo", true) + +ここで我々は `Iso` のための implicit materializer を書こうとしたが、壁にあたってしまった。 +`conv` のような関数の適用を型検査するときに scala は型引数の `L` を推論しなければいけないが、お手上げ状態になってしまう (ドメインに特化した知識なので仕方がない)。 +結果として、`Iso[C, L]` を合成する implicit マクロを定義しても、scalac はマクロ展開時に `L` を `Nothing` だと推論してしまい、全てが崩れてしまう。 + +### 提案 + +[https://github.com/scala/scala/pull/2499](https://github.com/scala/scala/pull/2499) が示すとおり、上記の問題の解法は非常にシンプルでエレガントなものだ。 + +Scala 2.10 においてはマクロの適用は全ての型引数が推論されるまでは展開されない。しかし、そうする必要は特に無い。 +タイプチェッカはできる所まで推論して (この例の場合、`C` は `Foo` と推論され、`L` は未定となる) そこで一旦停止する。その後マクロを展開して、展開された型を補助にタイプチェッカは再び以前未定だった型引数の型検査を続行する。Scala 2.11.0 ではそのように実装されている。 + +このテクニックを具体例で例示したものとして [files/run/t5923c](https://github.com/scala/scala/tree/7b890f71ecd0d28c1a1b81b7abfe8e0c11bfeb71/test/files/run/t5923c) テストがある。 +全てがすごくシンプルになっていることに注意してほしい。implicit マクロの `materializeIso` は最初の型引数だけを使って展開コードを生成する。 +型推論は自動的に行われるので、(推論することができなかった) 2つ目の型引数のことは分からなくてもいい。 + +ただし、`Nothing` に関してはまだ[おかしい制限](https://github.com/scala/scala/blob/7b890f71ecd0d28c1a1b81b7abfe8e0c11bfeb71/test/files/run/t5923a/Macros_1.scala)があるので注意する必要がある。 + +## blackbox vs whitebox + +本稿の前半で紹介した素の具現化は [blackbox](/ja/overviews/macros/blackbox-whitebox.html) と [whitebox](/ja/overviews/macros/blackbox-whitebox.html) のどちらでもいい。 + +blackbox な具現化と whitebox な具現化には大きな違いが一つある。blackbox な implicit マクロの展開 (例えば、明示的な `c.abort` の呼び出しや展開時の型検査の失敗) +はコンパイルエラーとなるが、whitebox な implicit マクロの展開は、実際のエラーはユーザ側には報告されずに現在の implicit 検索から implicit の候補が抜けるだけになる。 +これによって、blackbox implicit マクロの方がエラー報告という意味では良いけども、whitebox implicit マクロの方が動的に無効化できるなどより柔軟性が高いというトレードオフが生じる。 + +関数従属性の具現化は [whitebox](/ja/overviews/macros/blackbox-whitebox.html) マクロじゃないと動作しないことにも注意。 +関数従属性の具現化を [blackbox](/ja/overviews/macros/blackbox-whitebox.html) だと宣言すると正しく動作しない。 diff --git a/_ja/overviews/macros/inference.md b/_ja/overviews/macros/inference.md new file mode 100644 index 0000000000..af477dff8c --- /dev/null +++ b/_ja/overviews/macros/inference.md @@ -0,0 +1,14 @@ +--- +layout: multipage-overview +language: ja + +discourse: false + +title: 型推論補助マクロ +--- +EXPERIMENTAL + +**Eugene Burmako 著**
      +**Eugene Yokota 訳** + +このページは [/ja/overviews/macros/implicits.html](/ja/overviews/macros/implicits.html) に移動した。 diff --git a/_ja/overviews/macros/overview.md b/_ja/overviews/macros/overview.md new file mode 100644 index 0000000000..e100219d2b --- /dev/null +++ b/_ja/overviews/macros/overview.md @@ -0,0 +1,378 @@ +--- +layout: multipage-overview +language: ja + +discourse: false + +partof: macros +overview-name: Macros + +num: 3 + +title: def マクロ +--- +EXPERIMENTAL + +**Eugene Burmako 著**
      +**Eugene Yokota 訳** + +def マクロは Scala のバージョン 2.10.0 より追加された実験的機能だ。 +def マクロ機能の一部が、徹底した仕様が書かれることを条件に将来の Scala のいつかに安定化することが仮予定されている。 + +追記 このガイドは Scala 2.10.0 向けに書かれたもので、現在は Scala 2.11.x 系リリースサイクルのまっただ中なので当然本稿の内容が古くなっている。 +しかしながら、このガイドが廃れたかと言うとそうとも言えなくて、ここで書かれていることの全ては Scala 2.10.x と Scala 2.11.x +の両方で動作するため目を通す価値はあるはずだ。 +これを読んだ後で、[準クォート](/overviews/quasiquotes/intro.html)と +[マクロバンドル](/ja/overviews/macros/bundles.html)のガイドからマクロ定義を簡略化する最新情報を仕入れてほしい。 +さらに詳しい具体例を調べるには [macro workshop](https://github.com/scalamacros/macrology201) +も参考にしてほしい。 + +## 直観 + +以下がマクロ定義のプロトタイプだ: + + def m(x: T): R = macro implRef + +一見するとマクロ定義は普通の関数定義と変わらないが、違いが 1つあってそれは本文が条件付きキーワード `macro` で始まり、次に静的なマクロ実装メソッドの識別子が続くことだ。この識別子は qualify されていてもいい (つまり、`.` で区切ってスコープ外の識別子を参照してもいいということ)。 + +もし、型検査時にコンパイラがマクロ適用 `m(args)` を見つけると、コンパイラはそのマクロに対応するマクロ実装メソッドに `args` の抽象構文木を引数として渡して呼び出すことによってマクロ適用を展開する。マクロ実装の戻り値もまた抽象構文木で、コールサイトにおいてそれはインライン化され、それが再び型検査される。 + +以下のコードはマクロ実装 `Asserts.assertImpl` を参照するマクロ定義 `assert` を宣言する (`assertImpl` の定義も後でみる): + + def assert(cond: Boolean, msg: Any) = macro Asserts.assertImpl + +そのため、`assert(x < 10, "limit exceeded")` の呼び出しはコンパイル時における以下の呼び出しにつながる: + + assertImpl(c)(<[ x < 10 ]>, <[ “limit exceeded” ]>) + +ただし、`c` はコールサイトにおいてコンパイラが収集した情報を格納したコンテキスト引数で、残りの 2つの引数は、2つの式 `x < 10` と `"limit exceeded"` を表す抽象構文木。 + +本稿においては、式 `expr` を表す抽象構文木を `<[ expr ]>` と表記する。今回提唱された Scala 言語の拡張にはこの表記法に対応するものは含まれていない。実際には、構文木は `scala.reflect.api.Trees` トレイト内の型から構築され、上記の 2つの式は以下のようになる: + + Literal(Constant("limit exceeded")) + + Apply( + Select(Ident(TermName("x")), TermName("$less"), + List(Literal(Constant(10))))) + +ここに `assert` マクロの実装の一例を載せる: + + import scala.reflect.macros.Context + import scala.language.experimental.macros + + object Asserts { + def raise(msg: Any) = throw new AssertionError(msg) + def assertImpl(c: Context) + (cond: c.Expr[Boolean], msg: c.Expr[Any]) : c.Expr[Unit] = + if (assertionsEnabled) + <[ if (!cond) raise(msg) ]> + else + <[ () ]> + } + +この例が示すとおり、マクロ実装はいくつかのパラメータリストを持つ。まず `scala.reflect.macros.Context` 型の パラメータを 1つ受け取るリスト。次に、マクロ定義のパラメータと同じ名前を持つパラメータを列挙したリスト。しかし、もとのマクロのパラメータの型 `T` の代わりにマクロ実装のパラメータは `c.Expr[T]` 型を持つ。`Expr[T]` は `Context` に定義され `T` 型の抽象構文木をラッピングする。マクロ実装 `assertImpl` の戻り型もまたラッピングされた構文木で、`c.Expr[Unit]` 型を持つ。 + +また、マクロは実験的で、高度な機能だと考えられているため、マクロを定義するにはその機能を明示的に有効化する必要があることに注意してほしい。 +これは、ファイルごとに `import scala.language.experimental.macros` と書くか、コンパイルごとに (コンパイラスイッチとして) `-language:experimental.macros` を用いることで行われる。 +しかし、ユーザ側は特にコンパイラスイッチや追加の設定などで有効化しなくても普通のメソッド同様に見えるし、普通のメソッド同様に使うことができる。 + +### 多相的なマクロ + +マクロ定義とマクロ実装の両方ともジェネリックにすることができる。もしマクロ実装に型パラメータがあれば、マクロ定義の本文において実際の型引数が明示的に渡される必要がある。実装内での型パラメータは context bounds の `WeakTypeTag` と共に宣言することができる。その場合、適用サイトでの実際の型引数を記述した型タグがマクロの展開時に一緒に渡される。 + +以下のコードはマクロ実装 `QImpl.map` を参照するマクロ定義 `Queryable.map` を宣言する: + + class Queryable[T] { + def map[U](p: T => U): Queryable[U] = macro QImpl.map[T, U] + } + + object QImpl { + def map[T: c.WeakTypeTag, U: c.WeakTypeTag] + (c: Context) + (p: c.Expr[T => U]): c.Expr[Queryable[U]] = ... + } + +ここで、型が `Queryable[String]` である値 `q` があるとして、そのマクロ呼び出し + + q.map[Int](s => s.length) + +を考える。この呼び出しは以下の reflective なマクロ呼び出しに展開される。 + + QImpl.map(c)(<[ s => s.length ]>) + (implicitly[WeakTypeTag[String]], implicitly[WeakTypeTag[Int]]) + +## 完全な具体例 + +この節ではコンパイル時に文字列を検査して形式を適用する `printf` マクロを具体例として、最初から最後までの実装をみていく。 +説明を簡略化するために、ここではコンソールの Scala コンパイラを用いるが、後に説明があるとおりマクロは Maven や sbt からも使える。 + +マクロを書くには、まずマクロの窓口となるマクロ定義から始める。 +マクロ定義はシグネチャに思いつくまま好きなものを書ける普通の関数だ。 +しかし、その本文は実装への参照のみを含む。 +前述のとおり、マクロを定義するは `scala.language.experimental.macros` をインポートするか、特殊なコンパイラスイッチ `-language:experimental.macros` を用いて有効化する必要がある。 + + import scala.language.experimental.macros + def printf(format: String, params: Any*): Unit = macro printf_impl + +マクロ実装はそれを使うマクロ定義に対応する必要がある (通常は 1つだが、複数のマクロ定義を宣言することもできる)。簡単に言うと、マクロ定義のシグネチャ内の全ての型 `T` のパラメータはマクロ実装のシグネチャ内では `c.Expr[T]` となる必要がある。このルールの完全なリストはかなり込み入ったものだが、これは問題とならない。もしコンパイラが気に入らなければ、エラーメッセージに期待されるシグネチャを表示するからだ。 + + import scala.reflect.macros.Context + def printf_impl(c: Context)(format: c.Expr[String], params: c.Expr[Any]*): c.Expr[Unit] = ... + +コンパイラ API は `scala.reflect.macros.Context` から使うことができる。そのうち最も重要な部分であるリフレクション API は `c.universe` から使える。 +よく使われる多くの関数や型を含むため、`c.universe._` をインポートするのが慣例となっている: + + import c.universe._ + +まずマクロは渡された書式文字列をパースする必要がある。 +マクロはコンパイル時に実行されるため、値ではなく構文木に対してはたらく。 +そのため、`printf` マクロの書式文字列のパラメータは `java.lang.String` 型のオブジェクトではなくコンパイル時リテラルとなる。 +また、`printf(get_format(), ...)` だと `format` は文字列リテラルではなく関数の適用を表す AST であるため、以下のコードでは動作しない。 + + val Literal(Constant(s_format: String)) = format.tree + +典型的なマクロは Scala のコードを表す AST (抽象構文木) を作成する必要がある。(このマクロも例に漏れない) +Scala コードの生成については[リフレクションの概要](http://docs.scala-lang.org/ja/overviews/reflection/overview.html)を参照してほしい。AST の作成の他に以下のコードは型の操作も行う。 +`Int` と `String` に対応する Scala 型をどうやって取得しているのかに注目してほしい。 +リンクしたリフレクションの概要で型の操作の詳細を説明する。 +コード生成の最終ステップでは、全ての生成されたコードを `Block` へと組み合わせる。 +`reify` は AST を簡単に作成する方法を提供する。 + + val evals = ListBuffer[ValDef]() + def precompute(value: Tree, tpe: Type): Ident = { + val freshName = TermName(c.fresh("eval$")) + evals += ValDef(Modifiers(), freshName, TypeTree(tpe), value) + Ident(freshName) + } + + val paramsStack = Stack[Tree]((params map (_.tree)): _*) + val refs = s_format.split("(?<=%[\\w%])|(?=%[\\w%])") map { + case "%d" => precompute(paramsStack.pop, typeOf[Int]) + case "%s" => precompute(paramsStack.pop, typeOf[String]) + case "%%" => Literal(Constant("%")) + case part => Literal(Constant(part)) + } + + val stats = evals ++ refs.map(ref => reify(print(c.Expr[Any](ref).splice)).tree) + c.Expr[Unit](Block(stats.toList, Literal(Constant(())))) + +以下のコードは `printf` マクロの完全な定義を表す。 +追随するには、空のディレクトリを作り、コードを `Macros.scala` という名前の新しいファイルにコピーする。 + + import scala.reflect.macros.Context + import scala.collection.mutable.{ListBuffer, Stack} + + object Macros { + def printf(format: String, params: Any*): Unit = macro printf_impl + + def printf_impl(c: Context)(format: c.Expr[String], params: c.Expr[Any]*): c.Expr[Unit] = { + import c.universe._ + val Literal(Constant(s_format: String)) = format.tree + + val evals = ListBuffer[ValDef]() + def precompute(value: Tree, tpe: Type): Ident = { + val freshName = TermName(c.fresh("eval$")) + evals += ValDef(Modifiers(), freshName, TypeTree(tpe), value) + Ident(freshName) + } + + val paramsStack = Stack[Tree]((params map (_.tree)): _*) + val refs = s_format.split("(?<=%[\\w%])|(?=%[\\w%])") map { + case "%d" => precompute(paramsStack.pop, typeOf[Int]) + case "%s" => precompute(paramsStack.pop, typeOf[String]) + case "%%" => Literal(Constant("%")) + case part => Literal(Constant(part)) + } + + val stats = evals ++ refs.map(ref => reify(print(c.Expr[Any](ref).splice)).tree) + c.Expr[Unit](Block(stats.toList, Literal(Constant(())))) + } + } + +`printf` マクロを使うには、同じディレクトリ内に別のファイル `Test.scala` を作って以下のコードをコピーする。 +マクロを使用するのは関数を呼び出すのと同じぐらいシンプルであることに注目してほしい。`scala.language.experimental.macros` をインポートする必要も無い。 + + object Test extends App { + import Macros._ + printf("hello %s!", "world") + } + +マクロ機構の重要な一面は別コンパイルだ。マクロ展開を実行するためには、コンパイラはマクロ実装を実行可能な形式で必要とする。そのため、マクロ実装はメインのコンパイルを行う前にコンパイルされている必要がある。 +これをしないと、以下のようなエラーをみることになる: + + ~/Projects/Kepler/sandbox$ scalac -language:experimental.macros Macros.scala Test.scala + Test.scala:3: error: macro implementation not found: printf (the most common reason for that is that + you cannot use macro implementations in the same compilation run that defines them) + pointing to the output of the first phase + printf("hello %s!", "world") + ^ + one error found + + ~/Projects/Kepler/sandbox$ scalac Macros.scala && scalac Test.scala && scala Test + hello world! + +## コツとトリック + +### コマンドライン Scala コンパイラを用いてマクロを使う + +このシナリオは前節で説明したとおりだ。つまり、マクロとそれを使用するコードを別に呼び出した `scalac` によってコンパイルすることで、全てうまくいくはずだ。REPL をつかっているなら、さらに都合がいい。なぜなら REPL はそれぞれの行を独立したコンパイルとして扱うため、マクロを定義してすぐに使うことができる。 + +  +### Maven か sbt を用いてマクロを使う + +本稿での具体例では最もシンプルなコマンドラインのコンパイルを使っているが、マクロは Maven や sbt などのビルドツールからも使うことができる。完結した具体例としては [https://github.com/scalamacros/sbt-example](https://github.com/scalamacros/sbt-example) か [https://github.com/scalamacros/maven-example](https://github.com/scalamacros/maven-example) を見てほしいが、要点は以下の 2点だ: + +
        +
      • マクロは、scala-reflect.jar をライブラリ依存性として必要とする。
      • +
      • 別コンパイル制約により、マクロは別のプロジェクト内で定義する必要がある。
      • +
      + +### Scala IDE か Intellij IDEA を用いてマクロを使う + +別プロジェクトに分かれている限り、Scala IDE と Intellij IDEA の両方において、マクロは正しく動作することが分かっている。 + +### マクロのデバッグ + +マクロのデバッグ、すなわちマクロ展開を駆動している論理のデバッグは比較的容易だ。マクロはコンパイラ内で展開されるため、デバッガ内でコンパイラを実行するだけでいい。そのためには、以下を実行する必要がある: + +
        +
      1. デバッグ設定のクラスパスに Scala home の lib ディレクトリ内の全て (!) のライブラリを追加する。(これは、scala-library.jarscala-reflect.jar、そして scala-compiler.jar の jar ファイルを含む。
      2. +
      3. scala.tools.nsc.Main をエントリーポイントに設定する。
      4. +
      5. JVM のシステムプロパティに -Dscala.usejavacp=true を渡す (とても重要!)
      6. +
      7. コンパイラのコマンドラインの引数を -cp <マクロのクラスへのパス> Test.scala
      8. に設定する。ただし、Test.scala は展開されるマクロの呼び出しを含むテストファイルとする。 +
      + +上の手順をふめば、マクロ実装内にブレークポイントを置いてデバッガを起動できるはずだ。 + +ツールによる特殊なサポートが本当に必要なのはマクロ展開の結果 (つまり、マクロによって生成されたコード) のデバッグだ。このコードは手動で書かれていないため、ブレークポイントを設置することはできず、ステップ実行することもできない。Scala IDE と Intellij IDEA のチームはいずれそれぞれのデバッガにこのサポートを追加することになると思うが、それまでは展開されたマクロをデバッグする唯一の方法は `-Ymacro-debug-lite` という print を使った診断だけだ。これは、マクロによって生成されたコードを表示して、また生成されたコードの実行を追跡して println する。 + +### 生成されたコードの検査 + +`-Ymacro-debug-lite` を用いることで展開されたコードを準 Scala 形式と生の AST 形式の両方でみることができる。それぞれに利点があり、前者は表層的な解析に便利で、後者はより詳細なデバッグに不可欠だ。 + + ~/Projects/Kepler/sandbox$ scalac -Ymacro-debug-lite Test.scala + typechecking macro expansion Macros.printf("hello %s!", "world") at + source-C:/Projects/Kepler/sandbox\Test.scala,line-3,offset=52 + { + val eval$1: String = "world"; + scala.this.Predef.print("hello "); + scala.this.Predef.print(eval$1); + scala.this.Predef.print("!"); + () + } + Block(List( + ValDef(Modifiers(), TermName("eval$1"), TypeTree().setType(String), Literal(Constant("world"))), + Apply( + Select(Select(This(TypeName("scala")), TermName("Predef")), TermName("print")), + List(Literal(Constant("hello")))), + Apply( + Select(Select(This(TypeName("scala")), TermName("Predef")), TermName("print")), + List(Ident(TermName("eval$1")))), + Apply( + Select(Select(This(TypeName("scala")), TermName("Predef")), TermName("print")), + List(Literal(Constant("!"))))), + Literal(Constant(()))) + +### 捕獲されない例外を投げるマクロ + +マクロが捕獲されない例外を投げるとどうなるだろうか?例えば、`printf` に妥当ではない入力を渡してクラッシュさせてみよう。 +プリントアウトが示すとおり、特に劇的なことは起きない。コンパイラは自身を行儀の悪いマクロから守る仕組みになっているため、スタックトレースのうち関係のある部分を表示してエラーを報告するだけだ。 + + ~/Projects/Kepler/sandbox$ scala + Welcome to Scala version 2.10.0-20120428-232041-e6d5d22d28 (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_25). + Type in expressions to have them evaluated. + Type :help for more information. + + scala> import Macros._ + import Macros._ + + scala> printf("hello %s!") + :11: error: exception during macro expansion: + java.util.NoSuchElementException: head of empty list + at scala.collection.immutable.Nil$.head(List.scala:318) + at scala.collection.immutable.Nil$.head(List.scala:315) + at scala.collection.mutable.Stack.pop(Stack.scala:140) + at Macros$$anonfun$1.apply(Macros.scala:49) + at Macros$$anonfun$1.apply(Macros.scala:47) + at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:237) + at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:237) + at scala.collection.IndexedSeqOptimized$class.foreach(IndexedSeqOptimized.scala:34) + at scala.collection.mutable.ArrayOps.foreach(ArrayOps.scala:39) + at scala.collection.TraversableLike$class.map(TraversableLike.scala:237) + at scala.collection.mutable.ArrayOps.map(ArrayOps.scala:39) + at Macros$.printf_impl(Macros.scala:47) + + printf("hello %s!") + ^ + +### 警告とエラーの報告 + +ユーザと対話するための正式な方法は `scala.reflect.macros.FrontEnds` のメソッドを使うことだ。 +`c.error` はコンパイルエラーを報告し、`c.info` は警告を発令し、`c.abort` はエラーを報告しマクロの実行を停止する。 + + scala> def impl(c: Context) = + c.abort(c.enclosingPosition, "macro has reported an error") + impl: (c: scala.reflect.macros.Context)Nothing + + scala> def test = macro impl + defined term macro test: Any + + scala> test + :32: error: macro has reported an error + test + ^ + +[SI-6910](https://issues.scala-lang.org/browse/SI-6910) に記述されているとおり、現時点ではある位置から複数の警告やエラーの報告はサポートされていないことに注意してほしい。そのため、ある位置で最初のエラーか警告だけが報告され他は失くなってしまう。(ただし、同じ位置で後から報告されてもエラーは警告よりも優先される) + +  +### より大きなマクロを書く + +マクロ実装が実装メソッドの本文におさまりきらなくなって、モジュール化の必要性が出てくると、コンテキストパラメータを渡して回る必要があることに気付くだろう。マクロを定義するのに必要なもののほとんどがこのコンテキストにパス依存しているからだ。 + +1つの方法としては `Context` 型のパラメータを受け取るクラスを書いて、マクロ実装をそのクラス内のメソッドに分けるという方法がある。これは一見自然でシンプルにみえるが、実は正しく書くのは難しい。以下に典型的なコンパイルエラーを示す。 + + scala> class Helper(val c: Context) { + | def generate: c.Tree = ??? + | } + defined class Helper + + scala> def impl(c: Context): c.Expr[Unit] = { + | val helper = new Helper(c) + | c.Expr(helper.generate) + | } + :32: error: type mismatch; + found : helper.c.Tree + (which expands to) helper.c.universe.Tree + required: c.Tree + (which expands to) c.universe.Tree + c.Expr(helper.generate) + ^ + +このコードの問題はパス依存型のミスマッチだ。同じ `c` を使って helper を構築したにもかかわらず、Scala コンパイラは `impl` の `c` が `Helper` の `c` と同じものであることが分からない。 + +幸いなことに、少し助けてやるだけでコンパイラは何が起こっているのか気付くことができる。様々ある解法の1つは細別型 (refinement type) を使うことだ。以下の例はそのアイディアの最も簡単な例だ。例えば、`Context` から `Helper` への暗黙の変換を書いてやることで明示的なインスタンス化を回避して呼び出しを単純化することができる。 + + scala> abstract class Helper { + | val c: Context + | def generate: c.Tree = ??? + | } + defined class Helper + + scala> def impl(c1: Context): c1.Expr[Unit] = { + | val helper = new { val c: c1.type = c1 } with Helper + | c1.Expr(helper.generate) + | } + impl: (c1: scala.reflect.macros.Context)c1.Expr[Unit] + +もう1つの方法はコンテキストのアイデンティティを明示的な型パラメータとして渡す方法だ。`Helper` のコンストラクタが `c.type` を用いて `Helper.c` と元の `c` が同じであることを表していることに注目してほしい。Scala の型推論は単独ではこれを解くことができないため、手伝ってあげているわけだ。 + + scala> class Helper[C <: Context](val c: C) { + | def generate: c.Tree = ??? + | } + defined class Helper + + scala> def impl(c: Context): c.Expr[Unit] = { + | val helper = new Helper[c.type](c) + | c.Expr(helper.generate) + | } + impl: (c: scala.reflect.macros.Context)c.Expr[Unit] diff --git a/_ja/overviews/macros/paradise.md b/_ja/overviews/macros/paradise.md new file mode 100644 index 0000000000..47fdd6d83f --- /dev/null +++ b/_ja/overviews/macros/paradise.md @@ -0,0 +1,62 @@ +--- +layout: multipage-overview +language: ja + +discourse: false + +partof: macros +overview-name: Macros + +num: 10 + +title: マクロパラダイス +--- +NEW + +**Eugene Burmako 著**
      +**Eugene Yokota 訳** + +> I have always imagined that paradise will be a kind of library. +> Jorge Luis Borges, "Poem of the Gifts" + +マクロパラダイス (Macro paradise) とは Scala の複数のバージョンをサポートするコンパイラプラグインで、一般向けにリリースされている scalac と共に正しく動作するように設計されている。 +これによって、将来の Scala に取り込まれるよりもいち早く最新のマクロ機能を使えるようになっている。 +[サポートされている機能とバージョンの一覧](/ja/overviews/macros/roadmap.html))に関してはロードマップページを、 +動作の保証に関しては[マクロパラダイスのアナウンスメント](http://scalamacros.org/news/2013/08/07/roadmap-for-macro-paradise.html)を参照してほしい。 + + ~/210x $ scalac -Xplugin:paradise_*.jar -Xshow-phases + phase name id description + ---------- -- ----------- + parser 1 parse source into ASTs, perform simple desugaring + macroparadise 2 let our powers combine + namer 3 resolve names, attach symbols to trees in paradise + packageobjects 4 load package objects in paradise + typer 5 the meat and potatoes: type the trees in paradise + ... + +マクロパラダイスプラグインに対してコンパイル時の依存性を導入するマクロパラダイスの機能と、コンパイル時の依存性を導入しない機能があるが、どの機能も実行時にはマクロパラダイスを必要としない。 +詳細は[機能の一覧を](/ja/overviews/macros/roadmap.html)参照。 + +具体例に関しては [https://github.com/scalamacros/sbt-example-paradise](https://github.com/scalamacros/sbt-example-paradise) を参照してほしいが、要点をまとめると、マクロパラダイスを使うには以下の二行をビルド定義に加えるだけでいい +(すでに[sbt を使っている](/ja/overviews/macros/overview.html#using_macros_with_maven_or_sbt)ことが前提だが): + + resolvers += Resolver.sonatypeRepo("releases") + + addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.0-M4" cross CrossVersion.full) + +マクロパラダイスを Maven から利用するには、Stack Overflow の [Enabling the macro-paradise Scala compiler plugin in Maven projects](http://stackoverflow.com/questions/19086241/enabling-the-macro-paradise-scala-compiler-plugin-in-maven-projects) に書かれた手順に従ってほしい。 +(Sonatype snapshots と `scala-reflect.jar` への依存性を追加することにも注意) + + + + org.scalamacros + paradise_ + 2.1.0-M4 + + + +マクロパラダイスのソースは [https://github.com/scalamacros/paradise](https://github.com/scalamacros/paradise) から入手できる。 +最新の 2.10.x リリース版、 +最新の 2.11.0 リリース版、 +Scala 2.10.x、2.11.x、2.12.x 系列のスナップショット版、 +および Scala virtualized に対してそれぞれブランチがある。 diff --git a/_ja/overviews/macros/quasiquotes.md b/_ja/overviews/macros/quasiquotes.md new file mode 100644 index 0000000000..80a155847b --- /dev/null +++ b/_ja/overviews/macros/quasiquotes.md @@ -0,0 +1,15 @@ +--- +layout: multipage-overview +language: ja + +discourse: false + +partof: macros +overview-name: Macros + +num: 8 + +title: 準クォート +--- + +準クォートのガイドは [/overviews/quasiquotes/intro.html](/overviews/quasiquotes/intro.html) に移動した。 diff --git a/_ja/overviews/macros/roadmap.md b/_ja/overviews/macros/roadmap.md new file mode 100644 index 0000000000..978f893d2b --- /dev/null +++ b/_ja/overviews/macros/roadmap.md @@ -0,0 +1,42 @@ +--- +layout: multipage-overview +language: ja + +discourse: false + +partof: macros +overview-name: Macros + +num: 11 + +title: ロードマップ +--- + +EXPERIMENTAL + +**Eugene Burmako 著**
      +**Eugene Yokota 訳** + +今の所、Scala 2.12 におけるリフレクションおよびマクロ関連の新機能の導入の予定は無いため、 +バグ修正や安定化を除けば Scala 2.12 とパラダイス 2.12 の機能は Scala 2.11 とパラダイス 2.11 と同じものとなる。 + +機能的には、目下労力をさいているのは [scala.meta](http://scalameta.org) だ。 +これは Scala のメタプログラミングの新しい土台となるもので、現行の `scala.reflect` ベースのものに比べて、よりシンプルに、堅固になり、移植性にも適したものとなる予定だ。 +いつの日か scala.meta が scala.reflect を置き換えて、Scala におけるメタプログラミングの新標準となることを目指している。 + +| 機能 | Scala 2.10 | [パラダイス 2.10](/ja/overviews/macros/paradise.html) | Scala 2.11 | [パラダイス 2.11](/ja/overviews/macros/paradise.html) | Scala 2.12 | [パラダイス 2.12](/ja/overviews/macros/paradise.html) | +|-----------------------------------------------------------------------------------|----------------------------|--------------------------------------------------|----------------------------|--------------------------------------------------|-----------------------------|--------------------------------------------------| +| [blackbox と whitebox の分化](/ja/overviews/macros/blackbox-whitebox.html) | No | No 1 | Yes | Yes 1 | Yes | Yes 1 | +| [def マクロ](/ja/overviews/macros/overview.html) | Yes | Yes 1 | Yes | Yes 1 | Yes | Yes 1 | +| [マクロバンドル](/ja/overviews/macros/bundles.html) | No | No 1 | Yes | Yes 1 | Yes | Yes 1 | +| [implicit マクロ](/ja/overviews/macros/implicits.html) | Yes (since 2.10.2) | Yes 1 | Yes | Yes 1 | Yes | Yes 1 | +| [関数従属性の具現化](/ja/overviews/macros/implicits.html#fundep_materialization) | No | Yes 2 | Yes | Yes 1 | Yes | Yes 1 | +| [型プロバイダ](/ja/overviews/macros/typeproviders.html) | 一部サポート (see docs) | Yes 2 | 一部サポート (see docs) | Yes 2 | 一部サポート (see docs) | Yes 2 | +| [準クォート](/ja/overviews/quasiquotes/intro.html) | No | Yes 1 | Yes | Yes 1 | Yes | Yes 1 | +| [型マクロ](/ja/overviews/macros/typemacros.html) | No | No | No | No | No | No | +| [型指定の無いマクロ](/ja/overviews/macros/untypedmacros.html) | No | No | No | No | No | No | +| [マクロアノテーション](/ja/overviews/macros/annotations.html) | No | Yes 2 | No | Yes 2 | No | Yes 2 | + +

      1 この機能は、マクロパラダイスに対してコンパイル時でも実行時でもライブラリ依存性を導入しない。そのため、出てきたバイトコードを使ったコンパイルにも、このバイトコードの実行にもマクロパラダイスをクラスパスに通すことを必要としない。

      +

      2 この機能は、マクロパラダイスに対してコンパイル時のライブラリ依存性を導入するが、実行時には必要ない。そのため、出てきたバイトコードを使ったコンパイルをするのにユーザ側のビルドにもプラグインを追加する必要があるが、このバイトコードやこのバイトコードによってマクロ展開されたものを実行するのにクラスパスに追加されるものはない。

      +

      3 -Yfundep-materialization フラグが有効になっている場合のみ動作する。

      diff --git a/_ja/overviews/macros/typemacros.md b/_ja/overviews/macros/typemacros.md new file mode 100644 index 0000000000..d7ecd103b9 --- /dev/null +++ b/_ja/overviews/macros/typemacros.md @@ -0,0 +1,102 @@ +--- +layout: multipage-overview +language: ja + +discourse: false + +title: 型マクロ +--- +OBSOLETE + +**Eugene Burmako 著**
      +**Eugene Yokota 訳** + +型マクロ (type macro) は[マクロパラダイス](/ja/overviews/macros/paradise.html)の以前のバージョンから利用可能だったが、マクロパラダイス 2.0 ではサポートされなくなった。 +[the paradise 2.0 announcement](http://scalamacros.org/news/2013/08/05/macro-paradise-2.0.0-snapshot.html) に説明と移行のための戦略が書かれている。 + +## 直観 + +def マクロがコンパイラが特定をメソッドの呼び出しを見つけた時にカスタム関数を実行させることができるように、型マクロは特定の型が使われた時にコンパイラにフックできる。以下のコードの抜粋は、データベースのテーブルから簡単な CRUD 機能を持ったケースクラスを生成する `H2Db` マクロの定義と使用例を示す。 + + type H2Db(url: String) = macro impl + + object Db extends H2Db("coffees") + + val brazilian = Db.Coffees.insert("Brazilian", 99, 0) + Db.Coffees.update(brazilian.copy(price = 10)) + println(Db.Coffees.all) + +`H2Db` マクロの完全なソースコードは [GitHub にて](https://github.com/xeno-by/typemacros-h2db)提供して、本稿では重要な点だけをかいつまんで説明する。まず、マクロは、コンパイル時にデータベースに接続することで静的に型付けされたデータベースのラッパーを生成する。(構文木の生成に関しては[リフレクションの概要](http://docs.scala-lang.org/ja/overviews/reflection/overview.html)にて説明する) 次に、NEW `c.introduceTopLevel` API ([Scaladoc](https://scala-webapps.epfl.ch/jenkins/view/misc/job/macro-paradise211-nightly-main/ws/dists/latest/doc/scala-devel-docs/api/index.html#scala.reflect.macros.Synthetics)) を用いて生成されたラッパーをコンパイラによって管理されているトップレベル定義のリストに挿入する。最後に、マクロは生成されたクラスのスーパーコンストラクタを呼び出す `Apply` ノードを返す。注意 `c.Expr[T]` に展開される def マクロとちがって型マクロは `c.Tree` に展開されることに注意してほしい。これは、`Expr` が値を表すのに対して、型マクロは型に展開することによる。 + + type H2Db(url: String) = macro impl + + def impl(c: Context)(url: c.Expr[String]): c.Tree = { + val name = c.freshName(c.enclosingImpl.name).toTypeName + val clazz = ClassDef(..., Template(..., generateCode())) + c.introduceTopLevel(c.enclosingPackage.pid.toString, clazz) + val classRef = Select(c.enclosingPackage.pid, name) + Apply(classRef, List(Literal(Constant(c.eval(url))))) + } + + object Db extends H2Db("coffees") + // equivalent to: object Db extends Db$1("coffees") + +合成クラスを生成してその参照へと展開するかわりに、型マクロは `Template` 構文木を返すことでそのホストを変換することもできる。scalac 内部ではクラス定義とオブジェクト定義の両方とも `Template` 構文木の簡単なラッパーとして表現されているため、テンプレートへと展開することで型マクロはクラスやオブジェクトの本文全体を書き換えることができるようになる。このテクニックを活用した例も [GitHub で](https://github.com/xeno-by/typemacros-lifter)みることができる。 + + type H2Db(url: String) = macro impl + + def impl(c: Context)(url: c.Expr[String]): c.Tree = { + val Template(_, _, existingCode) = c.enclosingTemplate + Template(..., existingCode ++ generateCode()) + } + + object Db extends H2Db("coffees") + // equivalent to: object Db { + // + // + // } + +## 詳細 + +型マクロは def マクロと型メンバのハイブリッドを表す。ある一面では、型マクロはメソッドのように定義される (例えば、値の引数を取ったり、context bound な型パラメータを受け取ったりできる)。一方で、型マクロは型と同じ名前空間に属し、そのため型が期待される位置においてのみ使うことができるため、型や型マクロなどのみをオーバーライドすることができる。(より網羅的な例は [GitHub](https://github.com/scalamacros/kepler/blob/paradise/macros211/test/files/run/macro-typemacros-used-in-funny-places-a/Test_2.scala) を参照してほしい) + + + + + + + + + + + + + + + +
      機能def マクロ型マクロ型メンバ
      定義と実装に分かれているYesYesNo
      値パラメータを取ることができるYesYesNo
      型パラメータを取ることができるYesYesYes
      変位指定付きの 〃NoNoYes
      context bounds 付きの 〃YesYesNo
      オーバーロードすることができるYesYesNo
      継承することができるYesYesYes
      オーバーライドしたりされたりできるYesYesYes
      + +Scala のプログラムにおいて型マクロは、type、applied type、parent type、new、そして annotation という 5つ役割 (role) のうちの 1つとして登場する。マクロが使われた役割によって許される展開は異なっている。また、役割は NEW `c.macroRole` API ([Scaladoc](https://scala-webapps.epfl.ch/jenkins/view/misc/job/macro-paradise211-nightly-main/ws/dists/latest/doc/scala-devel-docs/api/index.html#scala.reflect.macros.Enclosures)) によって検査することができる。 + + + + + + + + + + + + +
      役割使用例クラス非クラス?Apply?Template?
      type def x: TM(2)(3) = ???YesYesNoNo
      applied type class C[T: TM(2)(3)]YesYesNoNo
      parent type class C extends TM(2)(3)
      new TM(2)(3){}
      YesNoYesYes
      new new TM(2)(3)YesNoYesNo
      annotation @TM(2)(3) class CYesNoYesNo
      + +要点をまとめると、展開された型マクロは型マクロの使用をそれが返す構文木に置き換える。ある展開が理にかなっているかどうかを考えるには、頭の中でマクロの使用例を展開される構文木で置き換えてみて結果のプログラムが正しいか確かめてみればいい。 + +例えば、 `class C extends TM(2)(3)` の中で `TM(2)(3)` のように使われている型マクロは `class C extends B(2)` となるように `Apply(Ident(TypeName("B")), List(Literal(Constant(2))))` と展開することができる。しかし、同じ展開は `TM(2)(3)` が `def x: TM(2)(3) = ???` の中の型として使われた場合は `def x: B(2) = ???` となるため、意味を成さない。(ただし、`B` そのものが型マクロではないとする。その場合は再帰的に展開され、その展開の結果がプログラムの妥当性を決定する。) + +## コツとトリック + +### クラスやオブジェクトの生成 + +[StackOverflow](http://stackoverflow.com/questions/13795490/how-to-use-type-calculated-in-scala-macro-in-a-reify-clause) でも説明したが、型マクロを作っていると `reify` がどんどん役に立たなくなっていくことに気付くだろう。その場合は、手で構文木を構築するだけではなく、マクロパラダイスにあるもう1つの実験的機能である[準クォート](/ja/overviews/quasiquotes/intro.html)を使うことも検討してみてほしい。 diff --git a/_ja/overviews/macros/typeproviders.md b/_ja/overviews/macros/typeproviders.md new file mode 100644 index 0000000000..e708c0ee59 --- /dev/null +++ b/_ja/overviews/macros/typeproviders.md @@ -0,0 +1,131 @@ +--- +layout: multipage-overview +language: ja + +discourse: false + +partof: macros +overview-name: Macros + +num: 7 + +title: 型プロバイダ +--- +EXPERIMENTAL + +**Eugene Burmako 著**
      +**Eugene Yokota 訳** + +型プロバイダ (type provider) はそれ専用にマクロの種類があるわけではなくて、すでに Scala マクロが提供する機能の上に成り立っている。 + +型プロバイダをエミュレートするのは 2通りの方法があって、構造的部分型に基づいたもの (これは「匿名型プロバイダ」と呼ぶ; anonymous type provider) と、 +マクロアノテーションに基づいたもの (これは「public 型プロバイダ」と呼ぶ; public type provider) がある。前者は 2.10.x、2.11.x、2.12.x 系列から既に使える機能を用いて実現されるが、 +後者はマクロパラダイスを必要とする。両方の方法とも消去型のプロバイダを実装するのに使うことができる。 + +マクロアノテーションのコンパイルと展開の両方にマクロパラダイスが必要なため、public 型プロバイダの作者とユーザの両方がマクロパラダイスを +ビルドに含める必要があることに注意してほしい。 +しかし、マクロアノテーションを展開した後は、その結果のコードにはマクロパラダイスへの参照は残らないため、コンパイル時にも実行時にもマクロパラダイスは必要ない。 + +## 導入 + +型プロバイダは強く型付けされた型ブリッジング機構で、F# 3.0 においてインフォメーションリッチプログラミング (information-rich programming) を可能とする。 +型プロバイダは、静的に受け取ったデータソースを元に定義群とその実装を生成するコンパイル時の仕組みだ。 +型プロバイダには、非消去 (non-erased) と消去 (erased) という 2つのモードがある。 +前者はテキストを用いたコード生成と似ていて、全ての生成された型はバイトコードになるが、後者は生成された型は型検査にだけ現れて、 +バイトコード生成が行われる前に消去されてプログラマ側で予め提供した上限境界 (upper bound) になる。 + +Scala では、マクロ展開は `ClassDef`、`ModuleDef`、`DefDef`、その他の定義ノードを含むプログラマが好きなコードを生成できるため、 +コード生成という型プロバイダの側面はすでにカバーされている。これを念頭において、型プロバイダをエミュレートするにはあと 2つの課題が残っている。 + +1. 生成された定義群を公開する (Scala 2.10.x、2.11.x、2.12.x 系列から使うことができる唯一の種類のマクロ、def マクロは展開されるスコープが制限されるという意味で局所的なものだ: [https://groups.google.com/d/msg/scala-user/97ARwwoaq2U/kIGWeiqSGzcJ](https://groups.google.com/d/msg/scala-user/97ARwwoaq2U/kIGWeiqSGzcJ)) +2. 生成された定義群を任意に消去可能とする (Scala は、抽象型メンバや値クラスといった多くの言語機構について型消去をサポートしているが、その機構は拡張可能ではないためマクロ作者がカスタマイズすることはできない。) + +## 匿名型プロバイダ + +def マクロによって展開された定義群のスコープは展開されたコードに制限されるが、これらの定義群はスコープを構造的部分型にすることで脱出可能だ。 +例えば、接続文字列を受け取って、渡されたデータベースをカプセル化したモジュールを生成する `h2db` マクロは以下のように展開する。 + + def h2db(connString: String): Any = macro ... + + // an invocation of the `h2db` macro + val db = h2db("jdbc:h2:coffees.h2.db") + + // expands into the following code + val db = { + trait Db { + case class Coffee(...) + val Coffees: Table[Coffee] = ... + } + new Db {} + } + +確かに、このままだとマクロ展開されたブロックの外部からは誰も `Coffee` クラスを直接見ることができないが、 +`db` の型を調べてみると、面白いことが分かる。 + + scala> val db = h2db("jdbc:h2:coffees.h2.db") + db: AnyRef { + type Coffee { val name: String; val price: Int; ... } + val Coffees: Table[this.Coffee] + } = $anon$1... + +見ての通り、タイプチェッカが `db` の型を推論しようとしたときに、ローカルで宣言されたクラスへの参照全てを元のクラスの公開されたメンバを全て含む構造的部分型に置き換えたみたいだ。 +こうしてできた型は生成された型の本質を表したものとなっており、それらメンバの静的型付けされたインターフェイスを提供する。 + + scala> db.Coffees.all + res1: List[Db$1.this.Coffee] = List(Coffee(Brazilian,99,0)) + +これはプロダクションで使えるバージョンの Scala で実現できるため、便利な型プロバイダの方法だと言えるが、 +構造的部分型のメンバにアクセスするのに Scala はリフレクションをつかった呼び出しを生成するので、性能に問題がある。 +これにもいくつかの対策があるが、この余白はそれを書くには狭すぎるので Travis Brown 氏の驚くべきブログシリーズを紹介する: +[その1](http://meta.plasm.us/posts/2013/06/19/macro-supported-dsls-for-schema-bindings/)、[その2](http://meta.plasm.us/posts/2013/07/11/fake-type-providers-part-2/)、 +[その3](http://meta.plasm.us/posts/2013/07/12/vampire-methods-for-structural-types/)。 + +## public 型プロバイダ + +[マクロパラダイス](/ja/overviews/macros/paradise.html)と[マクロアノテーション](/ja/overviews/macros/annotations.html)を使うことで +構造的部分型を使った回避策を使わなくても簡単に外部から見えるクラスを生成できるようになった。 +アノテーションを使った方法は率直なものなので、ここではあまり解説しない。 + + class H2Db(connString: String) extends StaticAnnotation { + def macroTransform(annottees: Any*) = macro ... + } + + @H2Db("jdbc:h2:coffees.h2.db") object Db + println(Db.Coffees.all) + Db.Coffees.insert("Brazilian", 99, 0) + +### 型消去の対策 + +これはまだ深くは研究していないけども、型メンバとシングルトン型を使えば F# 同様の消去型プロバイダを提供できるのではないかという仮説がある。 +具体的には、消去したくない型は普通に今までどおり宣言して、任意の上限境界に消去したいクラスは一意に定まる識別子を持ったシングルトン型によってパラメータ化された上限境界の型エイリアスとして提供すればいい。 +この方法を用いても全ての新しい型に対して型エイリアスのメタデータのための余計なバイトコードというオーバヘッドが発生するけども、このバイトコードは普通のクラスのバイトコードに比べると非常に小さいものとなる。 +このテクニックは匿名と public の両方の型プロバイダにあてはまる。 + + object Netflix { + type Title = XmlEntity["http://.../Title".type] + def Titles: List[Title] = ... + type Director = XmlEntity["http://.../Director".type] + def Directors: List[Director] = ... + ... + } + + class XmlEntity[Url] extends Dynamic { + def selectDynamic(field: String) = macro XmlEntity.impl + } + + object XmlEntity { + def impl(c: Context)(field: c.Tree) = { + import c.universe._ + val TypeRef(_, _, tUrl) = c.prefix.tpe + val ConstantType(Constant(sUrl: String)) = tUrl + val schema = loadSchema(sUrl) + val Literal(Constant(sField: String)) = field + if (schema.contains(sField)) q"${c.prefix}($sField)" + else c.abort(s"value $sField is not a member of $sUrl") + } + } + +## blackbox vs whitebox + +匿名と public の両方の型プロバイダとも [whitebox](/ja/overviews/macros/blackbox-whitebox.html) である必要がある。 +型プロバイダマクロを [blackbox](/ja/overviews/macros/blackbox-whitebox.html) だと宣言すると正しく動作しない。 diff --git a/_ja/overviews/macros/untypedmacros.md b/_ja/overviews/macros/untypedmacros.md new file mode 100644 index 0000000000..d8ae597381 --- /dev/null +++ b/_ja/overviews/macros/untypedmacros.md @@ -0,0 +1,65 @@ +--- +layout: multipage-overview +language: ja + +discourse: false + +title: 型指定の無いマクロ +--- +OBSOLETE + +**Eugene Burmako 著**
      +**Eugene Yokota 訳** + +型指定の無いマクロ (untyped macro) は[マクロパラダイス](/ja/overviews/macros/paradise.html)の以前のバージョンから利用可能だったが、マクロパラダイス 2.0 ではサポートされなくなった。 +[the paradise 2.0 announcement](http://scalamacros.org/news/2013/08/05/macro-paradise-2.0.0-snapshot.html) に説明と移行のための戦略が書かれている。 + +## 直観 + +静的に型付けされることは素晴らしいが、それは時として重荷ともなりうる。例えば、Alois Cochard 氏の型マクロを使った列挙型の実装の実験、いわゆる [Enum Paradise](https://github.com/aloiscochard/enum-paradise) をみてみよう。Alois は以下のように、ライトウェイトなスペックから列挙型モジュールを生成する型マクロを書かなければいけない: + + object Days extends Enum('Monday, 'Tuesday, 'Wednesday...) + +`Monday` や `Friday` のようにクリーンな識別子名を使う代わりに、タイプチェッカーが存在しない識別子に関して怒らないようにこれらの名前をクォートする必要がある。`Enum` マクロでは既存のバインディングを参照しているのではなく、新しいものを導入したいわけだが、コンパイラはマクロに渡されるものを全て型検査しようとするためこれを許さない。 + +`Enum` マクロがどのように実装されているかをマクロ定義と実装のシグネチャを読むことでみていこう。マクロ定義のシグネチャに `symbol: Symbol*` と書いてあることが分かる。これで、対応する引数の型検査をコンパイラに強制している: + + type Enum(symbol: Symbol*) = macro Macros.enum + object Macros { + def enum(c: Context)(symbol: c.Expr[Symbol]*): c.Tree = ... + } + +型指定の無いマクロはマクロに渡された引数の型検査をコンパイラの代わりに実行すると伝える記法と機構を提供する。 +そのためには、単にマクロ定義のパラメータ型をアンダースコアで置き換えマクロ定義のパラメータ型を `c.Tree` とする: + + type Enum(symbol: _*) = macro Macros.enum + object Macros { + def enum(c: Context)(symbol: c.Tree*): c.Tree = ... + } + +## 詳細 + +型検査を停止するアンダースコアは Scala プログラムの中で以下の 3ヶ所において使うことができる: + +
        +
      1. マクロへのパラメータ型
      2. +
      3. マクロへの可変長パラメータ型
      4. +
      5. マクロの戻り値型
      6. +
      + +マクロ以外や複合型の一部としてのアンダースコアの使用は期待通り動作しない。 +前者はコンパイルエラーに、後者、例えば `List[_]` は通常通り存在型を返すだろう。 + +型指定の無いマクロは抽出子マクロを可能とすることに注意してほしい: [SI-5903](https://issues.scala-lang.org/browse/SI-5903)。 +Scala 2.10.x においても `unapply` や `unaoolySeq` をマクロとして宣言することは可能だが、リンクした JIRA ケースに記述されているとおり、使い勝手は非常に制限されたものとなっている。パターンマッチング内におけるテキスト抽象化の全力は型指定の無いマクロによって発揮できるようになる。 +詳細は単体テストの [test/files/run/macro-expand-unapply-c](https://github.com/scalamacros/kepler/tree/paradise/macros/test/files/run/macro-expand-unapply-c) を参照。 + +もしマクロに型指定の無いパラメータがあった場合、マクロ展開を型付けする際にタイプチェッカーは引数に関しては何もせずに型指定の無いままマクロに渡す。もしいくつかのパラメータが型アノテーションを持っていたとしても、現行では無視される。これは将来改善される予定だ: [SI-6971](https://issues.scala-lang.org/browse/SI-6971)。引数が型検査されていないため、implicit の解決や型引数の推論は実行されない (しかし、両者ともそれぞれ `c.typeCheck` と `c.inferImplicitValue` として実行できる)。 + +明示的に渡された型引数はそのままマクロに渡される。もし型引数が渡されなかった場合は、値引数を型検査しない範囲で可能な限り型引数を推論してマクロに渡す。つまり、型引数は型検査されるということだが、この制約は将来無くなるかもしれない: [SI-6972](https://issues.scala-lang.org/browse/SI-6972)。 + +もし、def マクロが型指定の無い戻り値を持つ場合、マクロ展開後に実行される 2つの型検査のうち最初のものが省略される。ここで復習しておくと、def マクロが展開されるとまずその定義の戻り値の型に対して型検査され、次に展開されたものに期待される型に対して型検査が実行される。これに関しては Stack Overflow の [Static return type of Scala macros](http://stackoverflow.com/questions/13669974/static-return-type-of-scala-macros) を参照してほしい。型マクロは最初の型検査が行われないため、何も変わらない (そもそも型マクロに戻り値の型は指定できないからだ)。 + +最後に、型指定のないマクロのパッチは `c.Expr[T]` の代わりにマクロ実装のシグネチャのどこでも `c.Tree` を使うことを可能とする。 +マクロ定義の型指定なし/型付きと、構文木/式によるマクロ実装の 4通りの組み合わせ全てがパラメータと戻り値の型の両方においてサポートされている。 +さらに詳しいことはユニットテストを参照してほしい: [test/files/run/macro-untyped-conformance](https://github.com/scalamacros/kepler/blob/b55bda4860a205c88e9ae27015cf2d6563cc241d/test/files/run/macro-untyped-conformance/Impls_Macros_1.scala)。 diff --git a/_ja/overviews/macros/usecases.md b/_ja/overviews/macros/usecases.md new file mode 100644 index 0000000000..c5836b3d17 --- /dev/null +++ b/_ja/overviews/macros/usecases.md @@ -0,0 +1,28 @@ +--- +layout: multipage-overview + +language: ja +discourse: false + +partof: macros +overview-name: Macros + +num: 1 + +title: ユースケース +--- + +EXPERIMENTAL + +**Eugene Burmako 著**
      +**Eugene Yokota 訳** + +Scala 2.10 の実験的機能としてリリースされて以来、マクロは以前なら不可能もしくは法外に複雑だった多くのことを実現可能な領域に持ち込むことに成功した。 +Scala の商用ユーザと研究ユーザの両方がマクロを利用して様々なアイディアを形にしている。 +ここ EPFL においても我々はマクロを活用して研究を行っている。Lightbend 社もマクロを数々のプロジェクトに採用している。 +マクロはコミュニティー内でも人気があり、既にいくつかの興味深い応用が現れている。 + +最近行われた講演の ["What Are Macros Good For?"](http://scalamacros.org/paperstalks/2013-07-17-WhatAreMacrosGoodFor.pdf) では Scala 2.10 ユーザのマクロの利用方法を説明し、システム化した。講演の大筋はマクロはコード生成、静的な検査、および DSL に有効であるということで、これを研究や産業からの例を交えながら説明した。 + +Scala'13 ワークショップにおいて ["Scala Macros: Let Our Powers Combine!"](http://scalamacros.org/paperstalks/2013-04-22-LetOurPowersCombine.pdf) という論文を発表した。これは Scala 2.10 における最先端のマクロ論をより学問的な視点から説明した。 +この論文では Scala のリッチな構文と静的な型がマクロと相乗することを示し、また既存の言語機能をマクロによって新しい方法で活用できることを考察する。 diff --git a/_ja/overviews/parallel-collections/architecture.md b/_ja/overviews/parallel-collections/architecture.md new file mode 100644 index 0000000000..9cce07b915 --- /dev/null +++ b/_ja/overviews/parallel-collections/architecture.md @@ -0,0 +1,80 @@ +--- +layout: multipage-overview +title: 並列コレクションライブラリのアーキテクチャ + +discourse: false + +partof: parallel-collections +overview-name: Parallel Collections + +num: 5 +language: ja +--- + +通常の順次コレクションライブラリと同様に、Scala の並列コレクションライブラリは異なる並列コレクションの型に共通して存在する多くのコレクション演算を含む。 +これも順次コレクションで使われた手法だが、並列コレクションライブラリはほとんどの演算をいくつかある並列コレクション「テンプレート」の中で一度だけ実装することでコードの重複を回避することを目指している。これらの「テンプレート」は多くの異なる並列コレクションの実装により柔軟に継承されている。 + +この方法の利点はより簡略化された**メンテナンス性**と**拡張性**にある。 +メンテナンスに着目すると、並列コレクションの演算がそれぞれ単一の実装を持ち、全ての並列コレクションによって継承されることで、メンテナンスはより簡単にそして強固になる。なぜなら、バグフィクスはいちいち実装を重複させずに、クラスの継承を通して伝搬するからだ。 +同じ理由で、ライブラリ全体が拡張しやすくなる。新たなコレクションのクラスはその演算の大部分を単に継承すればいいからだ。 + +## 中心概念 + +前述の「テンプレート」となるトレイト群は、ほとんどの並列演算をスプリッタ (`Splitter`) とコンバイナ (`Combiner`) という二つの中心概念に基づいて実装する。 + +### スプリッタ + +その名前が示すように、スプリッタ (`Splitter`) の役割は並列コレクションを何らかの有意な方法で要素を分割することだ。 +基本的な考えとしては、コレクションを小さい部分にどんどん分割していき、逐次的に処理できるまで小さくしていくことだ。 + + trait Splitter[T] extends Iterator[T] { + def split: Seq[Splitter[T]] + } + +興味深いことに、`Splitter` は `Iterator` として実装されているため(`Iterator` の標準メソッドである `next` や `hasNext` を継承している)、フレームワークはスプリッタを分割だけではなく並列コレクションの走査にも利用できる。 +この「分割イテレータ」の特徴は、`split` メソッドが `this`(`Iterator` の一種である `Splitter`)を分割して、並列コレクション全体の要素に関する**交わりを持たない** (disjoint) の部分集合を走査する別の `Splitter` を生成することだ。 +通常のイテレータ同様に、`Splitter` は一度 `split` を呼び出すと無効になる。 + +一般的には、コレクションは `Splitter` を用いてだいたい同じサイズの部分集合に分割される。 +特に並列列などにおいて、任意のサイズの区分が必要な場合は、より正確な分割メソッドである `psplit` を実装する `PreceiseSplitter` という `Splitter` のサブタイプが用いられる。 + +### コンバイナ + +コンバイナ (`Combiner`) は、Scala の順次コレクションライブラリのビルダ (`Builder`) をより一般化したものだと考えることができる。 +それぞれの順次コレクションが `Builder` を提供するように、それぞれの並列コレクションは各自 `Combiner` を提供する。 + +順次コレクションの場合は、`Builder` に要素を追加して、`result` メソッドを呼び出すことでコレクションを生成することができた。 +並列コレクションの場合は、`Combiner` は `combine` と呼ばれるメソッドを持ち、これは別の `Combiner` を受け取り両方の要素の和集合を含む新たな `Combiner` を生成する。`combine` が呼び出されると、両方の `Combiner` とも無効化される。 + + trait Combiner[Elem, To] extends Builder[Elem, To] { + def combine(other: Combiner[Elem, To]): Combiner[Elem, To] + } + +上のコードで、二つの型パラメータ `Elem` と `To` はそれぞれ要素型と戻り値のコレクションの型を表す。 + +**注意:** `c1 eq c2` が `true` である二つの `Combiner` (つまり、同一の `Combiner` という意味だ)があるとき、`c1.combine(c2)` は常に何もせずに呼ばれた側の `Combiner` である `c1` を返す。 + +## 継承関係 + +Scala の並列コレクションは、Scala の(順次)コレクションライブラリの設計から多大な影響を受けている。 +以下に示すよう、トレイト群は通常のコレクションフレームワーク内のトレイト群を鏡写しのように対応している。 + +[]({{ site.baseurl }}/resources/images/parallel-collections-hierarchy.png) + +
      Scala のコレクションと並列コレクションライブラリの継承関係
      +
      + +並列コレクションが順次コレクションに緊密に統合することで、順次コレクションと並列コレクションの単純な置換えが可能となることを目標としている。 + +順次か並列かのどちらでもありうる(`par` と `seq` を呼び出すことで並列コレクションと順次コレクションを切り替えられるような)コレクションへの参照を持つためには、両方のコレクション型に共通するスーパー型の存在が必要となる。 +これが、上の図に出てくる `GenTraversable`、`GenIterable`、`GenSeq`、`GenMap`、`GenSet` という「一般」(general) トレイト群で、これらは順番通り (in-order) の走査も、逐次的 (one-at-a-time) な走査も保証しない。 +対応する順次トレイトや並列トレイトはこれらを継承する。 +例えば、`ParSeq` と `Seq` は両方とも一般列 `GenSeq` のサブタイプだが、お互いは継承関係に無い。 + +順次コレクションと並列コレクションの継承関係に関するより詳しい議論は、このテクニカルリポートを参照してほしい。 \[[1][1]\] + +## 参照 + +1. [On a Generic Parallel Collection Framework, Aleksandar Prokopec, Phil Bawgell, Tiark Rompf, Martin Odersky, June 2011][1] + +[1]: http://infoscience.epfl.ch/record/165523/files/techrep.pdf "flawed-benchmark" diff --git a/_ja/overviews/parallel-collections/concrete-parallel-collections.md b/_ja/overviews/parallel-collections/concrete-parallel-collections.md new file mode 100644 index 0000000000..e7d7f98eca --- /dev/null +++ b/_ja/overviews/parallel-collections/concrete-parallel-collections.md @@ -0,0 +1,166 @@ +--- +layout: multipage-overview +title: 具象並列コレクションクラス + +discourse: false + +partof: parallel-collections +overview-name: Parallel Collections + +num: 2 +language: ja +--- + +## 並列配列 + +並列配列 ([`ParArray`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/parallel/mutable/ParArray.html)) は、線形で連続的な要素の配列を保持する列だ。 +そのため、内部の配列を変更することで効率的な要素の読み込みや更新ができるようになる。 +また、要素の走査も非常に効率的だ。 +並列配列は、サイズが一定であるという意味で配列と似ている。 + + scala> val pa = scala.collection.parallel.mutable.ParArray.tabulate(1000)(x => 2 * x + 1) + pa: scala.collection.parallel.mutable.ParArray[Int] = ParArray(1, 3, 5, 7, 9, 11, 13,... + + scala> pa reduce (_ + _) + res0: Int = 1000000 + + scala> pa map (x => (x - 1) / 2) + res1: scala.collection.parallel.mutable.ParArray[Int] = ParArray(0, 1, 2, 3, 4, 5, 6, 7,... + +内部的には、並列配列の[「スプリッタ」 (splitter)](architecture.html#id1) の分割は走査用の添字を更新した二つの新たなスプリッタを作る事に結局なる。 +[「コンバイナ」 (combiner)](architecture.html#id1) はより複雑だ。多くの変換メソッドの多く(例えば、`flatMap`、`filter`、`takeWhile` など)は、事前に結果の要素数(そのため、配列のサイズ)が分からないため、それぞれのコンバイナはならし定数時間 (amortized constant time) の + `+=` 演算を持つ配列バッファの変種だ。 +異なるプロセッサがそれぞれの並列配列コンバイナに要素を追加し、後で内部の配列を連結することで合成が行われる。 +要素の総数が分かった後になってから、内部の配列が割り当てられ、並列に書き込まれる。そのため、変換メソッドは読み込みメソッドに比べて少し高価だ。また、最後の配列の割り当ては JVM上で逐次的に実行されるため、map 演算そのものが非常に安価な場合は、配列の割り当てが逐次的ボトルネックとなりうる。 + +`seq` メソッドを呼び出すことで並列配列はその順次版である `ArraySeq` に変換される。 +`ArraySeq` は元の並列配列の内部構造と同じ配列を内部で使うためこの変換は効率的だ。 + +## 並列ベクトル + +並列ベクトル ([`ParVector`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/parallel/immutable/ParVector.html)) +は、低い定数係数の対数時間で読み込みと書き込みを行う不変列だ。 + + scala> val pv = scala.collection.parallel.immutable.ParVector.tabulate(1000)(x => x) + pv: scala.collection.parallel.immutable.ParVector[Int] = ParVector(0, 1, 2, 3, 4, 5, 6, 7, 8, 9,... + + scala> pv filter (_ % 2 == 0) + res0: scala.collection.parallel.immutable.ParVector[Int] = ParVector(0, 2, 4, 6, 8, 10, 12, 14, 16, 18,... + +不変ベクトルは 32分木として表されるため、スプリッタはそれぞれのサブツリーをスプリッタに割り当てることで分割される。 +現行のコンバイナの実装はベクトルとして要素を保持し、遅延評価で要素をコピーすることで合成する。 +このため、変換メソッドは並列配列のそれに比べてスケーラビリティが低い。 +ベクトルの連結が将来の Scala リリースで提供されるようになれば、コンバイナは連結を用いて合成できるようになり、変換メソッドはより効率的になる。 + +並列ベクトルは、順次[ベクトル](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Vector.html)の並列版で、定数時間で一方から他方へと変換できる。 + +## 並列範囲 + +並列範囲 ([`ParRange`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/parallel/immutable/ParRange.html)) +は、順序付けされた等間隔の要素の列だ。 +並列範囲は、逐次版の [Range](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Range.html) と同様に作成される: + + scala> 1 to 3 par + res0: scala.collection.parallel.immutable.ParRange = ParRange(1, 2, 3) + + scala> 15 to 5 by -2 par + res1: scala.collection.parallel.immutable.ParRange = ParRange(15, 13, 11, 9, 7, 5) + +順次範囲にビルダが無いのと同様に、並列範囲にはコンバイナが無い。 +並列範囲に対する `map` 演算は並列ベクトルを生成する。 +順次範囲と並列範囲は、一方から他方へと効率的に `seq` と `par` メソッドを用いて変換できる。 + +## 並列ハッシュテーブル + +並列ハッシュテーブルは要素を内部の配列に格納し、各要素のハッシュコードにより格納する位置を決定する。 +並列可変ハッシュ集合 ( +[mutable.ParHashSet](http://www.scala-lang.org/api/{{ site.scala-version}}/scala/collection/parallel/mutable/ParHashSet.html)) +と並列可変ハッシュマップ ([mutable.ParHashMap](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/parallel/mutable/ParHashMap.html)) +はハッシュテーブルに基づいている。 + + scala> val phs = scala.collection.parallel.mutable.ParHashSet(1 until 2000: _*) + phs: scala.collection.parallel.mutable.ParHashSet[Int] = ParHashSet(18, 327, 736, 1045, 773, 1082,... + + scala> phs map (x => x * x) + res0: scala.collection.parallel.mutable.ParHashSet[Int] = ParHashSet(2181529, 2446096, 99225, 2585664,... + +並列ハッシュテーブルのコンバイナは、要素をハッシュコードの最初の文字に応じてバケットに振り分ける。 +これらは単にバケットを連結することで合成される。 +最終的なハッシュテーブルが構築されると(つまりコンバイナの `result` メソッドが呼ばれると)、 +内部の配列が割り当てられ、異なるバケットからハッシュテーブル配列の別々の連続したセグメントへ並列して要素が書き込まれる。 + +順次ハッシュマップとハッシュ集合は `par` メソッドを用いて並列のものに変換できる。 +並列ハッシュテーブルは、その内部ではいくつかの区分に分けて要素を保持しているが、それぞれの要素数を管理するサイズマップを必要とする。 +そのため、順次ハッシュテーブルが並列テーブルに最初に変換されるときにはサイズマップが作成されなければいけない。 +ここで発生するテーブルの走査により最初の `par` の呼び出しはハッシュテーブルのサイズに対して線形の時間がかかる。 +それ以降のハッシュテーブルの変更はサイズマップの状態も更新するため、以降の `par` や `seq` を用いた変換は定数時間で実行される。 +サイズマップの更新は `useSizeMap` メソッドを用いることで開始したり、中止したりできる。 +重要なのは、順次ハッシュテーブルの変更は並列ハッシュテーブルにも影響があり、またその逆も真であることだ。 + +## 並列ハッシュトライ + +並列ハッシュトライは、不変集合と不変マップを効率的に表す不変ハッシュトライの並列版だ。 +これらは、[immutable.ParHashSet](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/parallel/immutable/ParHashSet.html) クラスと +[immutable.ParHashMap](http://www.scala-lang.org/api/{{ site.scala-version}}/scala/collection/parallel/immutable/ParHashMap.html) クラスにより提供される。 + + scala> val phs = scala.collection.parallel.immutable.ParHashSet(1 until 1000: _*) + phs: scala.collection.parallel.immutable.ParHashSet[Int] = ParSet(645, 892, 69, 809, 629, 365, 138, 760, 101, 479,... + + scala> phs map { x => x * x } sum + res0: Int = 332833500 + +並列ハッシュテーブル同様に、並列ハッシュトライのコンバイナは事前に要素をバケットにソートしておき、それぞれのバケットを別のプロセッサに割り当て、それぞれがサブトライを構築することで、結果のハッシュトライを並列に構築する。 + +並列ハッシュトライは `seq` と `par` メソッドを用いることで順次ハッシュトライと定数時間で相互に変換できる。 + +## 並列並行トライ + +[concurrent.TrieMap](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/concurrent/TrieMap.html) +は、複数のスレッドから同時にアクセスできる (concurrent thread-safe) マップだが、 +[mutable.ParTrieMap](http://www.scala-lang.org/api/{{ site.scala-version}}/scala/collection/parallel/mutable/ParTrieMap.html) +は、その並列版だ。 +並列データ構造の多くは、走査時にデータ構造が変更された場合に一貫性のある走査を保証しないが、並行トライは更新が次回の走査まで見えないことを保証する。 +つまり以下の 1 から 99 の数の平方根を出力する例のように、並行トライを走査中に変更できるようになる: + + scala> val numbers = scala.collection.parallel.mutable.ParTrieMap((1 until 100) zip (1 until 100): _*) map { case (k, v) => (k.toDouble, v.toDouble) } + numbers: scala.collection.parallel.mutable.ParTrieMap[Double,Double] = ParTrieMap(0.0 -> 0.0, 42.0 -> 42.0, 70.0 -> 70.0, 2.0 -> 2.0,... + + scala> while (numbers.nonEmpty) { + | numbers foreach { case (num, sqrt) => + | val nsqrt = 0.5 * (sqrt + num / sqrt) + | numbers(num) = nsqrt + | if (math.abs(nsqrt - sqrt) < 0.01) { + | println(num, nsqrt) + | numbers.remove(num) + | } + | } + | } + (1.0,1.0) + (2.0,1.4142156862745097) + (7.0,2.64576704419029) + (4.0,2.0000000929222947) + ... + +コンバイナは、内部的には `TrieMap` として実装されている。 +これは、並行なデータ構造であるため、変換メソッドの呼び出しに対して全体で一つのコンバイナのみが作成され、全てのプロセッサによって共有される。 +他の並列可変コレクションと同様に、`TrieMap` とその並列版である `ParTrieMap` は `seq` と `par` メソッドにより取得でき、これらは同じ内部構造にデータを格納してあるため一方で行われた変更は他方にも影響がある。変換は定数時間で行われる。 + +## 性能特性 + +列型の性能特性: + +| | head | tail | apply | 更新 | 先頭に
      追加 | 最後に
      追加 | 挿入 | +| -------- | ------ | -------- | ------ | ------ | --------- | -------- | ---- | +| `ParArray` | 定数 | 線形 | 定数 | 定数 | 線形 | 線形 | 線形 | +| `ParVector` | 実質定数 | 実質定数 | 実質定数 | 実質定数 | 実質定数 | 実質定数 | - | +| `ParRange` | 定数 | 定数 | 定数 | - | - | - | - | + +集合とマップ型の性能特性: + +| | 検索 | 追加 | 削除 | +| -------- | ------ | ------- | ------ | +| **不変** | | | | +| `ParHashSet`/`ParHashMap`| 実質定数 | 実質定数 | 実質定数 | +| **可変** | | | | +| `ParHashSet`/`ParHashMap`| 定数 | 定数 | 定数 | +| `ParTrieMap` | 実質定数 | 実質定数 | 実質定数 | diff --git a/_ja/overviews/parallel-collections/configuration.md b/_ja/overviews/parallel-collections/configuration.md new file mode 100644 index 0000000000..abc16c8854 --- /dev/null +++ b/_ja/overviews/parallel-collections/configuration.md @@ -0,0 +1,69 @@ +--- +layout: multipage-overview +title: 並列コレクションの設定 + +discourse: false + +partof: parallel-collections +overview-name: Parallel Collections + +num: 7 +language: ja +--- + +## タスクサポート + +並列コレクションは、演算のスケジューリングに関してモジュール化されている。 +全ての並列コレクションはタスクサポートというオブジェクトによりパラメータ化されており、これがタスクのスケジューリングとプロセッサへの負荷分散 (load balancing) を担当する。 + +タスクサポートは内部にスレッドプールの実装への参照を持っており、タスクをより小さいタスクにいつどのように分割するかを決定している。 +この内部の振る舞いに関してより詳しく知りたい場合はこのテクノロジーレポートを参照してほしい \[[1][1]\]。 + +現行の並列コレクションにはいくつかのタスクサポートの実装がある。 +JVM 1.6 以上でデフォルトで使われるのは、`ForkJoinTaskSupport` で、これは内部でフォーク/ジョインプールを使う。 +JVM 1.5 とその他のフォーク/ジョインプールをサポートしない JVM はより効率の劣る `ThreadPoolTaskSupport` を使う。 +また、`ExecutionContextTaskSupport` は `scala.conccurent` にあるデフォルトの実行コンテクスト (execution context) の実装を使い、`scala.concurrent` で使われるスレッドプールを再利用する(これは JVM のバージョンによってフォーク/ジョインプールか `ThreadPoolExecutor` が使われる)。それぞれの並列コレクションは、デフォルトで実行コンテクストのタスクサポートに設定されているため、並列コレクションは、Future API で使われるのと同じフォーク/ジョインプールが再利用されている。 + +以下に並列コレクションのタスクサポートを変更する具体例をみてみよう: + + scala> import scala.collection.parallel._ + import scala.collection.parallel._ + + scala> val pc = mutable.ParArray(1, 2, 3) + pc: scala.collection.parallel.mutable.ParArray[Int] = ParArray(1, 2, 3) + + scala> pc.tasksupport = new ForkJoinTaskSupport(new scala.concurrent.forkjoin.ForkJoinPool(2)) + pc.tasksupport: scala.collection.parallel.TaskSupport = scala.collection.parallel.ForkJoinTaskSupport@4a5d484a + + scala> pc map { _ + 1 } + res0: scala.collection.parallel.mutable.ParArray[Int] = ParArray(2, 3, 4) + +上の例では、並列コレクションに対して並列度2のフォーク/ジョインプールを使うように設定した。 +並列コレクションを `ThreadPoolExecutor` を使うように設定する場合は: + + scala> pc.tasksupport = new ThreadPoolTaskSupport() + pc.tasksupport: scala.collection.parallel.TaskSupport = scala.collection.parallel.ThreadPoolTaskSupport@1d914a39 + + scala> pc map { _ + 1 } + res1: scala.collection.parallel.mutable.ParArray[Int] = ParArray(2, 3, 4) + +並列コレクションがシリアライズされるとき、タスクサポートフィールドはシリアライゼーションから省かれる。 +並列コレクションがデシリアライズされるとき、タスクサポートはデフォルトの値である実行コンテクストタスクサポートに設定される。 + +カスタムのタスクサポートを実装するには、`TaskSupport` トレイトを拡張して以下のメソッドを実装する: + + def execute[R, Tp](task: Task[R, Tp]): () => R + + def executeAndWaitResult[R, Tp](task: Task[R, Tp]): R + + def parallelismLevel: Int + +`execute` メソッドはタスクを非同期的にスケジューリングし、計算の結果をフューチャー値として返す。 +`executeAndWait` メソッドは同じことを行うがタスクが完了してから結果を返す。 +`parallelismLevel` はタスクサポートがタスクのスケジューリングをするのに用いる対象コア数を返す。 + +## 参照 + +1. [On a Generic Parallel Collection Framework, June 2011][1] + + [1]: http://infoscience.epfl.ch/record/165523/files/techrep.pdf "parallel-collections" diff --git a/_ja/overviews/parallel-collections/conversions.md b/_ja/overviews/parallel-collections/conversions.md new file mode 100644 index 0000000000..73345f4fd9 --- /dev/null +++ b/_ja/overviews/parallel-collections/conversions.md @@ -0,0 +1,62 @@ +--- +layout: multipage-overview +title: 並列コレクションへの変換 + +discourse: false + +partof: parallel-collections +overview-name: Parallel Collections + +num: 3 +language: ja +--- + +## 順次と並列コレクション間での変換 + +全ての順次コレクションは、`par` メソッドを用いて何らかの並列コレクションへと変換できる。 +順次コレクションのいくつかには、直接対応する並列版を持つものがあり、それらのコレクションの変換は効率的だ。 +順次と並列コレクションが同じデータ構造で表現されているため変換は定数時間で実行される(唯一の例外は初回の `par` が少し高価である可変ハッシュマップと可変ハッシュ集合だが、二回目以降の `par` の呼び出しは定数時間で行われる)。 +また、可変コレクションに関しては、同じ内部構造を共有する場合、順次コレクションで行われた更新はそれに対応する並列版からも見えていることに注意して欲しい。 + +| 順次 | 並列 | +| ------------- | -------------- | +| **可変** | | +| `Array` | `ParArray` | +| `HashMap` | `ParHashMap` | +| `HashSet` | `ParHashSet` | +| `TrieMap` | `ParTrieMap` | +| **不変** | | +| `Vector` | `ParVector` | +| `Range` | `ParRange` | +| `HashMap` | `ParHashMap` | +| `HashSet` | `ParHashSet` | + +リスト、キュー、ストリーム等、その他のコレクションは、要素を順番にアクセスしなければいけないという意味で本質的に逐次的だ。それらのコレクションは、似ている並列コレクションに要素をコピーすることで、並列版に変換される。 +例えば、リストは標準の並列不変列である並列ベクトルに変換される。 + +全ての並列コレクションは、`seq` メソッドを用いて何らかの順次コレクションに変換できる。 +並列コレクションから順次コレクションへの変換は常に効率的で、定数時間で実行される。 +並列可変コレクションに対して `seq` を呼び出すと、同じ内部構造にデータを格納した順次コレクションを返す。 +そのため、コレクションの変更は、他方にも見えることになる。 + +## 異なるコレクション型の間での変換 + +順次と並列コレクション間での変換とは直交して、コレクションは別のコレクション型でも変換できる。 +例えば、`toSeq` を呼び出すことで順次集合を順次列に変換できるが、`toSeq` を呼び出して並列集合を並列列に変換することもできる。 +一般的なルールとしては、`X` に並列版があれば、`toX` メソッドは、そのコレクションを `ParX` コレクションに変換する。 + +以下の表に全ての変換をまとめる: + +| メソッド | 戻り値の型 | +| -------------- | -------------- | +| `toArray` | `Array` | +| `toList` | `List` | +| `toIndexedSeq` | `IndexedSeq` | +| `toStream` | `Stream` | +| `toIterator` | `Iterator` | +| `toBuffer` | `Buffer` | +| `toTraversable`| `GenTraverable`| +| `toIterable` | `ParIterable` | +| `toSeq` | `ParSeq` | +| `toSet` | `ParSet` | +| `toMap` | `ParMap` | diff --git a/_ja/overviews/parallel-collections/ctries.md b/_ja/overviews/parallel-collections/ctries.md new file mode 100644 index 0000000000..f8144460f1 --- /dev/null +++ b/_ja/overviews/parallel-collections/ctries.md @@ -0,0 +1,145 @@ +--- +layout: multipage-overview +title: 並行トライ + +discourse: false + +partof: parallel-collections +overview-name: Parallel Collections + +num: 4 +language: ja +--- + +並列データ構造の多くは、走査時にデータ構造が変更された場合に一貫性のある走査を保証しない。 +これは、ほとんどの可変コレクションにも当てはまることだ。 +並行トライ (concurrent trie) は、走査中に自身の更新を許すという意味で特殊なものだと言える。 +変更は、次回以降の走査のみで見えるようになる。 +これは、順次並行トライと並列並行トライの両方に当てはまることだ。 +唯一の違いは、前者は要素を逐次的に走査するのに対して、後者は並列に走査するということだけだ。 + +この便利な特性を活かして簡単に書くことができるアルゴリズムがいくつかある。 +これらのアルゴリズムは、 要素のデータセットを反復処理するが、要素によって異なる回数繰り返す必要のあるアルゴリズムであることが多い。 + +与えられた数の集合の平方根を計算する例を以下に示す。 +繰り返すたびに平方根の値が更新される。 +平方根の値が収束すると、その数は集合から外される。 + + case class Entry(num: Double) { + var sqrt = num + } + + val length = 50000 + + // リストを準備する + val entries = (1 until length) map { num => Entry(num.toDouble) } + val results = ParTrieMap() + for (e <- entries) results += ((e.num, e)) + + // 平方根を計算する + while (results.nonEmpty) { + for ((num, e) <- results) { + val nsqrt = 0.5 * (e.sqrt + e.num / e.sqrt) + if (math.abs(nsqrt - e.sqrt) < 0.01) { + results.remove(num) + } else e.sqrt = nsqrt + } + } + +上のバビロニア法による平方根の計算 (\[[3][3]\]) は、数によっては他よりも早く収束することに注意してほしい。 +そのため、それらの数を `result` から削除して処理が必要な要素だけが走査されるようにする必要がある。 + +もう一つの例としては、幅優先探索 (breadth-first search) アルゴリズムがある。 +これは、対象となるノードが見つかるか他に見るノードが無くなるまで反復的に前線ノードを広げていく。 +ここでは、二次元の地図上のノードを `Int` のタプルとして定義する。 +`map` はブーリアン型の二次元配列として定義し、これはその位置が占有されているかを示す。 +次に、今後拡張予定の全てのノード(ノード前線)を含む `open` と、拡張済みの全てのノードを含む `closed` という二つの平行トライマップを宣言する。 +地図の四隅から探索を始めて、地図の中心までの経路を見つけたいため、`open` マップを適切なノードに初期化する。 +次に、`open` マップ内の全てのノードを拡張するノードが無くなるまで反復的に拡張する。 +ノードが拡張されるたびに、`open` マップから削除され、`closed` マップに追加される。 +完了したら、対象ノードから初期ノードまでの経路を表示する。 + + val length = 1000 + + // Node 型を定義する + type Node = (Int, Int); + type Parent = (Int, Int); + + // Node 型の演算 + def up(n: Node) = (n._1, n._2 - 1); + def down(n: Node) = (n._1, n._2 + 1); + def left(n: Node) = (n._1 - 1, n._2); + def right(n: Node) = (n._1 + 1, n._2); + + // map と target を作る + val target = (length / 2, length / 2); + val map = Array.tabulate(length, length)((x, y) => (x % 3) != 0 || (y % 3) != 0 || (x, y) == target) + def onMap(n: Node) = n._1 >= 0 && n._1 < length && n._2 >= 0 && n._2 < length + + // open マップ - ノード前線 + // closed マップ - 滞在済みのノード + val open = ParTrieMap[Node, Parent]() + val closed = ParTrieMap[Node, Parent]() + + // 初期位置をいくつか追加する + open((0, 0)) = null + open((length - 1, length - 1)) = null + open((0, length - 1)) = null + open((length - 1, 0)) = null + + // 貪欲法による幅優先探索 + while (open.nonEmpty && !open.contains(target)) { + for ((node, parent) <- open) { + def expand(next: Node) { + if (onMap(next) && map(next._1)(next._2) && !closed.contains(next) && !open.contains(next)) { + open(next) = node + } + } + expand(up(node)) + expand(down(node)) + expand(left(node)) + expand(right(node)) + closed(node) = parent + open.remove(node) + } + } + + // 経路の表示 + var pathnode = open(target) + while (closed.contains(pathnode)) { + print(pathnode + "->") + pathnode = closed(pathnode) + } + println() + +並行トライはまた、逐次化可能 (linearizable) 、ロックフリー (lock-free)、かつ計算量が定数時間の `snapshot` 演算をサポートする。 +この演算は、ある特定の時点における全ての要素を含んだ新たな並列トライを作成する。 +これは、実質的にはそのトライのその時点での状態を捕捉したことと変わらない。 +`snapshot` 演算は、並列トライの新しいルートを作成するだけだ。 +後続の更新は並列トライのうち更新に関わる部分だけを遅延評価で再構築し他の部分には手を付けない。 +まず、これはスナップショット演算に要素のコピーを伴わないため、演算が高価ではないということだ。 +次に、コピーオンライト (copy-on-write) の最適化は平行トライの一部だけをコピーするため、後続の更新は水平にスケールする。 +`readOnlySnapshot` メソッドは、`snapshot` メソッドよりも少しだけ効率が良いが、更新のできないリードオンリーなマップを返す。 +このスナップショット機構を用いて、並行トライは逐次化可能で計算量が定数時間の、`clear` メソッドも提供する。 +並行トライとスナップショットの仕組みに関してさらに知りたい場合は、\[[1][1]\] と \[[2][2]\] を参照してほしい。 + +並行トライのイテレータはスナップショットに基づいている。 +イテレータオブジェクトが作成される前に並行トライのスナップショットが取られるため、イテレータはスナップショットが作成された時点での要素のみを走査する。 +当然、イテレータはリードオンリーなスナップショットを用いる。 + +`size` 演算もスナップショットに基づいている。 +率直に実装すると、`size` を呼び出した時点でイテレータ(つまり、スナップショット)が作り、全ての要素を走査しながら数えていくというふうになる。 +そのため、`size` を呼び出すたびに要素数に対して線形の時間を要することになる。 +しかし、並行トライは異なる部分のサイズをキャッシュするように最適化されているため、`size` の計算量はならし対数時間まで減少している。 +実質的には、一度 `size` を呼び出すと二回目以降の `size` の呼び出しは、典型的には最後に `size` が呼ばれた時点より更新された枝のサイズのみを再計算するというように、最小限の仕事のみを要するようになる。 +さらに、並列並行トライのサイズ計算は並列的に実行される。 + +## 参照 + +1. [Cache-Aware Lock-Free Concurrent Hash Tries][1] +2. [Concurrent Tries with Efficient Non-Blocking Snapshots][2] +3. [Methods of computing square roots][3] + + [1]: http://infoscience.epfl.ch/record/166908/files/ctries-techreport.pdf "Ctries-techreport" + [2]: http://lampwww.epfl.ch/~prokopec/ctries-snapshot.pdf "Ctries-snapshot" + [3]: http://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method "babylonian-method" diff --git a/_ja/overviews/parallel-collections/custom-parallel-collections.md b/_ja/overviews/parallel-collections/custom-parallel-collections.md new file mode 100644 index 0000000000..e02cae26b9 --- /dev/null +++ b/_ja/overviews/parallel-collections/custom-parallel-collections.md @@ -0,0 +1,249 @@ +--- +layout: multipage-overview +title: カスタム並列コレクションの作成 + +discourse: false + +partof: parallel-collections +overview-name: Parallel Collections + +num: 6 +language: ja +--- + +## コンバイナを持たない並列コレクション + +ビルダ無しでもカスタム順次コレクションを定義できるように、コンバイナ無しで並列コレクションを定義することが可能だ。 +コンバイナを持たなければ、(例えば `map`、`flatMap`、`collect`、`filter`、などの)変換メソッドはデフォルトでは、継承関係で一番近い標準コレクションの型を返すことになる。 +例えば、範囲はビルダを持たないため、その要素を写像 (`map`) するとベクトルが作られる。 + +以下に具体例として、並列の文字列コレクションを定義する。 +文字列は論理的には不変列なので、並列文字列は `immutable.ParSeq[Char]` を継承することにする: + + class ParString(val str: String) + extends immutable.ParSeq[Char] { + +次に、全ての不変列にあるメソッドを定義する: + + def apply(i: Int) = str.charAt(i) + + def length = str.length + +この並列コレクションの直列版も定義しなければならない。 +ここでは `WrappedString` クラスを返す: + + def seq = new collection.immutable.WrappedString(str) + +最後に、この並列文字列コレクションのスプリッタを定義しなければならない。 +このスプリッタは `ParStringSplitter` と名づけ、列スプリッタの `SeqSplitter[Char]` を継承することにする: + + def splitter = new ParStringSplitter(str, 0, str.length) + + class ParStringSplitter(private var s: String, private var i: Int, private val ntl: Int) + extends SeqSplitter[Char] { + + final def hasNext = i < ntl + + final def next = { + val r = s.charAt(i) + i += 1 + r + } + +上のコードでは、`ntl` は文字列の長さの合計、`i` は現在の位置、`s` は文字列自身を表す。 + +並列コレクションのイテレータ(別名スプリッタ)は、順次コレクションのイテレータにある `next` と `hasNext` の他にもいくつかのメソッドを必要とする。 +第一に、スプリッタがまだ走査していない要素の数を返す `remaining` というメソッドがある。 +次に、現在のスプリッタを複製する `dup` というメソッドがある。 + + def remaining = ntl - i + + def dup = new ParStringSplitter(s, i, ntl) + +最後に、現在のスプリッタの要素の部分集合を走査する別のスプリッタを作成するのに使われる `split` と `psplit` メソッドがある。 +`split` メソッドは、現在のスプリッタが操作する要素の、交わらなく (disjoint) 、空でもない、部分集合の列を返すことを約束する。 +現在のスプリッタが一つ以下の要素を持つ場合、`split` は自分自身だけが入った列を返す。 +`psplit` メソッドは、`sizes` パラメータが指定する数どおりの要素を走査するスプリッタの列を返す。 +もし `sizes` パラメータが現在のスプリッタよりも少ない要素を指定した場合は、残りの要素は追加のスプリッタに入れられ、それは列の最後に追加される。もし `sizes` パラメータが今ある要素よりも多くの要素を必要とした場合は、それぞれのサイズに空のスプリッタを追加して補う。 +また、`split` か `psplit` のどちらかを呼び出しても現在のスプリッタを無効化する。 + + def split = { + val rem = remaining + if (rem >= 2) psplit(rem / 2, rem - rem / 2) + else Seq(this) + } + + def psplit(sizes: Int*): Seq[ParStringSplitter] = { + val splitted = new ArrayBuffer[ParStringSplitter] + for (sz <- sizes) { + val next = (i + sz) min ntl + splitted += new ParStringSplitter(s, i, next) + i = next + } + if (remaining > 0) splitted += new ParStringSplitter(s, i, ntl) + splitted + } + } + } + +上のコードでは、`split` は `psplit` に基づいて実装されているが、これは並列の列ではよくあることだ。 +並列マップ、集合、`Iterable` のスプリッタを実装する方が `psplit` を必要としないため簡単であることが多い。 + +これで、並列文字列クラスができた。唯一の短所は `filter` のような変換メソッドを呼び出すと並列文字列の代わりに並列ベクトルが返ってくる点だ。 +フィルタをかけた後でベクトルから文字列を再び生成するのは高価であるかもしれず、これは望ましいとは言えない。 + +## コンバイナを持つ並列コレクション + +例えばコンマを除外するなどして、並列文字列内の文字を `filter` したいとする。 +前述のとおり、`filter` の呼び出しは並列ベクトルを返すが、(API のインターフェイスによっては並列文字列が必要なため)どうしても並列文字列が欲しい。 + +これを回避するには並列文字列コレクションのコンバイナを書かなくてはならない。 +今度は `ParSeq[Char]` の代わりに `ParSeqLike` を継承することで `filter` の戻り値の型がより特定のものであることを保証する(`ParSeq[Char]` ではなく、`ParString` を返す)。 +(二つの型パラメータを取る順次 `*Like` トレイト群とは異なり)`ParSeqLike` は第三の型パラメータを取り、これは並列コレクションに対応する順次版の型を指定する。 + + class ParString(val str: String) + extends immutable.ParSeq[Char] + with ParSeqLike[Char, ParString, collection.immutable.WrappedString] + +前に定義したメソッドはそのまま使えるが、`filter` の内部で使われる protected なメソッドである `newCombiner` を追加する。 + + protected[this] override def newCombiner: Combiner[Char, ParString] = new ParStringCombiner + +次に `ParStringCombiner` クラスを実装する。 +コンバイナは ビルダのサブタイプだが `combine` というメソッドを導入する。 +`combine` メソッドは、別のコンバイナを引数に取り現在のコンバイナと引数のコンバイナの両方の要素を含んだ新しいコンバイナを返す。 +`combine` を呼び出すと、現在のコンバイナと引数のコンバイナは無効化される。 +もし引数が現在のコンバイナと同じオブジェクトである場合は、`combine` は現在のコンバイナを返す。 +このメソッドは並列計算の中で複数回呼び出されるので、最悪でも要素数に対して対数時間で実行するなど、効率的であることが期待されている。 + +`ParStringCombiner` は内部に `StringBuilder` の列を管理することにする。 +これで列の最後の `StringBuilder` に要素を追加することで `+=` を実装し、現在のコンバイナと引数のコンバイナの `StringBuilder` のリストを連結することで `combine` を実装できるようになる。 +並列計算の最後に呼び出される `result` メソッドは、全ての `StringBuilder` を追加することで並列文字列を生成する。 +これにより、要素のコピーは、毎回 `combine` を呼ぶたびに行われるのではなく、最後に一度だけ行われる。 +理想的には、この処理を並列化してコピーも並列に実行したい(並列配列ではそうなっている)が、文字列の内部表現にまで踏み込まない限りはこれが限界だ。そのため、この逐次的ボトルネックを受け入れなければいけない。 + + private class ParStringCombiner extends Combiner[Char, ParString] { + var sz = 0 + val chunks = new ArrayBuffer[StringBuilder] += new StringBuilder + var lastc = chunks.last + + def size: Int = sz + + def +=(elem: Char): this.type = { + lastc += elem + sz += 1 + this + } + + def clear = { + chunks.clear + chunks += new StringBuilder + lastc = chunks.last + sz = 0 + } + + def result: ParString = { + val rsb = new StringBuilder + for (sb <- chunks) rsb.append(sb) + new ParString(rsb.toString) + } + + def combine[U <: Char, NewTo >: ParString](other: Combiner[U, NewTo]) = if (other eq this) this else { + val that = other.asInstanceOf[ParStringCombiner] + sz += that.sz + chunks ++= that.chunks + lastc = chunks.last + this + } + } + + +## どうやってコンバイナを実装すればいい? + +これには定義済みのレシピは無い。 +扱っているデータ構造に依存するし、実装者による創意工夫が必要なことも多い。 +しかし、通常用いられれるいくつかの方法がある: + +
        +
      1. 連結とマージ。 +これらの演算に対して効率的な(通常、対数時間の)実装を持つデータ構造がある。 +もし、扱っているコレクションがそのようなデータ構造を用いてるならば、コレクションそのものをコンバイナとして使える。 +フィンガーツリー、ロープ、そしてヒープの多くが特にこの方法に向いている。
      2. +
      3. 二段階評価。 +並列配列と並列ハッシュテーブルで用いられてる方法で、要素が効率良く連結可能なバケットに部分ソート可能で、バケットから最終的なデータ構造が並列に構築可能なことを前提とする。 +まず、第一段階では別々のプロセッサが独立して要素をバケットに書き込んでいき、最後にバケットが連結される。 +第二段階では、データ構造が割り当てられ、別々のプロセッサがそれぞれデータ構造の異なる部分に交わらないバケットから要素を書き込んでいく。 +異なるプロセッサが絶対にデータ構造の同じ部分を変更しないように注意しないと、微妙な並行エラーが発生する可能性がある。 +前の節で示したように、この方法はランダムアクセス可能な列にも応用できる。
      4. +
      5. 並行データ構造 (concurrent data structure)。 +上の二つの方法はデータ構造そのものには同期機構を必要としないが、二つの異なるプロセッサが絶対に同じメモリの位置を更新しないような方法で並行して構築可能であることを前提とする。 +並行スキップリスト、並行ハッシュテーブル、split-ordered list、並行AVLツリーなど、複数のプロセッサから安全に更新することが可能な並行データ構造が数多く存在する。 +考慮すべき重要な点は、並行データ構造は水平にスケーラブルな挿入方法を持っていることだ。 +並行な並列コレクションは、コンバイナはコレクション自身であることが可能で、単一のコンバイナのインスタンスを並列演算を実行する全てのプロセッサによって共有できる。
      6. +
      + +## コレクションフレームワークとの統合 + +`ParString` はまだ完成していない。 +`filter`、`partition`、`takeWhile`、や `span` などのメソッドで使われるカスタムのコンバイナを実装したが、ほとんどの変換メソッドは暗黙の `CanBuildFrom` のエビデンスを必要とする(完全な説明に関しては、Scala コレクションのガイドを参照)。 +これを提供して `ParString` をコレクションフレームワークの一部として完全に統合するには、`GenericParTemplate` というもう一つのトレイトをミックスインして `ParString` のコンパニオンオブジェクトを定義する必要がある。 + + class ParString(val str: String) + extends immutable.ParSeq[Char] + with GenericParTemplate[Char, ParString] + with ParSeqLike[Char, ParString, collection.immutable.WrappedString] { + + def companion = ParString + +コンパニオンオブジェクトの中で `CanBuildFrom` パラメータのための暗黙の値を提供する。 + + object ParString { + implicit def canBuildFrom: CanCombineFrom[ParString, Char, ParString] = + new CanCombinerFrom[ParString, Char, ParString] { + def apply(from: ParString) = newCombiner + def apply() = newCombiner + } + + def newBuilder: Combiner[Char, ParString] = newCombiner + + def newCombiner: Combiner[Char, ParString] = new ParStringCombiner + + def apply(elems: Char*): ParString = { + val cb = newCombiner + cb ++= elems + cb.result + } + } + +## 更なるカスタム化 -- 並行とその他のコレクション + +並行コレクションの実装は一筋縄ではいかない(並列コレクションと違って、並行コレクションは `collection.concurrent.TriMap` のように並行して更新可能なもの)。 +コンバイナは特に頭をひねるところだ。 +これまで見てきた多くの**並列** (parallel) コレクションでは、コンバイナは二段階評価を行った。 +第一段階では、異なるプロセッサによって要素はコンバイナに加えられ、コンバイナは一つに組み合わされる。 +第二段階で、全ての要素がそろった時点で結果のコレクションが構築される。 + +コンバイナのもう一つの方法としては、結果と成るコレクションを要素を使って構築してしまう方法がある。 +これは、そのコレクションがスレッドセーフであることを必要とし、コンバイナは**並行** (concurrent) な要素の挿入を可能とする必要がある。 +この場合、単一のコンバイナが全てのプロセッサにより共有される。 + +並行コレクションを並列化するには、コンバイナは `canBeShared` メソッドをオーバーライドして `true` を返す必要がある。 +これで並列演算が呼び出される時に単一のコンバイナのみが作成されることが保証される。 +次に `+=` メソッドがスレッドセーフである必要がある。 +最後に `combine` メソッドは現在のコンバイナと引数のコンバイナが同一である場合は現在のコンバイナを返す必要があるが、それ以外の場合は例外を投げてもいい。 + +スプリッタは負荷分散のために小さいスプリッタへと分割される。 +デフォルトでは、`remaining` メソッドで得られる情報によってスプリッタの分割をいつ止めるか決定する。 +コレクションによっては `remaining` の呼び出しは高価で、スプリッタの分割を決定するのに他の方法を使ったほうが望ましい場合もある。 +その場合は、スプリッタの `shouldSplitFurther` メソッドをオーバーライドする。 + +デフォルトの実装では、残りの要素数がコレクションのサイズを並列度の8倍で割った数より多い場合に分割される。 + + def shouldSplitFurther[S](coll: ParIterable[S], parallelismLevel: Int) = + remaining > thresholdFromSize(coll.size, parallelismLevel) + +同値の実装として、スプリッタに何回分割されたかを格納するカウンタを持たせ、分割回数が `3 + log(parallelismLevel)` よりも多い場合だけ `shouldSplitFurther` が `true` を返すようにできるが、これは `remaining` の呼び出しを回避する。 + +さらに、ある特定のコレクションに対して `remaining` を呼び出すのが安価な演算ではない場合(つまり、コレクション内の要素数を求めなければいけない場合)、スプリッタの `isRemainingCheap` メソッドをオーバーライドして `false` を返すべきだ。 + +最後に、スプリッタの `remaining` メソッドの実装が非常に厄介な場合は、コレクションの `isStrictSplitterCollection` メソッドをオーバーライドして `false` を返すことができる。そのようなコレクションは、スプリッタが厳格である(つまり、`remaining` メソッドが正しい値を返す)ことが必要であるメソッドが失敗するようになる。大切なのは、これは for-展開で使われるメソッドには影響しないことだ。 diff --git a/_ja/overviews/parallel-collections/overview.md b/_ja/overviews/parallel-collections/overview.md new file mode 100644 index 0000000000..d4360d200c --- /dev/null +++ b/_ja/overviews/parallel-collections/overview.md @@ -0,0 +1,169 @@ +--- +layout: multipage-overview +title: 概要 + +discourse: false + +partof: parallel-collections +overview-name: Parallel Collections + +num: 1 +language: ja +--- + +**Aleksandar Prokopec, Heather Miller 著**
      +**Eugene Yokota 訳** + +## 動機 + +近年におけるプロセッサ製造業者のシングルコアからマルチコアへの移行のまっただ中で、産学ともに認めざるをえないのは「大衆的並列プログラミング」が大きな難題であり続けていることだ。 + +並列コレクション (parallel collection) は、並列プログラミングを容易にすることを目指して Scala 標準ライブラリに取り込まれた。 +ユーザは低レベルな並列化に関する詳細を気にせず、親しみやすいコレクションという高レベルの抽象概念 (abstraction) を利用できる。 +この概念が隠蔽する並列性によって、信頼性のある並列実行が開発者にとってより身近なものになると願っている。 + +アイディアは簡単だ -- コレクションはよく理解されており、よく使われているプログラミングの抽象概念だ。 +さらに、その規則性により、コレクションは効率良く、ユーザが意識すること無く、並列化することができる。 +ユーザが順次コレクション (sequential collection) を並列に計算するものに「置き換える」ことを可能にすることで、Scala の並列コレクションは、より多くのコードに並列性をもたらす方向に大きな一歩を踏み出したと言える。 + +例えば、大きなコレクションに対してモナディックな演算を行なっている逐次的 (sequential) な例をみてほしい: + + val list = (1 to 10000).toList + list.map(_ + 42) + +同じ演算を並列に実行するには、単に順次コレクションである `list` に対して `par` メソッドを呼び出すだけでいい。後は、順次コレクションを普通に使うのと同じように並列コレクションを利用できる。上記の例は以下のように並列化できる: + + list.par.map(_ + 42) + +Scala の並列コレクションの設計は、2.8 で導入された Scala の(順次)コレクションライブラリに影響を受けており、またその一部となるように深く統合されている。 +Scala の(順次)コレクションライブラリの重要なデータ構造の多くに対して対応する並列のコレクションを提供する: + +* `ParArray` +* `ParVector` +* `mutable.ParHashMap` +* `mutable.ParHashSet` +* `immutable.ParHashMap` +* `immutable.ParHashSet` +* `ParRange` +* `ParTrieMap` (`collection.concurrent.TrieMap` は 2.10 より追加された) + +Scala の並列コレクションライブラリは、順次ライブラリコレクションと共通のアーキテクチャを持つだけでなく、その**拡張性**も共有している。 +つまり、普通の順次コレクションと同様に、ユーザは独自のコレクション型を統合して、標準ライブラリにある他の並列コレクションにあるものと同じ(並列の)演算を自動的に継承できる。 + +## 例をいくつか + +並列コレクションの一般性と利便性を例示するために、いくつかの簡単な具体例を用いて説明しよう。全ての例において、ユーザが意識すること無く演算は並列に実行されている。 + +**注意:** 以下の例ではサイズの小さいコレクションを並列化しているが、これはあくまで説明のための例で、実用では推奨されない。 +一般的な指標として、コレクションのサイズが数千要素など、サイズが大きいほど並列化による高速化が顕著であることが多い。(並列コレクションのサイズと性能に関する詳細に関しては、このガイドの[性能](performance.html)に関する節の[該当する項](performance.html#id18)を参照してほしい) + +#### map + +並列 `map` を使って `String` のコレクションを大文字に変換する: + + scala> val lastNames = List("Smith","Jones","Frankenstein","Bach","Jackson","Rodin").par + lastNames: scala.collection.parallel.immutable.ParSeq[String] = ParVector(Smith, Jones, Frankenstein, Bach, Jackson, Rodin) + + scala> lastNames.map(_.toUpperCase) + res0: scala.collection.parallel.immutable.ParSeq[String] = ParVector(SMITH, JONES, FRANKENSTEIN, BACH, JACKSON, RODIN) + +#### fold + +`ParArray` の `fold` を使った合計: + + scala> val parArray = (1 to 10000).toArray.par + parArray: scala.collection.parallel.mutable.ParArray[Int] = ParArray(1, 2, 3, ... + + scala> parArray.fold(0)(_ + _) + res0: Int = 50005000 + +#### filter + +並列 `filter` を使ってラストネームがアルファベットの "J" 以降のから始まるものを選択する: + + scala> val lastNames = List("Smith","Jones","Frankenstein","Bach","Jackson","Rodin").par + lastNames: scala.collection.parallel.immutable.ParSeq[String] = ParVector(Smith, Jones, Frankenstein, Bach, Jackson, Rodin) + + scala> lastNames.filter(_.head >= 'J') + res0: scala.collection.parallel.immutable.ParSeq[String] = ParVector(Smith, Jones, Jackson, Rodin) + +## 並列コレクションの意味論 + +並列コレクションのインターフェイスは普通の順次コレクションと同じ感覚で使うことができるが、特に副作用を伴う演算と結合則が成立しない演算においては、演算の意味するものが異なることに注意する必要がある。 + +これを理解するためには、まず、演算が**どのように**並列実行されているのかをイメージする必要がある。 +概念的には、Scala の並列コレクションフレームワークは、ある並列コレクションにおける演算を並列化するために、再帰的にコレクションを「分割」(split) し、並列にそれぞれの部分に演算を適用し、並列に完了した全ての結果を再び「合成」(combine) することで行う。 + +このような並列コレクションの並行 (concurrent) で、「アウト・オブ・オーダー」("out-of-order"、訳注: 記述された順序以外で演算が実行されること) な意味論から以下の二つの結果が導き出される: + +1. **副作用を伴う演算は非決定性につながる可能性がある** + +2. **結合則が成立しない演算は非決定性につながる可能性がある** + +### 副作用を伴う演算 + +並列コレクションフレームワークの**並行**実行の意味論を考慮すると、計算の決定性 (determinism) を維持するためには、コレクションに対して副作用 (side-effect) を伴う演算は一般的に避けるべきだ。具体例としては、`foreach` のようなアクセスメソッドを用いる場合に、渡されるクロージャ中から外の `var` を増加することが挙げられる。 + + scala> var sum = 0 + sum: Int = 0 + + scala> val list = (1 to 1000).toList.par + list: scala.collection.parallel.immutable.ParSeq[Int] = ParVector(1, 2, 3,… + + scala> list.foreach(sum += _); sum + res01: Int = 467766 + + scala> var sum = 0 + sum: Int = 0 + + scala> list.foreach(sum += _); sum + res02: Int = 457073 + + scala> var sum = 0 + sum: Int = 0 + + scala> list.foreach(sum += _); sum + res03: Int = 468520 + +ここでは、`sum` が 0 に初期化されて、`list` に対して `foreach` が呼び出されるたびに `sum` が異なる値を持っていることが分かる。この非決定性の原因は**データ競合** (data race; 同一の可変変数に対する並行した読み書き) だ。 + +上の例だと、二つのスレッドが `sum` の**同じ**値を読み込んで、その `sum` の値に対して何らかの演算を実行した後で、`sum` に新しい値を書きこもうとするかもしれない。以下に示すように、その場合は、大切な結果の上書き(つまり、損失)が起きる可能性がある: + + ThreadA: sum の値を読み込む、sum = 0 sum の値: 0 + ThreadB: sum の値を読み込む、sum = 0 sum の値: 0 + ThreadA: sum を 760 増加する、sum = 760 を書き込む sum の値: 760 + ThreadB: sum を 12 増加する、sum = 12 を書き込む sum の値: 12 + +上の例は、どちらか一方のスレッドが `0` に各自の並列コレクションの担当部分からの要素を加算する前に、二つのスレッドが同じ値 `0` を読み込むシナリオを示している。この場合、`ThreadA` は `0` を読み込み、`0+760` を合計し、`ThreadB` も `0` に自分の要素を加算し、`0+12` となった。各自の合計を計算した後、それぞれが経験結果を `sum` に書き込む。`ThreadA` が `ThreadB` よりも先に書き込むが、直後に `ThreadB` がそれを上書きするため、実質 `760` という値が上書きされ、失われることになった。 + +### 結合則が成立しない演算 + +**「アウト・オブ・オーダー」**実行の意味論を考慮すると、非決定性を避けるためには、慎重に、結合則が成立する演算のみを実行するべきだ。つまり、並列コレクション `pcoll` に対して `pcoll.reduce(func)` のように高階関数を呼び出すとき、`func` が要素に適用される順序が任意でも大丈夫であるように気をつけるべきだということだ。単純で、かつ明快な結合則が成立しない演算の例は減算だ: + + scala> val list = (1 to 1000).toList.par + list: scala.collection.parallel.immutable.ParSeq[Int] = ParVector(1, 2, 3,… + + scala> list.reduce(_-_) + res01: Int = -228888 + + scala> list.reduce(_-_) + res02: Int = -61000 + + scala> list.reduce(_-_) + res03: Int = -331818 + +上の例では、`ParVector[Int]` の `reduce` メソッドが `_-_` と共に呼び出されている。 +これは二つの不特定の要素を取り出し、前者から後者を引く。 +並列コレクションフレームワークはスレッドを呼び出し、それぞれが実質的に、独自にコレクションから異なる部位を取り出し `reduce(_-_)` を実行するため、同じコレクションに `reduce(_-_)` を実行するたびに毎回異なった結果が得られることとなる。 + +**注意:** 結合則が成立しない演算と同様に、交換則が成立しない演算も並列コレクションの高階関数に渡されると非決定的な振る舞いをみせると思われがちだが、それは間違っている。単純な例としては、文字列の連結がある。結合則は成立するが、交換則は成立しない演算だ: + + scala> val strings = List("abc","def","ghi","jk","lmnop","qrs","tuv","wx","yz").par + strings: scala.collection.parallel.immutable.ParSeq[java.lang.String] = ParVector(abc, def, ghi, jk, lmnop, qrs, tuv, wx, yz) + + scala> val alphabet = strings.reduce(_++_) + alphabet: java.lang.String = abcdefghijklmnopqrstuvwxyz + +並列コレクションにおける**「アウト・オブ・オーダー」**の意味論は、演算が(**時間的**な意味で、つまり非逐次的に)バラバラの順序で実行されるという意味であって、結果が(**空間的**に)バラバラに**「再合成」**されるという意味ではない。結果は、一般的に**順序どおり** (in-order) に再合成される。つまり、A、B、C の順番に分割された並列コレクションは、再び A、B、C の順番に再合成される。任意の B、C、A というような順序にはならない。 + +異なる並列コレクションの型における、分割と再合成の詳細ついてはこのガイドの[アーキテクチャ](architecture.html)の節を参照してほしい。 diff --git a/_ja/overviews/parallel-collections/performance.md b/_ja/overviews/parallel-collections/performance.md new file mode 100644 index 0000000000..9299680834 --- /dev/null +++ b/_ja/overviews/parallel-collections/performance.md @@ -0,0 +1,229 @@ +--- +layout: multipage-overview +title: 性能の測定 + +discourse: false + +partof: parallel-collections +overview-name: Parallel Collections + +num: 8 +outof: 8 +language: ja +--- + +## JVM における性能 + +JVM における性能モデルは論評こそは色々あるが、それに巻き込まれて結局よく理解されてないと言える。 +様々な理由から、あるコードは期待されているよりも性能が悪かったり、スケーラブルではなかったりする。 +以下にいくつかの理由をみていく。 + +一つは JVM 上のアプリケーションのコンパイル工程が静的にコンパイルされた言語のそれとは同じではないということが挙げられる(\[[2][2]\] 参照)。 +Java と Scala のコンパイラはソースコードを JVM バイトコードに変換するだけで、最適化はほとんど行わない。 +現代的な JVM の多くではプログラムのバイトコードが実行されると、それは実行しているマシンのコンピュータアーキテクチャのマシンコードに変換する。 +これはジャストインタイムコンパイラ (just-in-time compiler、JITコンパイラ) と呼ばれる。 +しかし、JITコンパイラは速度を優先するため、最適化のレベルは低いといえる。 +再コンパイルを避けるため、いわゆる HotSpot コンパイラは頻繁に実行される部分だけを最適化する。 +これがベンチマーク作者へ及ぼす影響は、プログラムを実行するたびにそれらが異なる性能を持つことだ。 +(例えば、ある特定のメソッドのような)同じコードを同じ JVM インスタンス上で複数回実行したとしても、そのコードが間に最適化された場合は全く異なる性能を示す可能性がある。 +さらに、コードのある部分の実行時間を測定することは JITコンパイラが最適化を実行している時間を含む可能性があり、一貫性に欠ける結果となることもある。 + +JVM において隠れて実行されるものの一つに自動メモリ管理がある。 +ときどきプログラムの実行が停止され、ガベージコレクタが実行されるのだ。 +もしベンチマーク測定されるプログラムがヒープメモリを少しでも使ったならば(ほとんどの JVM プログラムは使用する)、ガベージコレクタが実行されなければならず、測定を歪めることになる。測定するプログラムを多くの回数実行することでガベージコレクションも何回も発生させガベージコレクションの影響を償却 (amortize) する必要がある。 + +性能劣化の原因の一つとして、ジェネリックなメソッドにプリミティブ型を渡すことによって暗黙に発生するボクシングとアンボクシングが挙げられる。 +実行時にプリミティブ型は、ジェネリックな型パラメータに渡せるように、それらを表すオブジェクトに変換される。 +これは余計なメモリ割り当てを発生させ、遅い上に、ヒープにはいらないゴミができる。 + +並列的な性能に関して言えば、プログラマがオブジェクトがどこに割り当てられるかの明示的なコントールを持たないためメモリ輻輳 (memory contention) がよく問題となる。 +実際、GC効果により、オブジェクトがメモリの中を動きまわった後である、アプリケーションライフタイムにおける後期のステージでもメモリ輻輳が発生しうる。 +ベンチマークを書くときにはこのような効果も考慮する必要がある。 + +## マイクロベンチマークの例 + +コードの性能を計測するにあたり、上に挙げた効果を回避する方法がいくつかある。 +第一に、対象となるマイクロベンチマークを十分な回数実行することで JITコンパイラがマシンコードにコンパイルし、また最適化されたことを確実にしなければいけない。これはウォームアップ段階 (warm-up phase) と呼ばれる。 + +マイクロベンチマークそのものは、独立した JVM インスタンスで実行することでプログラムの別の部分により割り当てられたオブジェクトのガベージコレクションや無関係な JITコンパイルによるノイズを低減すべきだ。 + +より積極的な最適化を行うサーバーバージョンの HotSpot JVM を用いて実行するべきだ。 + +最後に、ベンチマークの最中にガベージコレクションが発生する可能性を低減するために、理想的にはベンチマークの実行の前にガベージコレクションを行い、次のサイクルを可能な限り延期すべきだ。 + +Scala の標準ライブラリには `scala.testing.Benchmark` トレイトが定義されており、上記のことを考えて設計されている。 +並行トライの `map` 演算をベンチマークする具体例を以下に示す: + + import collection.parallel.mutable.ParTrieMap + import collection.parallel.ForkJoinTaskSupport + + object Map extends testing.Benchmark { + val length = sys.props("length").toInt + val par = sys.props("par").toInt + val partrie = ParTrieMap((0 until length) zip (0 until length): _*) + + partrie.tasksupport = new ForkJoinTaskSupport(new scala.concurrent.forkjoin.ForkJoinPool(par)) + + def run = { + partrie map { + kv => kv + } + } + } + +`run` メソッドはマイクロベンチマークの本体で、これが何度も実行され実行時間が計測される。 +上のコードでの `Map` オブジェクトは `scala.testing.Benchmark` トレイトを拡張しシステム指定されたいくつかのパラメータを読み込む。 +`par` は並列度、`length` はトライの要素数をそれぞれ表す。 + +このプログラムをコンパイルした後、このように実行する: + + java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=1 -Dlength=300000 Map 10 + +`server` フラグはサーバ VM が使われることを指定する。 +`cp` はクラスパスを指定し、現ディレクトリと Scala ライブラリの jar を含む。 +`-Dpar` と `-Dlength` は並列度と要素数を指定する。 +最後に、`10` はベンチマークが同じ JVM 内で実行される回数を指定する。 + +以下はハイパースレッディング付きクアッドコアの i7 で `par` を `1`、`2`、`4` と `8` に設定して得られた実行時間だ: + + Map$ 126 57 56 57 54 54 54 53 53 53 + Map$ 90 99 28 28 26 26 26 26 26 26 + Map$ 201 17 17 16 15 15 16 14 18 15 + Map$ 182 12 13 17 16 14 14 12 12 12 + +初期の試行の実行時間が高めであるが、コードが最適化されると低減することが上のデータから分かる。 +さらに、`4` スレッドに対して `8` スレッドの性能向上が少ししかみられないことからハイパースレッディングによる利益があまりないことも分かる。 + +## コレクションがどれだけ大きければ並列化するべきか? + +これはよく問われる質問だが、これには少し込み入った答が必要になる。 + +並列化の採算が取れる(つまり、並列化による高速化が並列化することに伴うオーバーヘッドを上回る)コレクションのサイズは多くの要素に依存するからだ。 +全ては書ききれないが、いくつかを挙げる: + +
        +
      • マシンのアーキテクチャ。 +異なる CPU の種類はそれぞれ異なる性能特性やスケーラビリティ特性を持つ。 +それとは別に、マシンがマルチコアなのかマザーボード経由で通信するマルチプロセッサなのかにもよる。
      • +
      • JVM のベンダとバージョン。 +異なる VM はそれぞれコードに対して異なる最適化を実行時に行う。 +異なるメモリ管理や同期のテクニックを実装する。 +ForkJoinPool をサポートしないものもあるので、その場合はよりオーバーヘッドのかかる ThreadPoolExecutor が補欠で使われる。
      • +
      • 要素あたりの負荷。 +並列演算の関数や条件関数が要素あたりの負荷を決定する。 +負荷が軽ければ軽いほど、並列化による高速化を得るのに必要な要素数は多くなる。
      • +
      • 特定のコレクション。 +例えば、ParArrayParTrieMap のスプリッタではコレクションの走査するスピードが異なり、これは走査だけをみても要素あたりの負荷があるということだ。
      • +
      • 特定の演算。 +例えば、ParVector の(filter のような)変換メソッドは(foreach のような)アクセスメソッドにくらべてかなり遅い。
      • +
      • 副作用。 +メモリ領域を並行で変更したり、foreachmap その他に渡されるクロージャから同期機構を用いると輻輳が発生する可能性がある。
      • +
      • メモリ管理。 +大量にオブジェクトを割り当てるとガベージコレクションサイクルが誘発される。 +新しいオブジェクトへの参照がどのように取り回されるかによって GC サイクルにかかる時間が異なる。
      • +
      + +上に挙げたものを単独でみても、それらを論理的に論じてコレクションの採算が取れるサイズの正確な答を出すのは簡単なことではない。 +サイズがいくつであるかを大まかに示すために、以下に安価で副作用を伴わないベクトルの集約演算(この場合、sum)をクアッドコアの i7(ハイパースレッディング無し)で JDK7 上で実行した具体例を示す: + + import collection.parallel.immutable.ParVector + + object Reduce extends testing.Benchmark { + val length = sys.props("length").toInt + val par = sys.props("par").toInt + val parvector = ParVector((0 until length): _*) + + parvector.tasksupport = new collection.parallel.ForkJoinTaskSupport(new scala.concurrent.forkjoin.ForkJoinPool(par)) + + def run = { + parvector reduce { + (a, b) => a + b + } + } + } + + object ReduceSeq extends testing.Benchmark { + val length = sys.props("length").toInt + val vector = collection.immutable.Vector((0 until length): _*) + + def run = { + vector reduce { + (a, b) => a + b + } + } + } + +まずこのベンチマークを `250000` 要素で実行して `1`、`2`、`4` スレッドに関して以下の結果を得た: + + java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=1 -Dlength=250000 Reduce 10 10 + Reduce$ 54 24 18 18 18 19 19 18 19 19 + java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=2 -Dlength=250000 Reduce 10 10 + Reduce$ 60 19 17 13 13 13 13 14 12 13 + java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=4 -Dlength=250000 Reduce 10 10 + Reduce$ 62 17 15 14 13 11 11 11 11 9 + +次に、順次ベクトルの集約と実行時間を比較するために要素数を `120000` まで減らして `4` スレッドを使った: + + java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=4 -Dlength=120000 Reduce 10 10 + Reduce$ 54 10 8 8 8 7 8 7 6 5 + java -server -cp .:../../build/pack/lib/scala-library.jar -Dlength=120000 ReduceSeq 10 10 + ReduceSeq$ 31 7 8 8 7 7 7 8 7 8 + +この場合は、`120000` 要素近辺が閾値のようだ。 + +もう一つの具体例として、`mutable.ParHashMap` と(変換メソッドである)`map` メソッドに注目して同じ環境で以下のベンチマークを実行する: + + import collection.parallel.mutable.ParHashMap + + object Map extends testing.Benchmark { + val length = sys.props("length").toInt + val par = sys.props("par").toInt + val phm = ParHashMap((0 until length) zip (0 until length): _*) + + phm.tasksupport = new collection.parallel.ForkJoinTaskSupport(new scala.concurrent.forkjoin.ForkJoinPool(par)) + + def run = { + phm map { + kv => kv + } + } + } + + object MapSeq extends testing.Benchmark { + val length = sys.props("length").toInt + val hm = collection.mutable.HashMap((0 until length) zip (0 until length): _*) + + def run = { + hm map { + kv => kv + } + } + } + +`120000` 要素だとスレッド数を `1` から `4` に変化させていくと以下の実行時間が得られる: + + java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=1 -Dlength=120000 Map 10 10 + Map$ 187 108 97 96 96 95 95 95 96 95 + java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=2 -Dlength=120000 Map 10 10 + Map$ 138 68 57 56 57 56 56 55 54 55 + java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=4 -Dlength=120000 Map 10 10 + Map$ 124 54 42 40 38 41 40 40 39 39 + +ここで要素数を `15000` まで減らして順次ハッシュマップと比較する: + + java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=1 -Dlength=15000 Map 10 10 + Map$ 41 13 10 10 10 9 9 9 10 9 + java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=2 -Dlength=15000 Map 10 10 + Map$ 48 15 9 8 7 7 6 7 8 6 + java -server -cp .:../../build/pack/lib/scala-library.jar -Dlength=15000 MapSeq 10 10 + MapSeq$ 39 9 9 9 8 9 9 9 9 9 + +このコレクションのこの演算に関しては、`15000` 要素以上あるときには並列化の高速化による採算が取れる(一般に、配列やベクトルに比べてハッシュマップやハッシュ集合の方が少ない要素で並列化の効果を得ることができる)。 + +## 参照 + +1. [Anatomy of a flawed microbenchmark, Brian Goetz][1] +2. [Dynamic compilation and performance measurement, Brian Goetz][2] + + [1]: http://www.ibm.com/developerworks/java/library/j-jtp02225/index.html "flawed-benchmark" + [2]: http://www.ibm.com/developerworks/library/j-jtp12214/ "dynamic-compilation" diff --git a/_ja/overviews/reflection/annotations-names-scopes.md b/_ja/overviews/reflection/annotations-names-scopes.md new file mode 100644 index 0000000000..4a951d80ca --- /dev/null +++ b/_ja/overviews/reflection/annotations-names-scopes.md @@ -0,0 +1,412 @@ +--- +layout: multipage-overview + +discourse: false + +partof: reflection +overview-name: Reflection + +num: 4 + +language: ja +title: アノテーション、名前、スコープ、その他 +--- + +EXPERIMENTAL + +## アノテーション + +Scala において宣言は `scala.annotation.Annotation` のサブタイプを用いて注釈を付けることができる。 +さらに、Scala は [Java のアノテーションシステム](http://docs.oracle.com/javase/7/docs/technotes/guides/language/annotations.html#_top)に統合するため、標準 Java コンパイラによって生成されたアノテーションを取り扱うこともできる。 + +アノテーションは、それが永続化されていればリフレクションを使ってインスペクトすることができるため、アノテーション付きの宣言を含むクラスファイルから読み込むことができる。カスタムアノテーション型は +`scala.annotation.StaticAnnotation` か +`scala.annotation.ClassfileAnnotation` を継承することで永続化することができる。 +その結果、アノテーション型のインスタンスはクラスファイル内の特別な属性として保存される。 +実行時リフレクションに必要なメタデータを永続化するには +`scala.annotation.Annotation` を継承するだけでは不十分であることに注意してほしい。さらに、 +`scala.annotation.ClassfileAnnotation` を継承しても実行時には Java +アノテーションとしては認識されないことに注意してほしい。そのためには、Java でアノテーションを書く必要がある。 + +API は 2種類のアノテーションを区別する: + +- **Java アノテーション**: Java コンパイラによって生成された定義に付加されたアノテーション、つまりプログラムの定義に付けられた `java.lang.annotation.Annotation` のサブタイプ。Scala リフレクションによって読み込まれると `scala.annotation.ClassfileAnnotation` トレイトが自動的に全ての Java アノテーションに追加される。 +- **Scala アノテーション**: Scala コンパイラによって生成された定義や型に付加されたアノテーション。 + +Java と Scala のアノテーションの違いは +`scala.reflect.api.Annotations#Annotation` に顕著に現れており、これは +`scalaArgs` と `javaArgs` 両方を公開する。 +`scala.annotation.ClassfileAnnotation` を継承する +Scala または Java アノテーションに対しては `scalaArgs` は空で +(もしあれば) 引数は `javaArgs` に保持される。他の全ての Scala +アノテーションの場合は、引数は `scalaArgs` に保持され、`javaArgs` は空となる。 + +`scalaArgs` 内の引数は型付けされた構文木として表される。 +これらの構文木はタイプチェッカより後のどのフェーズにおいても変換されないことに注意する必要がある。 +`javaArgs` 内の引数は `scala.reflect.api.Names#Name` から +`scala.reflect.api.Annotations#JavaArgument` へのマップとして表現される。 +`JavaArgument` のインスタンスは Java アノテーションの引数の様々な型を表現する: + +
        +
      • リテラル (プリミティブ型と文字列の定数)
      • +
      • 配列
      • +
      • 入れ子になったアノテーション
      • +
      + +## 名前 + +**名前** (name) は文字列の簡単なラッパーだ。 +[`Name`](http://www.scala-lang.org/api/current/index.html#scala.reflect.api.Names$NameApi) +には 2つのサブタイプ `TermName` と `TypeName` があり (オブジェクトやメンバーのような) 項の名前と +(クラス、トレイト、型メンバのような) 型の名前を区別する。同じオブジェクト内に同名の項と型が共存することができる。別の言い方をすると、型と項は別の名前空間を持つ。 + +これらの名前はユニバースに関連付けられている。具体例を使って説明しよう。 + + scala> import scala.reflect.runtime.universe._ + import scala.reflect.runtime.universe._ + + scala> val mapName = TermName("map") + mapName: scala.reflect.runtime.universe.TermName = map + +上のコードでは、実行時リフレクション・ユニバースに関連付けられた `Name` を作成している。 +(これはパス依存型である `reflect.runtime.universe.TermName` が表示されていることからも分かる。) + +名前は型のメンバの照会に用いられる。例えば、`List` クラス内で宣言されている (項である) `map` メソッドを検索するには以下のようにする: + + scala> val listTpe = typeOf[List[Int]] + listTpe: scala.reflect.runtime.universe.Type = scala.List[Int] + + scala> listTpe.member(mapName) + res1: scala.reflect.runtime.universe.Symbol = method map + +型メンバを検索するには `TypeName` を代わりに使って `member` を呼び出す。 +暗黙の変換を使って文字列から項もしくは型の名前に変換することもできる: + + scala> listTpe.member("map": TermName) + res2: scala.reflect.runtime.universe.Symbol = method map + +### 標準名 + +Scala のプログラムにおいて、「`_root_`」のような特定の名前は特殊な意味を持つ。 +そのため、それらは Scala の構造物をリフレクションを用いてアクセスするのに欠かすことができない。 +例えば、リフレクションを用いてコンストラクタを呼び出すには**標準名** (standard name) +`universe.nme.CONSTRUCTOR` を用いる。これは、JVM 上でのコンストラクタ名である項名「``」を指す。 + +- 「``」、「`package`」、「`_root_`」のような**標準項名** (standard term names) と +- 「``」、「`_`」、「`_*`」のような**標準型名** (standard type names) + +の両方が存在する。 + +「`package`」のようないくつかの名前は型名と項名の両方が存在する。 +標準名は `Universe` クラスの `nme` と `tpnme` というメンバとして公開されている。 +全ての標準名の仕様は [API doc](http://www.scala-lang.org/api/current/index.html#scala.reflect.api.StandardNames) を参照。 + +## スコープ + +**スコープ** (scope) は一般にある構文スコープ内の名前をシンボルに関連付ける。 +スコープは入れ子にすることもできる。リフレクション API +で公開されているスコープの基底型は [Symbol](http://www.scala-lang.org/api/current/index.html#scala.reflect.api.Symbols$Symbol) の iterable という最小限のインターフェイスのみを公開する。 + +追加機能は +[scala.reflect.api.Types#TypeApi](http://www.scala-lang.org/api/current/index.html#scala.reflect.api.Types$TypeApi) +内で定義されている `member` と `declarations` +が返す**メンバスコープ** (member scope) にて公開される。 +[scala.reflect.api.Scopes#MemberScope](http://www.scala-lang.org/api/current/index.html#scala.reflect.api.Scopes$MemberScope) +は `sorted` メソッドをサポートしており、これはメンバを**宣言順に**ソートする。 + +以下に `List` クラスでオーバーライドされている全てのシンボルのリストを宣言順に返す具体例をみてみよう: + + scala> val overridden = listTpe.declarations.sorted.filter(_.isOverride) + overridden: List[scala.reflect.runtime.universe.Symbol] = List(method companion, method ++, method +:, method toList, method take, method drop, method slice, method takeRight, method splitAt, method takeWhile, method dropWhile, method span, method reverse, method stringPrefix, method toStream, method foreach) + +## Expr + +構文木の基底型である `scala.reflect.api.Trees#Tree` の他に、型付けされた構文木は +[`scala.reflect.api.Exprs#Expr`](http://www.scala-lang.org/api/current/index.html#scala.reflect.api.Exprs$Expr) 型によっても表すことができる。 +`Expr` は構文木と、その構文木の型に対するアクセスを提供するための型タグをラッピングする。 +`Expr` は主にマクロのために便宜的に型付けられた構文木を作るために使われる。多くの場合、これは +`reify` と `splice` メソッドが関わってくる。 +(詳細は[マクロ](http://docs.scala-lang.org/ja/overviews/macros/overview.html)を参照) + +## フラグとフラグ集合 + +**フラグ** (flag) は +`scala.reflect.api.Trees#Modifiers` である `flags` を用いて定義を表す構文木に修飾子を与えるのに使われる。 +以下に修飾子を受け付ける構文木を挙げる: + +- `scala.reflect.api.Trees#ClassDef`。クラスとトレイト。 +- `scala.reflect.api.Trees#ModuleDef`。オブジェクト。 +- `scala.reflect.api.Trees#ValDef`。`val`、`var`、パラメータ、自分型注釈。 +- `scala.reflect.api.Trees#DefDef`。メソッドとコンストラクタ。 +- `scala.reflect.api.Trees#TypeDef`。型エイリアス、抽象型メンバ、型パラメータ。 + +例えば、`C` という名前のクラスを作るには以下のように書く: + + ClassDef(Modifiers(NoFlags), TypeName("C"), Nil, ...) + +ここでフラグ集合は空だ。`C` を private にするには、以下のようにする: + + ClassDef(Modifiers(PRIVATE), TypeName("C"), Nil, ...) + +垂直バー演算子 (`|`) を使って組み合わせることができる。例えば、private final +クラスは以下のように書く: + + ClassDef(Modifiers(PRIVATE | FINAL), TypeName("C"), Nil, ...) + +全てのフラグのリストは +`scala.reflect.api.FlagSets#FlagValues` にて定義されており、 +`scala.reflect.api.FlagSets#Flag` から公開されている。 +(一般的には、これを +`import scala.reflect.runtime.universe.Flag._` のようにワイルドカードインポートする。) + +定義の構文木はコンパイル後にはシンボルとなるため、これらの構文木の修飾子のフラグは結果となるシンボルのフラグへと変換される。 +構文木と違ってシンボルはフラグを公開しないが、`isXXX` +というパターンのテストメソッドを提供する +(例えば `isFinal` は final かどうかをテストする)。 +特定のフラグはある種類のシンボルでしか使われないため、場合によってはシンボルを +`asTerm`、`asType`、`asClass` といったメソッドを使って変換する必要がある。 + +**注意:** リフレクションAPI のこの部分は再設計の候補に挙がっている。リフレクションAPI +の将来のリリースにおいてフラグ集合が他のものと置き換わる可能性がある。 + +## 定数 + +Scala の仕様において**定数式** (constant expression) と呼ばれる式は +Scala コンパイラによってコンパイル時に評価することができる。 +以下に挙げる式の種類はコンパイル時定数だ。 +([Scala 言語仕様 の 6.24](http://scala-lang.org/files/archive/spec/2.11/06-expressions.html#constant-expressions) 参照): + +1. プリミティブ値クラスのリテラル ([Byte](http://www.scala-lang.org/api/current/index.html#scala.Byte)、 [Short](http://www.scala-lang.org/api/current/index.html#scala.Short)、 [Int](http://www.scala-lang.org/api/current/index.html#scala.Int)、 [Long](http://www.scala-lang.org/api/current/index.html#scala.Long)、 [Float](http://www.scala-lang.org/api/current/index.html#scala.Float)、 [Double](http://www.scala-lang.org/api/current/index.html#scala.Double)、 [Char](http://www.scala-lang.org/api/current/index.html#scala.Char)、 [Boolean](http://www.scala-lang.org/api/current/index.html#scala.Boolean) および [Unit](http://www.scala-lang.org/api/current/index.html#scala.Unit))。これは直接対応する型で表される。 +2. 文字列リテラル。これは文字列のインスタンスとして表される。 +3. 一般に [scala.Predef#classOf](http://www.scala-lang.org/api/current/index.html#scala.Predef$@classOf[T]:Class[T]) で構築されるクラスへの参照。[型](http://www.scala-lang.org/api/current/index.html#scala.reflect.api.Types$Type)として表される。 +4. Java の列挙要素。[シンボル](http://www.scala-lang.org/api/current/index.html#scala.reflect.api.Symbols$Symbol)として表される。 + +定数式の用例としては + +- 構文木内のリテラル (`scala.reflect.api.Trees#Literal` 参照) +- Java のクラスファイルアノテーションへ渡されるリテラル (`scala.reflect.api.Annotations#LiteralArgument` 参照) + +などがある。具体例をみてみよう。 + + Literal(Constant(5)) + +上の式は Scala ソース内での整数リテラル `5` を表す AST を構築する。 + +`Constant` は「仮想ケースクラス」の一例で、普通のクラスなのだが、あたかもケースクラスであるかのうように構築したりパターンマッチしたりすることができる。 +`Literal` と `LiteralArgument` の両方ともがリテラルのコンパイル時定数を返す +`value` メソッドを公開する。 + +具体例で説明しよう: + + Constant(true) match { + case Constant(s: String) => println("A string: " + s) + case Constant(b: Boolean) => println("A Boolean value: " + b) + case Constant(x) => println("Something else: " + x) + } + assert(Constant(true).value == true) + +クラス参照は `scala.reflect.api.Types#Type` のインスタンスを用いて表される。 +この参照は、`scala.reflect.runtime.currentMirror` のような +`RuntimeMirror` の `runtimeClass` メソッドを用いてランタイムクラスへと変換することができる。 +(Scala コンパイラがクラス参照の処理を行なっている段階においては、その参照が指すランタイムクラスがまだコンパイルされていない可能性があるため、このように型からランタイムクラスへと変換することが必要となる。) + +Java の列挙要素への参照はシンボル (`scala.reflect.api.Symbols#Symbol`) +として表され、対応する列挙要素を JVM 上で返すことができるメソッドを持つ。 +`RuntimeMirror` を使って対応する列挙型や列挙値への参照の実行時の値をインスペクトすることができる。 + +具体例をみてこう: + + // Java ソース: + enum JavaSimpleEnumeration { FOO, BAR } + + import java.lang.annotation.*; + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.TYPE}) + public @interface JavaSimpleAnnotation { + Class classRef(); + JavaSimpleEnumeration enumRef(); + } + + @JavaSimpleAnnotation( + classRef = JavaAnnottee.class, + enumRef = JavaSimpleEnumeration.BAR + ) + public class JavaAnnottee {} + + // Scala ソース: + import scala.reflect.runtime.universe._ + import scala.reflect.runtime.{currentMirror => cm} + + object Test extends App { + val jann = typeOf[JavaAnnottee].typeSymbol.annotations(0).javaArgs + + def jarg(name: String) = jann(TermName(name)) match { + // Constant is always wrapped in a Literal or LiteralArgument tree node + case LiteralArgument(ct: Constant) => value + case _ => sys.error("Not a constant") + } + + val classRef = jarg("classRef").value.asInstanceOf[Type] + println(showRaw(classRef)) // TypeRef(ThisType(), JavaAnnottee, List()) + println(cm.runtimeClass(classRef)) // class JavaAnnottee + + val enumRef = jarg("enumRef").value.asInstanceOf[Symbol] + println(enumRef) // value BAR + + val siblings = enumRef.owner.typeSignature.declarations + val enumValues = siblings.filter(sym => sym.isVal && sym.isPublic) + println(enumValues) // Scope { + // final val FOO: JavaSimpleEnumeration; + // final val BAR: JavaSimpleEnumeration + // } + + val enumClass = cm.runtimeClass(enumRef.owner.asClass) + val enumValue = enumClass.getDeclaredField(enumRef.name.toString).get(null) + println(enumValue) // BAR + } + +## プリティプリンタ + +[`Trees`](http://www.scala-lang.org/api/current/index.html#scala.reflect.api.Trees) と +[`Types`](http://www.scala-lang.org/api/current/index.html#scala.reflect.api.Types) +を整形して表示するユーティリティを説明しよう。 + +### 構文木の表示 + +`show` メソッドは、リフレクションオブジェクトを整形して表示する。 +この形式は Scala コードの糖衣構文を展開して Java のようしたものを提供する。具体例をみていこう: + + scala> import scala.reflect.runtime.universe._ + import scala.reflect.runtime.universe._ + + scala> def tree = reify { final class C { def x = 2 } }.tree + tree: scala.reflect.runtime.universe.Tree + + scala> show(tree) + res0: String = + { + final class C extends AnyRef { + def () = { + super.(); + () + }; + def x = 2 + }; + () + } + +`showRaw` メソッドは、Scala の構文木 (AST) のようなリフレクションオブジェクトの内部構造を表示する。 +これは Scala のタイプチェッカが見るものと同じものだ。 + +ここで注意すべきなのは、この形式どおりに構文木を構築すればマクロの実装でも使えるのじゃないかと思うかもしれないが、うまくいかないことが多いということだ。これはシンボルについての情報などが完全には表示されていないためだ (名前だけが表示される)。 +そのため、妥当な Scala コードがあるときにその AST をインスペクトするのに向いていると言える。 + + scala> showRaw(tree) + res1: String = Block(List( + ClassDef(Modifiers(FINAL), TypeName("C"), List(), Template( + List(Ident(TypeName("AnyRef"))), + emptyValDef, + List( + DefDef(Modifiers(), nme.CONSTRUCTOR, List(), List(List()), TypeTree(), + Block(List( + Apply(Select(Super(This(tpnme.EMPTY), tpnme.EMPTY), nme.CONSTRUCTOR), List())), + Literal(Constant(())))), + DefDef(Modifiers(), TermName("x"), List(), List(), TypeTree(), + Literal(Constant(2))))))), + Literal(Constant(()))) + +`showRaw` はインスペクトしたものの `scala.reflect.api.Types` を併記することができる。 + + scala> import scala.tools.reflect.ToolBox // requires scala-compiler.jar + import scala.tools.reflect.ToolBox + + scala> import scala.reflect.runtime.{currentMirror => cm} + import scala.reflect.runtime.{currentMirror=>cm} + + scala> showRaw(cm.mkToolBox().typeCheck(tree), printTypes = true) + res2: String = Block[1](List( + ClassDef[2](Modifiers(FINAL), TypeName("C"), List(), Template[3]( + List(Ident[4](TypeName("AnyRef"))), + emptyValDef, + List( + DefDef[2](Modifiers(), nme.CONSTRUCTOR, List(), List(List()), TypeTree[3](), + Block[1](List( + Apply[4](Select[5](Super[6](This[3](TypeName("C")), tpnme.EMPTY), ...))), + Literal[1](Constant(())))), + DefDef[2](Modifiers(), TermName("x"), List(), List(), TypeTree[7](), + Literal[8](Constant(2))))))), + Literal[1](Constant(()))) + [1] TypeRef(ThisType(scala), scala.Unit, List()) + [2] NoType + [3] TypeRef(NoPrefix, TypeName("C"), List()) + [4] TypeRef(ThisType(java.lang), java.lang.Object, List()) + [5] MethodType(List(), TypeRef(ThisType(java.lang), java.lang.Object, List())) + [6] SuperType(ThisType(TypeName("C")), TypeRef(... java.lang.Object ...)) + [7] TypeRef(ThisType(scala), scala.Int, List()) + [8] ConstantType(Constant(2)) + +### 型の表示 + +`show` メソッドは型を**可読な**文字列形式で表示することができる: + + scala> import scala.reflect.runtime.universe._ + import scala.reflect.runtime.universe._ + + scala> def tpe = typeOf[{ def x: Int; val y: List[Int] }] + tpe: scala.reflect.runtime.universe.Type + + scala> show(tpe) + res0: String = scala.AnyRef{def x: Int; val y: scala.List[Int]} + +`scala.reflect.api.Trees` のための `showRaw` 同様に、 +`scala.reflect.api.Types` のための `showRaw` +は Scala タイプチェッカが使う Scala AST を表示する。 + + scala> showRaw(tpe) + res1: String = RefinedType( + List(TypeRef(ThisType(scala), TypeName("AnyRef"), List())), + Scope( + TermName("x"), + TermName("y"))) + +この `showRaw` メソッドにはデフォルトでは `false` +になっている名前付きパラメータ `printIds` と `printKinds` を持つ。 +`true` を渡すことで `showRaw` はシンボルのユニークID +とシンボルの種類 (パッケージ、型、メソッド、getter その他) を表示することができる。 + + scala> showRaw(tpe, printIds = true, printKinds = true) + res2: String = RefinedType( + List(TypeRef(ThisType(scala#2043#PK), TypeName("AnyRef")#691#TPE, List())), + Scope( + TermName("x")#2540#METH, + TermName("y")#2541#GET)) + +## 位置情報 + +**位置情報** ([`Position`](http://www.scala-lang.org/api/current/index.html#scala.reflect.api.Position)) +はシンボルや構文木のノードの出処を追跡するのに使われる。警告やエラーの表示でよく使われ、プログラムのどこが間違ったのかを正確に表示することができる。位置情報はソースファイルの列と行を表す。 +(ソースファイルの初めからのオフセットは「ポイント」と呼ばれるが、これは便利ではないことがある) +位置情報はそれが指す行の内容も保持する。全ての構文木やシンボルが位置情報を持つわけではなく、ない場合は +`NoPosition` オブジェクトで表される。 + +位置情報はソースファイルの 1文字を指すこともできれば、文字の範囲を指すこともできる。 +前者の場合は**オフセット位置情報** (offset position)、後者の場合は**範囲位置情報** +(range position) が使われる。範囲位置情報は `start` と `end` オフセットを保持する。 + `start` と `end` オフセットは `focusStart` と `focusEnd` +メソッドを用いて「フォーカス」することができ、これは位置情報を返す +(範囲位置情報では無い位置情報に対して呼ばれた場合は `this` を返す) 。 + +位置情報はいくつかのメソッドを使って比較することができる。 +`precedes` メソッドは、2つの位置情報が定義済みであり (つまり `NoPosition` ではない)、かつ +`this` の位置情報の終点が与えられた位置情報の始点を超えない場合に真を返す。 +他にも、範囲位置情報は (`includes` メソッドを用いて) 包含関係を調べたり、 +(`overlaps` メソッドを用いて) 交差関係を調べることができる。 + +範囲位置情報は**透明** (transparent) か**非透明** (opaque) だ。 +範囲位置情報を持つ構文木は以下の不変条件を満たす必要があるため、範囲位置情報が透明か非透明であるかは許可される用法に関わってくる: + +- オフセット位置情報を持つ構文木は範囲位置情報を持つ部分木を持ってはいけない。 +- 範囲位置情報を持つ構文木が範囲位置情報を持つ部分木を持つ場合、部分木の範囲は親の範囲に包含されなくてはいけない。 +- 同じノードの部分木の非透明な範囲位置情報同士は交差してはいけない。 (このため、交差は最大で単一の点となる) + +`makeTransparent` メソッドを使って非透明な範囲位置情報を透明な他は何も変わらないものに変換することができる。 diff --git a/_ja/overviews/reflection/environment-universes-mirrors.md b/_ja/overviews/reflection/environment-universes-mirrors.md new file mode 100644 index 0000000000..68c68edc65 --- /dev/null +++ b/_ja/overviews/reflection/environment-universes-mirrors.md @@ -0,0 +1,206 @@ +--- +layout: multipage-overview + +discourse: false + +partof: reflection +overview-name: Reflection + +num: 2 + +language: ja +title: 環境、ユニバース、ミラー +--- + +EXPERIMENTAL + +## 環境 + +リフレクションの環境は、リフレクションを用いたタスクが実行時に実行されたのかコンパイル時に実行されたのかによって変わる。 +この環境が実行時かコンパイル時かという違いは**ユニバース**と呼ばれるものによってカプセル化されている。 +リフレクション環境におけるもう 1つの重要なものにリフレクションを用いてアクセスが可能な実体の集合がある。 +この実体の集合は**ミラー**と呼ばれているものによって決定される。 + +例えば、実行時リフレクションによってアクセス可能な実体は `ClassloaderMirror` によって公開されている。 +このミラーは特定のクラスローダによって読み込まれた実体 (パッケージ、型、メンバ) のみへのアクセスを提供する。 + +ミラーはリフレクションを用いてアクセスすることができる実体の集合を決定するだけではなく、 +それらの実体に対するリフレクションを用いた演算を提供する。 +例えば、実行時リフレクションにおいて **invoker ミラー**を使うことで任意のクラスのメソッドやコンストラクタを呼び出すことができる。 + +## ユニバース + +実行時とコンパイル時という 2つの主要なリフレクション機能があるため、ユニバースにも 2つのタイプがある。 +その時のタスクに応じて適切なユニバースを選ぶ必要がある。 + +- **実行時リフレクション** のためには `scala.reflect.runtime.universe` +- **コンパイル時リフレクション**のためには `scala.reflect.macros.Universe` + +を選ぶ。 + +ユニバースは、型 (`Type`)、構文木 (`Tree`)、アノテーション (`Annotation`) +といったリフレクションで使われる主要な概念に対するインターフェイスを提供する。 + +## ミラー + +リフレクションによって提供される全ての情報は**ミラー** (mirror) を通して公開されている。 +型情報の種類やリフレクションを用いたタスクの種類によって異なるミラーを使う必要がある。 +**クラスローダミラー**を使うことで型情報やそのメンバを取得することができる。 +クラスローダミラーから、より特殊化された (最も広く使われている) **invoker ミラー** +を取得してリフレクションを使ったメソッドやコンストラクタ呼び出しやフィールドへのアクセスを行うことができる。 + +要約すると: + +- **クラスローダミラー** これらのミラーは (`staticClass`/`staticModule`/`staticPackage` メソッドを使って) 名前をシンボルへと翻訳する。 +- **invoker ミラー** これらのミラーは (`MethodMirror.apply` や `FieldMirror.get` といったメソッドを使って) リフレクションを用いた呼び出しを実装する。これらの invoker ミラーは最も広く使われているミラーだ。 + +### 実行時のミラー + +実行時におけるミラーの作り方は `ru.runtimeMirror()` だ (ただし、`ru` は `scala.reflect.runtime.universe`)。 + +`scala.reflect.api.JavaMirrors#runtimeMirror` の戻り値は +`scala.reflect.api.Mirrors#ReflectiveMirror` 型のクラスローダミラーで、名前 (`name`) からシンボル (`symbol`) を読み込むことができる。 + +クラスローダミラーから +(`scala.reflect.api.Mirrors#InstanceMirror`、 `scala.reflect.api.Mirrors#MethodMirror`、 `scala.reflect.api.Mirrors#FieldMirror`、`scala.reflect.api.Mirrors#ClassMirror`、そして `scala.reflect.api.Mirrors#ModuleMirror` を含む) +invoker ミラーを作成することができる。 + +以下に具体例を用いてこれら 2つのタイプのミラーがどう関わっているのかを説明する。 + +### ミラーの型とその用例 + +`ReflectiveMirror` は名前を用いてシンボルを読み込むのと、invoker ミラーを作るのに使われる。作り方: `val m = ru.runtimeMirror()`。 +具体例: + + scala> val ru = scala.reflect.runtime.universe + ru: scala.reflect.api.JavaUniverse = ... + + scala> val m = ru.runtimeMirror(getClass.getClassLoader) + m: scala.reflect.runtime.universe.Mirror = JavaMirror ... + +`InstanceMirror` はメソッド、フィールド、内部クラス、および内部オブジェクトの invoker ミラーを作成するのに使われる。作り方: `val im = m.reflect()`。 +具体例: + + scala> class C { def x = 2 } + defined class C + + scala> val im = m.reflect(new C) + im: scala.reflect.runtime.universe.InstanceMirror = instance mirror for C@3442299e + +`MethodMirror` はインスタンス・メソッド (Scala にはインスタンス・メソッドのみがある。オブジェクトのメソッドは `ModuleMirror.instance` から取得されるオブジェクト・インスタンスのインスタンス・メソッドだ。) の呼び出しに使われる。作り方: `val mm = im.reflectMethod()`。 +具体例: + + scala> val methodX = ru.typeOf[C].declaration(ru.TermName("x")).asMethod + methodX: scala.reflect.runtime.universe.MethodSymbol = method x + + scala> val mm = im.reflectMethod(methodX) + mm: scala.reflect.runtime.universe.MethodMirror = method mirror for C.x: scala.Int (bound to C@3442299e) + + scala> mm() + res0: Any = 2 + +`FieldMirror` はインスタンス・フィールドの get と set を行うのに使われる (メソッド同様に Scala はインスタンス・フィールドのみがある。)。作り方: `val fm = im.reflectField()`。 +具体例: + + scala> class C { val x = 2; var y = 3 } + defined class C + + scala> val m = ru.runtimeMirror(getClass.getClassLoader) + m: scala.reflect.runtime.universe.Mirror = JavaMirror ... + + scala> val im = m.reflect(new C) + im: scala.reflect.runtime.universe.InstanceMirror = instance mirror for C@5f0c8ac1 + + scala> val fieldX = ru.typeOf[C].declaration(ru.TermName("x")).asTerm.accessed.asTerm + fieldX: scala.reflect.runtime.universe.TermSymbol = value x + + scala> val fmX = im.reflectField(fieldX) + fmX: scala.reflect.runtime.universe.FieldMirror = field mirror for C.x (bound to C@5f0c8ac1) + + scala> fmX.get + res0: Any = 2 + + scala> fmX.set(3) + + scala> val fieldY = ru.typeOf[C].declaration(ru.TermName("y")).asTerm.accessed.asTerm + fieldY: scala.reflect.runtime.universe.TermSymbol = variable y + + scala> val fmY = im.reflectField(fieldY) + fmY: scala.reflect.runtime.universe.FieldMirror = field mirror for C.y (bound to C@5f0c8ac1) + + scala> fmY.get + res1: Any = 3 + + scala> fmY.set(4) + + scala> fmY.get + res2: Any = 4 + +`ClassMirror` はコンストラクタの invoker ミラーを作成するのに使われる。作り方: 静的クラスは `val cm1 = m.reflectClass()`、内部クラスは `val mm2 = im.reflectClass()`。 +具体例: + + scala> case class C(x: Int) + defined class C + + scala> val m = ru.runtimeMirror(getClass.getClassLoader) + m: scala.reflect.runtime.universe.Mirror = JavaMirror ... + + scala> val classC = ru.typeOf[C].typeSymbol.asClass + classC: scala.reflect.runtime.universe.Symbol = class C + + scala> val cm = m.reflectClass(classC) + cm: scala.reflect.runtime.universe.ClassMirror = class mirror for C (bound to null) + + scala> val ctorC = ru.typeOf[C].declaration(ru.nme.CONSTRUCTOR).asMethod + ctorC: scala.reflect.runtime.universe.MethodSymbol = constructor C + + scala> val ctorm = cm.reflectConstructor(ctorC) + ctorm: scala.reflect.runtime.universe.MethodMirror = constructor mirror for C.(x: scala.Int): C (bound to null) + + scala> ctorm(2) + res0: Any = C(2) + +`ModuleMirror` はシングルトン・オブジェクトのインスタンスにアクセスするのに使われる。作り方: 静的なオブジェクトは `val mm1 = m.reflectModule()`、内部オブジェクトは `val mm2 = im.reflectModule()`。 +具体例: + + scala> object C { def x = 2 } + defined module C + + scala> val m = ru.runtimeMirror(getClass.getClassLoader) + m: scala.reflect.runtime.universe.Mirror = JavaMirror ... + + scala> val objectC = ru.typeOf[C.type].termSymbol.asModule + objectC: scala.reflect.runtime.universe.ModuleSymbol = object C + + scala> val mm = m.reflectModule(objectC) + mm: scala.reflect.runtime.universe.ModuleMirror = module mirror for C (bound to null) + + scala> val obj = mm.instance + obj: Any = C$@1005ec04 + +### コンパイル時ミラー + +コンパイル時ミラーは名前からシンボルを読み込むクラスローダミラーだけが使われる。 + +クラスローダミラーは `scala.reflect.macros.Context#mirror` を用いて作る。 +クラスローダミラーを使う典型的なメソッドには `scala.reflect.api.Mirror#staticClass`、 +`scala.reflect.api.Mirror#staticModule`、 +そして `scala.reflect.api.Mirror#staticPackage` がある。具体例で説明すると: + + import scala.reflect.macros.Context + + case class Location(filename: String, line: Int, column: Int) + + object Macros { + def currentLocation: Location = macro impl + + def impl(c: Context): c.Expr[Location] = { + import c.universe._ + val pos = c.macroApplication.pos + val clsLocation = c.mirror.staticModule("Location") // get symbol of "Location" object + c.Expr(Apply(Ident(clsLocation), List(Literal(Constant(pos.source.path)), Literal(Constant(pos.line)), Literal(Constant(pos.column))))) + } + } + +**注意**: 手動でシンボルを照会する代わりに他の高レベルな方法もある。例えば、文字列を使わなくてもよいため型安全な +`typeOf[Location.type].termSymbol` (もしくは `ClassSymbol` が必要ならば `typeOf[Location].typeSymbol`) がある。 diff --git a/_ja/overviews/reflection/overview.md b/_ja/overviews/reflection/overview.md new file mode 100644 index 0000000000..91a34ea6aa --- /dev/null +++ b/_ja/overviews/reflection/overview.md @@ -0,0 +1,303 @@ +--- +layout: multipage-overview + +partof: reflection +overview-name: Reflection + +num: 1 + +language: ja +title: 概要 + +discourse: false +--- + +EXPERIMENTAL + +**Heather Miller、Eugene Burmako、Philipp Haller 著**
      +**Eugene Yokota 訳** + +**リフレクション** (reflection) とは、プログラムが実行時において自身をインスペクトしたり、変更したりできる能力のことだ。それはオブジェクト指向、関数型、論理プログラミングなど様々なプログラミングのパラダイムに渡って長い歴史を持つ。 +それぞれのパラダイムが、時として顕著に異なる方向性に向けて**現在の**リフレクションを進化させてきた。 +LISP/Scheme のような関数型の言語が動的なインタープリタを可能とすることに比重を置いてきたのに対し、Java のようなオブジェクト指向言語は実行時におけるクラスメンバのインスペクションや呼び出しを実現するための実行時リフレクションに主な比重を置いてきた。 + +複数の言語やパラダイムに渡る主要なリフレクションの用例を以下に 3つ挙げる: + +
        +
      1. 実行時リフレクション。実行時にランタイム型 (runtime type) やそのメンバをインスペクトしたり呼び出す能力。
      2. +
      3. コンパイル時リフレクション。コンパイル時に抽象構文木にアクセスしたり、それを操作する能力。
      4. +
      5. レイフィケーション (reification)。(1) の場合は実行時に、(2) の場合はコンパイル時に抽象構文木を生成すること。
      6. +
      + +Scala 2.10 までは Scala は独自のリフレクション機能を持っていなかった。 +代わりに、Java リフレクションを使って (1) の実行時リフレクションのうちの非常に限定的な一部の機能のみを使うことができた。 +しかし、存在型、高カインド型、パス依存型、抽象型など多くの Scala 独自の型の情報はそのままの Java リフレクションのもとでは実行時に復元不可能だった。 +これらの Scala 独自の型に加え、Java リフレクションはコンパイル時にジェネリックである Java 型の実行時型情報も復元できない。 +この制約は Scala のジェネリック型の実行時リフレクションも受け継いでいる。 + +Scala 2.10 は、Scala 独自型とジェネリック型に対する Java の実行時リフレクションの欠点に対処するためだけではなく、 +汎用リフレクション機能を持ったより強力なツールボックスを追加するために新しいリフレクションのライブラリを導入する。 +Scala 型とジェネリックスに対する完全な実行時リフレクション (1) の他に、 +Scala 2.10 は[マクロ]({{site.baseurl}}/ja/overviews/macros/overview.html) という形でコンパイル時リフレクション機能 (2) と、 +Scala の式を抽象構文木へと**レイファイ** (reify) する機能 (3) も提供する。 + +## 実行時リフレクション + +実行時リフレクション (runtime reflection) とは何だろう? +**実行時**に何らかの型もしくはオブジェクトが渡されたとき、リフレクションは以下のことができる: + +
        +
      • ジェネリック型を含め、そのオブジェクトがどの型かをインスペクトでき、
      • +
      • 新しいオブジェクトを作成することができ、
      • +
      • そのオブジェクトのメンバにアクセスしたり、呼び出したりできる。
      • +
      + +それぞれの能力をいくつかの具体例とともにみていこう。 + +### 具体例 + +  +#### ランタイム型のインスペクション (実行時におけるジェネリック型も含む) + +他の JVM言語同様に、Scala の型はコンパイル時に**消去** (erase) される。 +これは、何らかのインスタンスのランタイム型をインスペクトしてもコンパイル時に +Scala コンパイラが持つ型情報を全ては入手できない可能性があることを意味する。 + +**型タグ** (`TypeTag`) は、コンパイル時に入手可能な全ての型情報を実行時に持ち込むためのオブジェクトだと考えることができる。 +しかし、型タグは常にコンパイラによって生成されなくてはいけないことに注意してほしい。 +この生成は暗黙のパラメータか context bound によって型タグが必要とされた時にトリガーされる。 +そのため、通常は、型タグは暗黙のパラメータか context bound によってのみ取得できる。 + +例えば、context bound を使ってみよう: + + scala> import scala.reflect.runtime.{universe => ru} + import scala.reflect.runtime.{universe=>ru} + + scala> val l = List(1,2,3) + l: List[Int] = List(1, 2, 3) + + scala> def getTypeTag[T: ru.TypeTag](obj: T) = ru.typeTag[T] + getTypeTag: [T](obj: T)(implicit evidence$1: ru.TypeTag[T])ru.TypeTag[T] + + scala> val theType = getTypeTag(l).tpe + theType: ru.Type = List[Int] + +上の例では、まず `scala.reflect.runtime.universe` をインポートして +(型タグを使うためには必ずインポートされる必要がある)、`l` という名前の `List[Int]` を作る。 +次に、context bound を持った型パラメータ `T` を持つ `getTypeTag` というメソッドは定義する +(REPL が示すとおり、これは暗黙の evidence パラメータを定義することに等価であり、コンパイラは `T` に対する型タグを生成する)。 +最後に、このメソッドに `l` を渡して呼び出し、`TypeTag` に格納される型を返す `tpe` を呼び出す。 +見ての通り、正しい完全な型 (つまり、`List` の具象型引数を含むということ) である `List[Int]` が返ってきた。 + +目的の `Type` のインスタンスが得られれば、これをインスペクトすることもできる。以下に具体例で説明しよう: + + scala> val decls = theType.declarations.take(10) + decls: Iterable[ru.Symbol] = List(constructor List, method companion, method isEmpty, method head, method tail, method ::, method :::, method reverse_:::, method mapConserve, method ++) + +#### ランタイム型のインスタンス化 + +リフレクションによって得られた型は適当な invoker ミラーを使ってコンストラクタを呼び出すことでインスタンス化することができる +(ミラーに関しては[後ほど]({{ site.baseurl }}/ja/overviews/reflection/overview.html#mirrors)説明する)。 +以下に REPL を使った具体例を用いて説明しよう: + + scala> case class Person(name: String) + defined class Person + + scala> val m = ru.runtimeMirror(getClass.getClassLoader) + m: scala.reflect.runtime.universe.Mirror = JavaMirror with ... + +最初のステップとして現在のクラスローダで読み込まれた (`Person` クラスを含む) +全てのクラスや型をアクセス可能とするミラー `m` を取得する。 + + scala> val classPerson = ru.typeOf[Person].typeSymbol.asClass + classPerson: scala.reflect.runtime.universe.ClassSymbol = class Person + + scala> val cm = m.reflectClass(classPerson) + cm: scala.reflect.runtime.universe.ClassMirror = class mirror for Person (bound to null) + +次に、`reflectClass` メソッドを使って `Person` クラスの `ClassMirror` を取得する。 +`ClassMirror` は `Person` クラスのコンストラクタへのアクセスを提供する。 + + scala> val ctor = ru.typeOf[Person].declaration(ru.nme.CONSTRUCTOR).asMethod + ctor: scala.reflect.runtime.universe.MethodSymbol = constructor Person + +`Person` のコンストラクタのシンボルは実行時ユニバース `ru` を用いて `Person` 型の宣言から照会することによってのみ得られる。 + + scala> val ctorm = cm.reflectConstructor(ctor) + ctorm: scala.reflect.runtime.universe.MethodMirror = constructor mirror for Person.(name: String): Person (bound to null) + + scala> val p = ctorm("Mike") + p: Any = Person(Mike) + +#### ランタイム型のメンバへのアクセスと呼び出し + +一般的に、ランタイム型のメンバは適当な invoker ミラーを使ってコンストラクタを呼び出すことでインスタンス化することができる +(ミラーに関しては[後ほど]({{ site.baseurl }}/ja/overviews/reflection/overview.html#mirrors)説明する)。 +以下に REPL を使った具体例を用いて説明しよう: + + scala> case class Purchase(name: String, orderNumber: Int, var shipped: Boolean) + defined class Purchase + + scala> val p = Purchase("Jeff Lebowski", 23819, false) + p: Purchase = Purchase(Jeff Lebowski,23819,false) + +この例では `Purchase` `p` の `shipped` フィールドをリフレクションを使って get/set を行う: + + scala> import scala.reflect.runtime.{universe => ru} + import scala.reflect.runtime.{universe=>ru} + + scala> val m = ru.runtimeMirror(p.getClass.getClassLoader) + m: scala.reflect.runtime.universe.Mirror = JavaMirror with ... + +`shipped` メンバにアクセスするには、前の例と同じく、`p` のクラス (`Purchase`) を含むクラスローダが読み込んだ全てのクラスを入手可能とするミラー `m` +を取得することから始める。 + + scala> val shippingTermSymb = ru.typeOf[Purchase].declaration(ru.TermName("shipped")).asTerm + shippingTermSymb: scala.reflect.runtime.universe.TermSymbol = method shipped + +次に、`shipped` フィールドの宣言を照会して `TermSymbol` (`Symbol` 型の 1つ) を得る。 +この `Symbol` は後で (何からのオブジェクトの) このフィールドの値にアクセスするのに必要なミラーを得るのに使う。 + + scala> val im = m.reflect(p) + im: scala.reflect.runtime.universe.InstanceMirror = instance mirror for Purchase(Jeff Lebowski,23819,false) + + scala> val shippingFieldMirror = im.reflectField(shippingTermSymb) + shippingFieldMirror: scala.reflect.runtime.universe.FieldMirror = field mirror for Purchase.shipped (bound to Purchase(Jeff Lebowski,23819,false)) + +ある特定のインスタンスの `shipped` メンバにアクセスするためには、その特定のインスタンス `p` +のためのミラー `im` を必要とする。 +このインスタンスミラーから `p` の型のフィールドを表す `TermSymbol` に対して `FieldMirror` を得ることができる。 + +特定のフィールドに対して `FieldMirror` が得られたところで、`get` と `set` メソッドを使って特定のインスタンスの +`shipped` メンバを get/set できる。 +`shipped` の状態を `true` に変更してみよう。 + + scala> shippingFieldMirror.get + res7: Any = false + + scala> shippingFieldMirror.set(true) + + scala> shippingFieldMirror.get + res9: Any = true + +### Java のランタイムクラス と Scala のランタイム型の比較 + +Java のリフレクションを使って実行時に Java の **Class** +のインスタンスを取得したことのある読者は、Scala ではランタイム**型**を取得することに気付いただろう。 + +以下の REPL の実行結果は Scala のクラスに対して Java +リフレクションを使った場合に予想外もしくは間違った結果が返ってくることがあることを示す。 + +まず、抽象型メンバ `T` を持つ基底クラス `E` を定義して、それから 2つの派生クラス基底 +`C` と `D` を派生する。 + + scala> class E { + | type T + | val x: Option[T] = None + | } + defined class E + + scala> class C extends E + defined class C + + scala> class D extends C + defined class D + +次に具象型メンバ `T` (この場合 `String`) を使う `C` と `D` のインスタンスを作成する。 + + scala> val c = new C { type T = String } + c: C{type T = String} = $anon$1@7113bc51 + + scala> val d = new D { type T = String } + d: D{type T = String} = $anon$1@46364879 + +ここで Java リフレクションの `getClass` と `isAssignableFrom` メソッドを使って +`c` と `d` のランタイムクラスを表す `java.lang.Class` のインスタンスを取得して、 +`d` のランタイムクラスが `c` のランタイムクラスのサブクラスであるかを検証する。 + + scala> c.getClass.isAssignableFrom(d.getClass) + res6: Boolean = false + +`D` が `C` を継承することは上のコードにより明らかなので、この結果は意外なものかもしれない。 +この「`d` のクラスは `c` のクラスのサブクラスであるか?」 +というような簡単な実行時型検査において期待される答は `true` だと思う。 +しかし、上の例で気付いたかもしれないが、`c` と `d` がインスタンス化されるとき +Scala コンパイラは実はそれぞれに `C` と `D` の匿名のサブクラスを作成している。 +これは Scala コンパイラが Scala 特定の (つまり、非 Java の) 言語機能を +JVM 上で実行させるために等価な Java バイトコードに翻訳する必要があるからだ。 +そのため、Scala コンパイラは往々にしてユーザが定義したクラスの代わりに合成クラス (つまり、自動的に生成されたクラス) +を作成してそれを実行時に使用する。これは Scala +では日常茶飯事と言ってもいいぐらいで、クロージャ、型メンバ、型の細別、ローカルクラスなど多くの +Scala 機能に対して Java リフレクションを使う事で観測することができる。 + +このような状況においては、これらの Scala オブジェクトに対して +Scala リフレクションを使うことで正確なランタイム型を得ることができる。 +Scala のランタイム型は全てのコンパイル時の型情報を保持することでコンパイル時と実行時の型のミスマッチを回避している。 + +以下に Scala リフレクションを使って渡された 2つの引数のランタイム型を取得して両者のサブタイプ関係をチェックするメソッドを定義する。 +もしも、第1引数の型が第2引数の型のサブタイプである場合は `true` を返す。 + + scala> import scala.reflect.runtime.{universe => ru} + import scala.reflect.runtime.{universe=>ru} + + scala> def m[T: ru.TypeTag, S: ru.TypeTag](x: T, y: S): Boolean = { + | val leftTag = ru.typeTag[T] + | val rightTag = ru.typeTag[S] + | leftTag.tpe <:< rightTag.tpe + | } + m: [T, S](x: T, y: S)(implicit evidence$1: scala.reflect.runtime.universe.TypeTag[T], implicit evidence$2: scala.reflect.runtime.universe.TypeTag[S])Boolean + + scala> m(d, c) + res9: Boolean = true + +以上に示した通り、これは期待される結果を返す。`d` のランタイム型は確かに `c` のランタイム型のサブタイプだ。 + +## コンパイル時リフレクション + +Scala リフレクションは、プログラムがコンパイル時に**自身**を変更するという**メタプログラミング**の一種を可能とする。 +このコンパイル時リフレクションはマクロという形で実現されており、抽象構文木を操作するメソッドをコンパイル時に実行できる能力として提供される。 + +マクロの特に興味深い側面の1つは `scala.reflect.api` で提供される Scala の実行時リフレクションの基となっている +API に基づいていることだ。これにより、マクロと実行時リフレクションを利用した実装の間で汎用コードを共有することが可能となっている。 + +[マクロのガイド]({{ site.baseurl }}/ja/overviews/macros/overview.html)はマクロ固有のことに焦点を絞っているのに対し、 +本稿ではリフレクション API 全般を取り扱っていることに注意してほしい。 +しかし、[シンボル、構文木、型]({{site.baseurl }}/ja/overviews/reflection/symbols-trees-types.html)の節で詳しく説明される抽象構文木のように多くの概念は直接マクロにも応用することができる。 + +## 環境 + +全てのリフレクションを用いたタスクは適切な環境設定を必要とする。 +この環境はリフレクションを用いたタスクが実行時に行われるのかコンパイル時に行われるのかによって異なる。 +実行時とコンパイル時における環境の違いは**ユニバース**と呼ばれているものによってカプセル化されている。 +リフレクション環境におけるもう 1つの重要なものにリフレクションを用いてアクセスが可能な実体の集合がある。 +この実体の集合は**ミラー**と呼ばれているものによって決定される。 + +ミラーはリフレクションを用いてアクセスすることができる実体の集合を決定するだけではなく、 +それらの実体に対するリフレクションを用いた演算を提供する。 +例えば、実行時リフレクションにおいて **invoker ミラー**を使うことで任意のクラスのメソッドやコンストラクタを呼び出すことができる。 + +### ユニバース + +ユニバース (`Universe`) は Scala リフレクションへの入り口だ。 +ユニバースは、型 (`Type`)、構文木 (`Tree`)、アノテーション (`Annotation`) +といったリフレクションで使われる主要な概念に対するインターフェイスを提供する。 +詳細はこのガイドの[ユニバース]({{ site.baseurl}}/ja/overviews/reflection/environment-universes-mirrors.html)の節か、 +`scala.reflect.api` パッケージの[ユニバースの API doc](http://www.scala-lang.org/api/{{ site.scala-version}}/scala/reflect/api/Universe.html) +を参考にしてほしい。 + +このガイドにおける多くの例を含め、Scala リフレクションを利用するには何らかの +`Universe` もしくはその `Universe` のメンバをインポートする必要がある。 +典型的には実行時リフレクションを利用するには +`scala.reflect.runtime.universe` の全てのメンバをワイルドカードインポートを用いてインポートする: + + import scala.reflect.runtime.universe._ + +### ミラー + +ミラー (`Mirror`) は Scala リフレクションの中心部を構成する。 +リフレクションによって提供される全ての情報はこのミラーと呼ばれるものを通して公開されている。 +型情報の種類やリフレクションを用いたタスクの種類によって異なるミラーを使う必要がある。 + +詳細はこのガイドの[ミラー]({{ site.baseurl}}/ja/overviews/reflection/environment-universes-mirrors.html)の節か、 +`scala.reflect.api` パッケージの[ミラーの API doc](http://www.scala-lang.org/api/{{ site.scala-version}}/scala/reflect/api/Mirrors.html) +を参考にしてほしい。 diff --git a/_ja/overviews/reflection/symbols-trees-types.md b/_ja/overviews/reflection/symbols-trees-types.md new file mode 100644 index 0000000000..4d92411afc --- /dev/null +++ b/_ja/overviews/reflection/symbols-trees-types.md @@ -0,0 +1,698 @@ +--- +layout: multipage-overview + +discourse: false + +partof: reflection +overview-name: Reflection + +num: 3 + +language: ja +title: シンボル、構文木、型 +--- + +EXPERIMENTAL + +## シンボル + +**シンボル** (symbol) は名前 (name) とその名前が参照するクラスやメソッドのような実体 +(entity) の間のバインディングを作るのに用いられる。Scala において定義され名前を付けられるものは全て関連付けられたシンボルを持つ。 + +シンボルは実体 (`class`、`object`、`trait` など) もしくはメンバ (`val`、`var`、`def` など) +の宣言に関する全ての情報を格納するため、実行時リフレクションとコンパイル時リフレクション +(マクロ) の両方において中心的な役割を果たす抽象体だ。 + +全てのシンボルにある基本的な `name` メソッドをはじめ、より複雑で込み入った概念である +`ClassSymbol` に定義される `baseClasses` を取得するメソッドなど、シンボルは幅広い情報を提供する。 +もう一つの一般的なシンボルの利用方法としてはメンバのシグネチャのインスペクトや、 +クラスの型パラメータの取得、メソッドのパラメータ型の取得、フィールドの型の取得などが挙げられる。 + +### シンボルのオーナーの階層 + +シンボルは階層化されている。 +例えば、メソッドのパラメータを表すシンボルはそのメソッドのシンボルに**所有**されており、 +メソッドのシンボルはそれを内包するクラス、トレイト、もしくはオブジェクトに**所有**されており、 +クラスはそれを含むパッケージに**所有**されいてるという具合だ。 + +例えばトップレベルのパッケージのようなトップレベルの実体であるためにシンボルにオーナーが無い場合は、 +`NoSymbol` という特殊なシングルトン・オブジェクトのオーナーが用いられる。 +シンボルが無いことを表す `NoSymbol` は空を表わしたり、デフォルトの値として API の中で多用されている。 +`NoSymbol` の `owner` にアクセスすると例外が発生する。 +[`Symbol`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/reflect/api/Symbols$SymbolApi.html) +型によって提供される一般インターフェイスに関しては API doc を参照してほしい。 + +### 型シンボル (`TypeSymbol`) + +型シンボル (`TypeSymbol`) は型、クラス、トレイトの宣言そして、型パラメータを表す。 +より特定の `ClassSymbol` には当てはまらないメンバとして `isAbstractType`、 +`isContravariant`、`isCovariant` といったメソッドを持つ。 + +- `ClassSymbol`: クラスやトレイトの宣言に格納される全ての情報へのアクセスを提供する。具体的には、`name`、修飾子 (`isFinal`、 `isPrivate`、 `isProtected`、 `isAbstractClass` など)、 `baseClasses`、 `typeParams` など。 + +### 項シンボル (`TermSymbol`) + +項シンボル (`TermSymbol`) は `val`、`var`、`def`、そしてオブジェクトの宣言、パッケージや値のパラメータを表す。 + +- メソッド・シンボル (`MethodSymbol`) は `def` の宣言を表す (`TermSymbol` のサブクラスだ)。メソッドが(基本)コンストラクタであるか、可変長引数をサポートするかなどの問い合わせを行うことができる。 +- モジュール・シンボル (`ModuleSymbol`) はオブジェクトの宣言を表す。`moduleClass` メンバを用いてオブジェクトに暗黙的に関連付けられているクラスを照会することができる。逆の照会も可能だ。モジュール・クラスから `selfType.termSymbol` によって関連付けられるモジュール・シンボルを得られる。 + +### シンボル変換 + +状況によっては汎用の `Symbol` 型を返すメソッドを使う場面があるかもしれない。 +その場合、汎用の `Symbol` 型をより特定の特殊化されたシンボル型へと変換することができる。 +例えば `MethodSymbol` のインターフェイスを使いたいといった状況に合わせて、`asMethod` や `asClass` のようなシンボル変換を用いると特殊化した +`Symbol` のサブタイプに変換することができる。 + +具体例を用いて説明しよう。 + + scala> import scala.reflect.runtime.universe._ + import scala.reflect.runtime.universe._ + + scala> class C[T] { def test[U](x: T)(y: U): Int = ??? } + defined class C + + scala> val testMember = typeOf[C[Int]].member(TermName("test")) + testMember: scala.reflect.runtime.universe.Symbol = method test + +この場合、`member` は期待される `MethodSymbol` ではなく `Symbol` のインスタンスを返す。 +このため、`asMethod` を使って `MethodSymbol` が返されたことを保証する必要がある。 + + scala> testMember.asMethod + res0: scala.reflect.runtime.universe.MethodSymbol = method test + +### 自由シンボル + +2つのシンボル型 `FreeTermSymbol` と `FreeTypeSymbol` +は入手可能な情報が不完全であるという特殊なステータスを持つシンボルだ。 +これらのシンボルはレイフィケーションの過程において生成される +(詳しくは構文木のレイフィケーションの節を参照)。 +レイフィケーションがシンボルを特定できない場合 +(例えば、ローカルクラスを参照しているため、あるシンボルが対応するクラスファイルから見つけることができない場合) +元の名前とオーナー、そして元の型シグネチャに似た代理シグネチャを持った合成のダミーシンボルへとレイファイする。このシンボルは自由型 +(free type) と呼ばれる。 +あるシンボルが自由型かどうかは `sym.isFreeType` を呼ぶことで確かめることができる。 +また、`tree.freeTypes` を呼ぶことで特定の構文木とその部分木から参照されている全ての自由型のリストを取得することができる。 +最後に、`-Xlog-free-types` を用いることでレイフィケーションが自由型を生成したときに警告を得ることができる。 + +## 型 + +名前が示すとおり、`Type` のインスタンスは対応するシンボルの型情報を表す。 +これは、直接宣言もしくは継承されたメンバ (メソッド、フィールド、型エイリアス、抽象型、内部クラス、トレイトなど)、 +基底型、型消去などを含む。他にも、型は型の適合性 (conformance) や等価性 (equivalence) を検査することができる。 + +### 型のインスタンス化 + +一般的には以下の 3通りの方法で `Type` を得ることができる。 + +1. `Universe` にミックスインされている `scala.reflect.api.TypeTags` の `typeOf` メソッド経由。(最も簡単で、一般的な方法) +2. `Int`、`Boolean`、`Any`、や `Unit` のような標準型はユニバースからアクセス可能だ。 +3. `scala.reflect.api.Types` の `typeRef` や `polyType` といったメソッドを使った手動のインスタンス化。(非推奨) + +#### `typeOf` を用いた型のインスタンス化 + +多くの場合、型をインスタンス化するのには +`scala.reflect.api.TypeTags#typeOf` メソッドを使うことができる。 +これは型引数を受け取り、その引数を表す `Type` のインスタンスを返す。 +具体例で説明すると、 + + scala> import scala.reflect.runtime.universe._ + import scala.reflect.runtime.universe._ + + scala> typeOf[List[Int]] + res0: scala.reflect.runtime.universe.Type = scala.List[Int] + +この例では、型コンストラクタ `List` に型引数 `Int` が適用された +[`scala.reflect.api.Types$TypeRef`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/reflect/api/Types$TypeRef.html) +が返っている。 + +しかし、この方法はインスタンス化しようとしている型を手動で指定する必要があることに注意してほしい。 +もし任意のインスタンスに対応する `Type` のインスタンスを取得しようとしてる場合はどうすればいいだろう? +型パラメータに context bound を付けたメソッドを定義すればいいだけだ。これは特殊な `TypeTag` +を生成し、そこから任意のインスタンスに対する型を取得することができる: + + scala> def getType[T: TypeTag](obj: T) = typeOf[T] + getType: [T](obj: T)(implicit evidence$1: scala.reflect.runtime.universe.TypeTag[T])scala.reflect.runtime.universe.Type + + scala> getType(List(1,2,3)) + res1: scala.reflect.runtime.universe.Type = List[Int] + + scala> class Animal; class Cat extends Animal + defined class Animal + defined class Cat + + scala> val a = new Animal + a: Animal = Animal@21c17f5a + + scala> getType(a) + res2: scala.reflect.runtime.universe.Type = Animal + + scala> val c = new Cat + c: Cat = Cat@2302d72d + + scala> getType(c) + res3: scala.reflect.runtime.universe.Type = Cat + +**注意**: `typeOf` メソッドは、型パラメータを受け取る型 +(例えば、`A` が型パラメータであるとき `typeOf[List[A]]`) では動作しない。 +その場合は、代わりに `scala.reflect.api.TypeTags#weakTypeOf` を使うことができる。 +これに関する詳細はこのガイドの [TypeTags]({{ site.baseurl }}/ja/overviews/reflection/typetags-manifests.html) +に関する節を参照。 + +#### 標準型 + +`Int`、`Boolean`、`Any`、や `Unit` のような標準型はユニバースの `definitions` メンバからアクセス可能だ。 +具体的には、 + + scala> import scala.reflect.runtime.universe + import scala.reflect.runtime.universe + + scala> val intTpe = universe.definitions.IntTpe + intTpe: scala.reflect.runtime.universe.Type = Int + +標準型のリストは [`scala.reflect.api.StandardDefinitions`](http://www.scala-lang.org/api/current/index.html#scala.reflect.api.StandardDefinitions$StandardTypes) 内の `StandardTypes` +トレイトにて定義されている。 + +### 型の一般的な演算 + +型の典型的な用例としては型の適合性の検査や、メンバの問い合わせがある。 +型に対する演算を 3つに大別すると以下のようになる: + +
        +
      1. 2つの型の間のサブタイプ関係の検査。
      2. +
      3. 2つの型の間の等価性の検査。
      4. +
      5. 渡された型の特定のメンバや内部型の問い合わせ。
      6. +
      + +#### サブタイプ関係 + +2つの `Type` インスタンスがあるとき、`<:<` を用いて簡単に一方がもう片方のサブタイプであるかを調べることができる。 +(後で説明する例外的な場合においては、`weak_<:<` を使う) + + scala> import scala.reflect.runtime.universe._ + import scala-lang.reflect.runtime.universe._ + + scala> class A; class B extends A + defined class A + defined class B + + scala> typeOf[A] <:< typeOf[B] + res0: Boolean = false + + scala> typeOf[B] <:< typeOf[A] + res1: Boolean = true + +`weak_<:<` メソッドは、2つの型の間の**弱い適合性** (weak conformance) をチェックするのに使われることに注意。 +これは典型的には数値型を取り扱う際に重要となる。 + +Scala の数値型は以下の順序付けに従っている (Scala 言語仕様 3.5.3 節): + +> Scala はいくつかの状況では、より一般的な適合性関係を用います。 もし `S <: T` であるか、あるいは、`S` と `T` 両方がプリミティブな数値型で、次の順序中で `S` が `T` の前にあるなら、型 `S` は型 `T` に弱く適合するといい、`S <:w T` と書きます。 + +| 弱適合性関係 | + --------------------------- +| `Byte` `<:w` `Short` | +| `Short` `<:w` `Int` | +| `Char` `<:w` `Int` | +| `Int` `<:w` `Long` | +| `Long` `<:w` `Float` | +| `Float` `<:w` `Double` | + +例えば、以下の if-式の型は弱い適合性によって決定されている。 + + scala> if (true) 1 else 1d + res2: Double = 1.0 + +上記の if-式では結果の型は 2つの型の**弱い最小の上限境界** +(weak least upper bound、つまり弱い適合性上で最小の上限境界) だと定義されている。 + +`Int` と `Double` の間では (上記の仕様により) `Double` +が弱い適合性上での最小の上限境界だと定義されいるため、 +`Double` が例の if-式の型だと推論される。 + +`weak_<:<` メソッドは弱い適合性をチェックすることに注意してほしい。 +(それに対して、`<:<` は仕様 3.5.3 節の弱い適合性を考慮しない適合性を検査する) +そのため、数値型 `Int` と `Double` の適合性関係を正しくインスペクトできる: + + scala> typeOf[Int] weak_<:< typeOf[Double] + res3: Boolean = true + + scala> typeOf[Double] weak_<:< typeOf[Int] + res4: Boolean = false + +`<:<` を使った場合は `Int` と `Double` は互いに不適合であると間違った結果となる: + + scala> typeOf[Int] <:< typeOf[Double] + res5: Boolean = false + + scala> typeOf[Double] <:< typeOf[Int] + res6: Boolean = false + +#### 型の等価性 + +型の適合性同様に 2つの型の等価性を簡単に検査することができる。 +2つの任意の型が与えられたとき、`=:=` メソッドを使うことでそれらが全く同一のコンパイル時型を表記しているかを調べることができる。 + + scala> import scala.reflect.runtime.universe._ + import scala.reflect.runtime.universe._ + + scala> def getType[T: TypeTag](obj: T) = typeOf[T] + getType: [T](obj: T)(implicit evidence$1: scala.reflect.runtime.universe.TypeTag[T])scala.reflect.runtime.universe.Type + + scala> class A + defined class A + + scala> val a1 = new A; val a2 = new A + a1: A = A@cddb2e7 + a2: A = A@2f0c624a + + scala> getType(a1) =:= getType(a2) + res0: Boolean = true + +両方のインスタンスの型情報が寸分違わず一致している必要があることに注意してほしい。 +例えば、以下のコードにおいて異なる型引数を取る 2つの `List` のインスタンスがある。 + + scala> getType(List(1,2,3)) =:= getType(List(1.0, 2.0, 3.0)) + res1: Boolean = false + + scala> getType(List(1,2,3)) =:= getType(List(9,8,7)) + res2: Boolean = true + +また、型の等価性を検査するためには**常に** `=:=` を使う必要があることに注意してほしい。 +つまり、型エイリアスをチェックすることができない `==` は絶対に使ってはいけないということだ: + + scala> type Histogram = List[Int] + defined type alias Histogram + + scala> typeOf[Histogram] =:= getType(List(4,5,6)) + res3: Boolean = true + + scala> typeOf[Histogram] == getType(List(4,5,6)) + res4: Boolean = false + +見てのとおり、`==` は `Histogram` と `List[Int]` が異なる型であると間違った結果を出している。 + +#### 型に対するメンバと宣言の照会 + +ある `Type` があるとき、特定のメンバや宣言を**照会** (query) することができる。 +`Type` の**メンバ** (member) には全てのフィールド、メソッド、型エイリアス、抽象型、内部クラス/オブジェクト/トレイトなどが含まれる。 +`Type` の**宣言** (declaration) にはその `Type` が表すクラス/オブジェクト/トレイト内で宣言された (継承されなかった) メンバのみが含まれる。 + +ある特定のメンバや宣言の `Symbol` を取得するにはその型に関連する定義のリストを提供する +`members` か `declarations` メソッドを使うだけでいい。単一のシンボルのみを返す +`meber` と `declaration` というメソッドもある。以下に 4つのメソッド全てのシグネチャを示す: + + /** The member with given name, either directly declared or inherited, an + * OverloadedSymbol if several exist, NoSymbol if none exist. */ + def member(name: Universe.Name): Universe.Symbol + + /** The defined or declared members with name name in this type; an + * OverloadedSymbol if several exist, NoSymbol if none exist. */ + def declaration(name: Universe.Name): Universe.Symbol + + /** A Scope containing all members of this type + * (directly declared or inherited). */ + def members: Universe.MemberScope // MemberScope is a type of + // Traversable, use higher-order + // functions such as map, + // filter, foreach to query! + + /** A Scope containing the members declared directly on this type. */ + def declarations: Universe.MemberScope // MemberScope is a type of + // Traversable, use higher-order + // functions such as map, + // filter, foreach to query! + +例えば、`List` の `map` メソッドを照会するには以下のようにする。 + + scala> import scala.reflect.runtime.universe._ + import scala.reflect.runtime.universe._ + + scala> typeOf[List[_]].member("map": TermName) + res0: scala.reflect.runtime.universe.Symbol = method map + +メソッドを照会するために `member` メソッドに `TermName` を渡していることに注意してほしい。 +ここで、`List` の自分型である `Self` のような型メンバを照会する場合は `TypeName` を渡す: + + scala> typeOf[List[_]].member("Self": TypeName) + res1: scala.reflect.runtime.universe.Symbol = type Self + +型の全てのメンバや宣言を面白い方法で照会することもできる。 +`members` メソッドを使って、渡された型の全ての継承もしくは宣言されたメンバを表す +`Symbol` の `Traversable` を取得することができる (`MemberScopeApi` は `Traversable` を継承する)。 +これにより、`foreach`、`filter`、`map` などの馴染み深いコレクションに対する高階関数を使って型のメンバを探検することができる。 +例えば、`List` のメンバのうち private なものだけを表示したいとする: + + scala> typeOf[List[Int]].members.filter(_.isPrivate).foreach(println _) + method super$sameElements + method occCounts + class CombinationsItr + class PermutationsItr + method sequential + method iterateUntilEmpty + +## 構文木 + +**構文木** (`Tree`) は、プログラムを表す Scala の抽象構文の基盤となっている。 +これらは抽象構文木 (abstract syntax tree) とも呼ばれ、一般に AST と略される。 + +Scala リフレクションで、構文木を生成または利用する API には以下のようなものがある: + +1. Scala アノテーションは引数に構文木を用い、`Annotation.scalaArgs` として公開されている。(詳細はこのガイドの[アノテーション]({{ site.baseurl }}/ja/overviews/reflection/names-exprs-scopes-more.html)の節を参照) +2. 任意の式を受け取りその AST を返す `reify` という特殊なメソッド。 +3. マクロを用いたコンパイル時リフレクション (詳細は[マクロ]({{ site.baseurl }}/ja/overview/macros/overview.html)参照) とツールボックスを用いた実行時リフレクションは両方とも構文木を用いてプログラムを表現する。 + +ここで注意してほしいのは構文木は `pos` (`Position`)、 `symbol` (`Symbol`)、 +と型検査の際に代入される `tpe` (`Type`) という 3つのフィールドの他は不変 (immutable) であることだ。 + +### 構文木の種類 + +構文木は以下の 3つのカテゴリーに大別することができる: + +1. **`TermTree` のサブクラス**は項を表す。例えば、メソッドの呼び出しは `Apply` ノードで表され、オブジェクトのインスタンス化は `New` ノードで行われる。 +2. **`TypTree` のサブクラス**はプログラムのソースコード中に現れる型を表す。例えば、`List[Int]` は `AppliedTypeTree` へとパースされる。**注意**: `TypTree` は綴り間違いではないし、概念的に `TypeTree` とは異なるものだ。(例えば型推論などによって) コンパイラが `Type` を構築する場合にプログラムの AST に統合できるように `TypeTree` にラッピングされる。 +3. **`SymTree` のサブクラス**は定義を導入または参照する。新しい定義の導入の具体例としてはクラスやトレイトの定義を表す `ClassDef` や、フィールドやパラメータ定義を表す `ValDef` が挙げられる。既存の定義に対する参照の例としてはローカル変数やメソッドなど現行のスコープ内にある既存の定義を参照する `Ident` を挙げることができる。 + +上記のカテゴリー以外の構文木を目にすることがあるとすれば、それは典型的には合成的なものか短命な構築物だ。 +例えば、各マッチケースをラッピングする `CaseDef` は項でも型でもなく、シンボルも持たない。 + +### 構文木をインスペクトする + +Scala リフレクションは、ユニバース経由で構文木を視覚化する方法をいくつか提供する。渡された構文木があるとき、 + +- `show` もしくは `toString` メソッドを使って構文木が表す擬似 Scala コードを表示することができる。 +- `showRaw` メソッドを使ってタイプチェッカが用いる生の構文木の内部構造を見ることができる。 + +具体例を使って説明しよう: + + scala> import scala.reflect.runtime.universe._ + import scala.reflect.runtime.universe._ + + scala> val tree = Apply(Select(Ident(TermName("x")), TermName("$plus")), List(Literal(Constant(2)))) + tree: scala.reflect.runtime.universe.Apply = x.$plus(2) + +`show` メソッド (もしくは同等の `toString`) を使ってこの構文木が何を表しているかを見てみよう。 + + scala> show(tree) + res0: String = x.$plus(2) + +見てのとおり、`tree` は `2` を項 `x` に加算する。 + +逆の方向に行くこともできる。ある Scala の式が与えられたとき、そこから構文木を取得した後で +`showRaw` メソッドを用いてコンパイラやタイプチェッカが使っている生の構文木の内部構造を見ることができる。 +例えば、以下の式があるとする: + + scala> import scala.reflect.runtime.universe._ + import scala.reflect.runtime.universe._ + + scala> val expr = reify { class Flower { def name = "Rose" } } + expr: scala.reflect.runtime.universe.Expr[Unit] = ... + +ここで、`reify` は Scala 式を受け取り `Tree` と `TypeTag` をラッピングする `Expr` を返す。 +(`Expr` の詳細に関してはこのガイドの[式]({{ site.baseurl }}/ja/overviews/reflection/names-exprs-scopes-more.html)の節を参照) +`expr` が保持する構文木は以下のように取得できる: + + scala> val tree = expr.tree + tree: scala.reflect.runtime.universe.Tree = + { + class Flower extends AnyRef { + def () = { + super.(); + () + }; + def name = "Rose" + }; + () + } + +生の構文木の内部構造をインスペクトするには以下のように行う: + + scala> showRaw(tree) + res1: String = Block(List(ClassDef(Modifiers(), TypeName("Flower"), List(), Template(List(Ident(TypeName("AnyRef"))), emptyValDef, List(DefDef(Modifiers(), nme.CONSTRUCTOR, List(), List(List()), TypeTree(), Block(List(Apply(Select(Super(This(tpnme.EMPTY), tpnme.EMPTY), nme.CONSTRUCTOR), List())), Literal(Constant(())))), DefDef(Modifiers(), TermName("name"), List(), List(), TypeTree(), Literal(Constant("Rose"))))))), Literal(Constant(()))) + +### 構文木の走査 + +構文木の内部構造が分かった所で、よくある次のステップは情報を抽出することだ。 +これは構文木を**走査**することで行われ、以下の 2通りの方法がある: + +
        +
      • パターンマッチングを用いた走査
      • +
      • Traverser のサブクラスを用いた走査
      • +
      + +#### パターンマッチングを用いた走査 + +パターンマッチングを用いた走査は最も簡単で一般的な構文木の走査方法だ。 +典型的にはある構文木の単一のノードの状態を知りたい場合にパターンマッチングを用いた走査を行う。 +例えば、以下の構文木に1つだけある `Apply` ノードから関数と引数を取得したいとする。 + + scala> import scala.reflect.runtime.universe._ + import scala.reflect.runtime.universe._ + + scala> val tree = Apply(Select(Ident(TermName("x")), TermName("$plus")), List(Literal(Constant(2)))) + tree: scala.reflect.runtime.universe.Apply = x.$plus(2) + +`tree` に対してマッチをかけてやるだけでよく、`Apply` ケースの場合には `Apply` の関数と引数を返す: + + scala> val (fun, arg) = tree match { + | case Apply(fn, a :: Nil) => (fn, a) + | } + fun: scala.reflect.runtime.universe.Tree = x.$plus + arg: scala.reflect.runtime.universe.Tree = 2 + +パターンマッチを左辺項に移すことで上記と同じことをより簡潔に実現できる: + + scala> val Apply(fun, arg :: Nil) = tree + fun: scala.reflect.runtime.universe.Tree = x.$plus + arg: scala.reflect.runtime.universe.Tree = 2 + +ノードは他のノード内に任意の深さで入れ子になることができるため、`Tree` +は普通かなり複雑となることに注意してほしい。これを示す簡単な例として、上記の構文木に +2つ目の `Apply` を加えて既にある和に `3` を加算する: + + scala> val tree = Apply(Select(Apply(Select(Ident(TermName("x")), TermName("$plus")), List(Literal(Constant(2)))), TermName("$plus")), List(Literal(Constant(3)))) + tree: scala.reflect.runtime.universe.Apply = x.$plus(2).$plus(3) + +これに上記と同じパターンマッチを適用すると外側の `Apply` +ノードが得られ、それは上で見た `x.$plus(2)` を表す構文木を関数部分として格納する: + + scala> val Apply(fun, arg :: Nil) = tree + fun: scala.reflect.runtime.universe.Tree = x.$plus(2).$plus + arg: scala.reflect.runtime.universe.Tree = 3 + + scala> showRaw(fun) + res3: String = Select(Apply(Select(Ident(TermName("x")), TermName("$plus")), List(Literal(Constant(2)))), TermName("$plus")) + +特定のノードで止まることなく構文木全体を走査したり、特定の型のノードを収集してインスペクトするなどより複雑なタスクを行うためには +`Traverser` を用いた走査の方が適しているかもしれない。 + +#### `Traverser` のサブクラスを用いた走査 + +初めから終わりまで構文木全体を走査する必要がある場合は、パターンマッチ中に現れうる全ての型に対する処理をする必要があるためパターンマッチングを用いた走査は適さない。 +そのため、そのような場合は `Traverser` クラスを用いる。 + +`Traevrser` は幅優先探索を用いて渡された構文木の全てのノードを訪れることを保証する。 + +`Traverser` を使うには、`Traverser` を継承して `traverse` メソッドをオーバーライドする。 +こうすることで必要なケースだけを処理するカスタムロジックを提供する。 +例えば `x.$plus(2).$plus(3)` の構文木があるとき、全ての `Apply` ノードを収集したいとする: + + scala> import scala.reflect.runtime.universe._ + import scala.reflect.runtime.universe._ + + scala> val tree = Apply(Select(Apply(Select(Ident(TermName("x")), TermName("$plus")), List(Literal(Constant(2)))), TermName("$plus")), List(Literal(Constant(3)))) + tree: scala.reflect.runtime.universe.Apply = x.$plus(2).$plus(3) + + scala> object traverser extends Traverser { + | var applies = List[Apply]() + | override def traverse(tree: Tree): Unit = tree match { + | case app @ Apply(fun, args) => + | applies = app :: applies + | super.traverse(fun) + | super.traverseTrees(args) + | case _ => super.traverse(tree) + | } + | } + defined module traverser + +上のコードは渡された構文木のうち `Apply` ノードだけを探してリストを構築している。 + +これはスーパークラス `Traverser` で既に幅優先探索として実装されている +`traverse` メソッドをサブクラス `traverser` のオーバーライドされた +`traverse` メソッドが特別なケースを**追加**するという形で実現されている。 +この特別なケースは `Apply(fun, args)` というパターンにマッチするノードのみに効用がある。 +(`fun` は `Tree` で表される関数、`args` は `Tree` のリストで表される引数のリストとなる) + +ある構文木がこのパターンにマッチすると (つまり、`Apply` ノードがあるとき)、 +`List[Apply]` である `applies` に追加して、走査を続行する。 + +マッチした場合の処理で `Apply` にラッピングされた関数 `fun` に対して `super.traverse` +そして引数のリスト `args` に対しては `super.traverseTrees` +(`super.traverse` とほぼ同じものだが、単一の `Tree` の代わりに `List[Tree]` を受け取る) +を呼び出していることに注意してほしい。 +両方の呼び出しとも目的は簡単で、`fun` の中にも `Apply` パターンがあるか分からないため部分木に対してもデフォルトの +`Traverser` の `traverse` メソッドが確かに呼ばれるようにしている。 +スーパークラスである `Traverser` は全ての入れ子になっている部分木に対して `this.traverse` +を呼び出すため、`Apply` パターンを含む部分木もカスタムの `traverse` メソッドを呼び出すことが保証されている。 + +`traverse` を開始して、その結果の `Apply` の `List` を表示するには以下のように行う: + + scala> traverser.traverse(tree) + + scala> traverser.applies + res0: List[scala.reflect.runtime.universe.Apply] = List(x.$plus(2), x.$plus(2).$plus(3)) + +### 構文木の構築 + +実行時リフレクションを行う際に、構文木を手動で構築する必要は無い。 +しかし、ツールボックスを用いて実行時コンパイルする場合やマクロを用いてコンパイル時リフレクションを行う場合はプログラムを表現する媒体として構文木が使われる。 +そのような場合、構文木を構築する 3通りの方法がある: + +1. `reify` メソッドを用いる (可能な限りこれを使うことを推奨する) +2. ツールボックスの `parse` メソッドを用いる +3. 手動で構築する (非推奨) + +#### `reify` を用いた構文木の構築 + +`reify` メソッドは Scala 式を引数として受け取り、その引数を `Tree` として表現したものを結果として返す。 + +Scala リフレクションでは、`reify` メソッドを用いた構文木の構築が推奨される方法だ。その理由を具体例を用いて説明しよう: + + scala> import scala.reflect.runtime.universe._ + import scala.reflect.runtime.universe._ + + scala> { val tree = reify(println(2)).tree; showRaw(tree) } + res0: String = Apply(Select(Select(This(TypeName("scala")), TermName("Predef")), TermName("println")), List(Literal(Constant(2)))) + +ここで、単に `println(2)` という呼び出しを `reify` している。 +つまり、`println(2)` という式をそれに対応する構文木の表現に変換している。そして、生の構文木の内部構造を出力している。 +`println` メソッドが `scala.Predef.println` に変換されたことに注目してほしい。 +このような変換は `reify` の結果がどこで用いられても意味が変わらないことを保証する。 +例えば、この `println(2)` というコードが独自の `println` を定義するブロックに挿入されたとしてもこのコードの振る舞いには影響しない。 + +このような構文木の構築は、識別子のバインディングを保持するため**健全** (hygenic) であるといわれる。 + +##### 構文木のスプライシング + +`reify` を使うことで複数の小さい構文木から 1つの構文木へと合成することもできる。これは +`Expr.splice` (スプライス、「継ぎ足す」という意味) を用いて行われる。 + +**注意**: `Expr` は `reify` の戻り値の型だ。**型付けされた** (typed) 構文木、`TypeTag` +そして `splice` などのレイフィケーションに関連するいくつかのメソッドを含む簡単なラッパーだと思ってもらえばいい。 +`Expr` に関する詳細は[このガイドの関連項目]({{ site.baseurl}}/ja/overviews/reflection/annotations-names-scopes.html)を参照。 + +例えば、`splice` を用いて `println(2)` を表す構文木を構築してみよう: + + scala> val x = reify(2) + x: scala.reflect.runtime.universe.Expr[Int(2)] = Expr[Int(2)](2) + + scala> reify(println(x.splice)) + res1: scala.reflect.runtime.universe.Expr[Unit] = Expr[Unit](scala.this.Predef.println(2)) + +ここで `2` と `println` をそれぞれ別に `reify` して、一方を他方の中に `splice` している。 + +しかし、`reify` の引数は妥当で型付け可能な Scala のコードであることが要求されることに注意してほしい。 +`println` の引数の代わりに、`println` そのものを抽象化しようとした場合は失敗することを以下に示す: + + scala> val fn = reify(println) + fn: scala.reflect.runtime.universe.Expr[Unit] = Expr[Unit](scala.this.Predef.println()) + + scala> reify(fn.splice(2)) + :12: error: Unit does not take parameters + reify(fn.splice(2)) + ^ + +見てのとおり、呼び出された関数の名前だけを捕捉したかったわけだが、コンパイラは引数無しの `println` という呼び出しをレイファイしたかったのだと決めてかかっている。 + +このようなユースケースは現在 `reify` を用いては表現することはできない。 + +#### ツールボックスの `parse` を用いた構文木の構築 + +**ツールボックス** (`Toolbox`) を使って構文木の型検査、コンパイル、および実行を行うことができる。 +ツールボックスはまた、文字列を構文木へとパースすることができる。 + +**注意**: ツールボックスの仕様は `scala-compiler.jar` にクラスパスが通っていることを必要とする。 + +`parse` メソッドを使った場合に、前述の `println` の例がどうなるかみてみよう: + + scala> import scala.reflect.runtime.universe._ + import scala.reflect.runtime.universe._ + + scala> import scala.tools.reflect.ToolBox + import scala.tools.reflect.ToolBox + + scala> val tb = runtimeMirror(getClass.getClassLoader).mkToolBox() + tb: scala.tools.reflect.ToolBox[scala.reflect.runtime.universe.type] = scala.tools.reflect.ToolBoxFactory$ToolBoxImpl@7bc979dd + + scala> showRaw(tb.parse("println(2)")) + res2: String = Apply(Ident(TermName("println")), List(Literal(Constant(2)))) + +`reify` と違って、ツールボックスは型付けの要求を必要としないことに注目してほしい。 +この柔軟性の引き換えに堅牢性が犠牲になっている。どういう事かと言うと、`reify` +と違ってこの `parse` は `println` が標準の `println` メソッドにバインドされていることが反映されていない。 + +**注意**: マクロを使っている場合は、`ToolBox.parse` を使うべきではない。マクロコンテキストに既に +`parse` メソッドが組み込まれているからだ。具体例を使って説明しよう: + + scala> import scala.language.experimental.macros + import scala.language.experimental.macros + + scala> def impl(c: scala.reflect.macros.Context) = c.Expr[Unit](c.parse("println(2)")) + impl: (c: scala.reflect.macros.Context)c.Expr[Unit] + + scala> def test = macro impl + test: Unit + + scala> test + 2 + +##### ツールボックスを用いた型検査 + +前に少し触れたが、ツールボックス (`ToolBox`) は文字列から構文木を構築する以外にも使い道があって、構文木の型検査、コンパイル、および実行を行うことができる。 + +プログラムの大まかな構造を保持する他に、構文木はプログラムの意味論に関する重要な情報を +`symbol` (定義を導入または参照する構文木に割り当てられたシンボル) や +`tpe` (構文木の型) という形で保持する。デフォルトでは、これらのフィールドは空だが、型検査をすることで充足される。 + +実行時リフレクションのフレームワークを利用する場合、型検査は `ToolBox.typeCheck` によって実装される。 +コンパイル時にマクロを利用する場合は `Context.typeCheck` メソッドを使う。 + + scala> import scala.reflect.runtime.universe._ + import scala.reflect.runtime.universe._ + + scala> val tree = reify { "test".length }.tree + tree: scala.reflect.runtime.universe.Tree = "test".length() + + scala> import scala.tools.reflect.ToolBox + import scala.tools.reflect.ToolBox + + scala> val tb = runtimeMirror(getClass.getClassLoader).mkToolBox() + tb: scala.tools.reflect.ToolBox[scala.reflect.runtime.universe.type] = ... + + scala> val ttree = tb.typeCheck(tree) + ttree: tb.u.Tree = "test".length() + + scala> ttree.tpe + res5: tb.u.Type = Int + + scala> ttree.symbol + res6: tb.u.Symbol = method length + +上の例では、`"test".length` という呼び出しを表現する構文木を構築して、`ToolBox` `tb` +の `typeCheck` メソッドを用いて構文木を型検査している。 +見てのとおり、`ttree` は正しい型 `Int` を取得して、`Symbol` も正しく設定されている。 + +#### 手動の構文木の構築 + +もし全てが失敗した場合は、手動で構文木を構築することもできる。これは最も低レベルな構文木を構築する方法で、他の方法がうまくいかなかった場合のみ挑むべき方法だ。 +`parse` に比べてより柔軟な方法を提供するが、その柔軟性は過度の冗長さと脆弱さによって実現されている。 + +`println(2)` を使った例題を手動で構築すると、こうなる: + + scala> Apply(Ident(TermName("println")), List(Literal(Constant(2)))) + res0: scala.reflect.runtime.universe.Apply = println(2) + +このテクニックの典型的なユースケースは単独では意味を成さない動的に構築された部分木を組み合わせて構文木を作る必要がある場合だ。 +そのような場合、引数が型付けられていることを必要とする `reify` はおそらく不適切だろう。 +構文木は個々の部分木では Scala ソースとして表現することができない式以下のレベルから組み立てられることがよくあるため、`parse` +でもうまくいかないだろう。 diff --git a/_ja/overviews/reflection/thread-safety.md b/_ja/overviews/reflection/thread-safety.md new file mode 100644 index 0000000000..d67254ae7a --- /dev/null +++ b/_ja/overviews/reflection/thread-safety.md @@ -0,0 +1,62 @@ +--- +layout: multipage-overview + +discourse: false + +partof: reflection +overview-name: Reflection + +num: 6 + +language: ja +title: スレッドセーフティ +--- + +EXPERIMENTAL + +残念ながら Scala 2.10.0 でリリースされた現行の状態ではリフレクションはスレッドセーフではない。 +[SI-6240](https://issues.scala-lang.org/browse/SI-6240) が報告されているので、それを使って進捗を追跡したり、技術的な詳細を照会することができるが、ここに現状で分かっていることをまとめてみたい。 + +

      NEWThread safety issues have been fixed in Scala 2.11.0-RC1, but we are going to keep this document available for now, since the problem still remains in the Scala 2.10.x series, and we currently don't have concrete plans on when the fix is going to be backported.

      + +現在の所、リフレクション関連では 2通りの競合状態があることが分かっている。第一はリフレクションの初期化 +(`scala.reflect.runtime.universe` が最初にアクセルされるときに呼ばれるコード) +は複数のスレッドから安全に呼び出すことができない。 +第二に、シンボルの初期化 +(シンボルのフラグまたは型シグネチャが最初にアクセスされたときに呼ばれるコード) +も安全ではない。以下が典型的な症例だ: + + java.lang.NullPointerException: + at s.r.i.Types$TypeRef.computeHashCode(Types.scala:2332) + at s.r.i.Types$UniqueType.(Types.scala:1274) + at s.r.i.Types$TypeRef.(Types.scala:2315) + at s.r.i.Types$NoArgsTypeRef.(Types.scala:2107) + at s.r.i.Types$ModuleTypeRef.(Types.scala:2078) + at s.r.i.Types$PackageTypeRef.(Types.scala:2095) + at s.r.i.Types$TypeRef$.apply(Types.scala:2516) + at s.r.i.Types$class.typeRef(Types.scala:3577) + at s.r.i.SymbolTable.typeRef(SymbolTable.scala:13) + at s.r.i.Symbols$TypeSymbol.newTypeRef(Symbols.scala:2754) + +実行時リフレクション (`scala.reflect.runtime.universe` から公開されるもの) +に比べてコンパイル時リフレクション (`scala.reflect.macros.Context` によってマクロに公開されるもの) +の方がこの問題の影響を受けづらいことはせめてもの救いだ。 +第一の理由は、マクロが実行される段階においてはコンパイル時リフレクションのユニバースは既に初期化済みであるため、競合状態の最初の状態は無くなることだ。 +第二の理由はこれまでにコンパイラそのものがスレッドセーフであったことが無いため、並列実行を行なっているツールが無いことだ。 +しかし、複数のスレッドを作成するマクロを作っている場合は気をつけるべきだろう。 + +一転して、実行時リフレクションの話は暗くなる。リフレクションの初期化は +`scala.reflect.runtime.universe` が初期化されるときに呼び出され、これは間接的に起こりうる。 +中でも顕著な例は context bound の `TypeTag` がついたメソッドを呼び出すと問題が起こりえることだ。 +これは、そのようなメソッドを呼び出すと Scala は普通は型タグを自動生成する必要があり、そのために型を生成する必要があり、そのためにはリフレクションのユニバースの初期化が必要だからだ。この結果、特殊な対策を取らない限りテストなどから +`TypeTag` を使ったメソッドを安全に呼び出すことができないということが導き出される。 +これは sbt など多くのツールがテストを並列実行するからだ。 + +まとめ: + +
        +
      • マクロを書いているならば、明示的にスレッドを使わない限り大丈夫だ。
      • +
      • 実行時リフレクションとスレッドやアクターを混ぜると危険。
      • +
      • TypeTag の context bound を使ったメソッドを複数のスレッドから呼び出すと非決定的な結果になる可能性がある。
      • +
      • この問題の進捗を知りたければ SI-6240 を参照する。
      • +
      diff --git a/_ja/overviews/reflection/typetags-manifests.md b/_ja/overviews/reflection/typetags-manifests.md new file mode 100644 index 0000000000..c4eea9aee9 --- /dev/null +++ b/_ja/overviews/reflection/typetags-manifests.md @@ -0,0 +1,155 @@ +--- +layout: multipage-overview + +discourse: false + +partof: reflection +overview-name: Reflection + +num: 5 + +language: ja +title: 型タグとマニフェスト +--- + +他の JVM言語同様に、Scala の型はコンパイル時に**消去** (erase) される。 +これは、何らかのインスタンスのランタイム型をインスペクトしてもコンパイル時に +Scala コンパイラが持つ型情報を全ては入手できない可能性があることを意味する。 + +マニフェスト (`scala.reflect.Manifest`) 同様に、**型タグ** (`TypeTag`) はコンパイル時に入手可能な全ての型情報を実行時に持ち込むオブジェクトだと考えることができる。 +例えば、`TypeTag[T]` はコンパイル時の型 `T` のランタイム型形式をカプセル化する。 +しかし `TypeTag` は、2.10 以前の `Manifest` という概念に比べより豊かで、かつ +Scala リフレクションに統合された代替であることに注意してほしい。 + +3通りの型タグがある: + +1. `scala.reflect.api.TypeTags#TypeTag`。Scala 型の完全な型記述子。例えば、`TypeTag[List[String]]` は型 `scala.List[String]` に関する全ての型情報を持つ。 + +2. `scala.reflect.ClassTag`。Scala 型の部分的な型記述子。例えば、`ClassTag[List[String]]` は消去されたクラス型の情報のみ (この場合、`scala.collection.immutable.List`) を保持する。`ClassTag` は型のランタイムクラスへのアクセスのみを提供し、`scala.reflect.ClassManifest` に相当する。 + +3. `scala.reflect.api.TypeTags#WeakTypeTag`。抽象型の型記述子 (以下の節での説明を参照)。 + +## 型タグの取得 + +マニフェスト同様、型タグは常にコンパイラによって生成され、以下の 3通りの方法で取得できる。 + +### `typeTag`、`classTag`、`weakTypeTag` メソッドを使う + +`Universe` が公開している `typeTag` を使うことで特定の型の `TypeTag` を直接取得することができる。 + +例えば、`Int` を表す `TypeTag` を得るには以下のようにする: + + import scala.reflect.runtime.universe._ + val tt = typeTag[Int] + +同様に `String` を表す `ClassTag` を得るには以下のように行う: + + import scala.reflect._ + val ct = classTag[String] + +これらのメソッドはそれぞれ型引数 `T` の `TypeTag[T]` か `ClassTag[T]` を構築する。 + +### `TypeTag[T]`、`ClassTag[T]`、もしくは `WeakTypeTag[T]` 型の暗黙のパラメータを使う + +`Manifest` 同様にコンパイラに `TypeTag` を生成するように申請することができる。 +これは、`TypeTag[T]` 型の暗黙のパラメータを宣言するだけで行われる。 +もしコンパイラが implicit の検索時にマッチする implicit の値を探すことができなければ自動的に +`TypeTag[T]` を生成する。 + +**注意**: 典型的に、これはメソッドかクラスのみに暗黙のパラメータを使うことで達成される。 + +例えば、任意のオブジェクトを受け取るメソッドを書いて、`TypeTag` +を使ってそのオブジェクトの型引数を表示することができる: + + import scala.reflect.runtime.universe._ + + def paramInfo[T](x: T)(implicit tag: TypeTag[T]): Unit = { + val targs = tag.tpe match { case TypeRef(_, _, args) => args } + println(s"type of $x has type arguments $targs") + } + +ここで `T` についてパラメータ化された多相メソッド `paramInfo` を暗黙のパラメータ +`(implicit tag: TypeTag[T])` と共に定義する。これで、`TypeTag` の `tpe` +メソッドを使って `tag` が表す (`Type` 型の) 型に直接アクセスすることができる。 + +実際に `paramInfo` メソッドを使ってみよう: + + scala> paramInfo(42) + type of 42 has type arguments List() + + scala> paramInfo(List(1, 2)) + type of List(1, 2) has type arguments List(Int) + +### 型パラメータの context bound を使う + +型パラメータに context bound を付けることで上と同じことをもう少し簡潔に書ける。 +独立した暗黙のパラメータを定義する代わりに以下のように型パラメータのリストに +`TypeTag` を付けることができる: + + def myMethod[T: TypeTag] = ... + +context bound `[T: TypeTag]` からコンパイラは `TypeTag[T]` +型の暗黙のパラメータを生成して前節の暗黙のパラメータを使った用例のようにメソッドを書き換える。 + +上の具体例を context bound を使って書きなおしてみる: + + import scala.reflect.runtime.universe._ + + def paramInfo[T: TypeTag](x: T): Unit = { + val targs = typeOf[T] match { case TypeRef(_, _, args) => args } + println(s"type of $x has type arguments $targs") + } + + scala> paramInfo(42) + type of 42 has type arguments List() + + scala> paramInfo(List(1, 2)) + type of List(1, 2) has type arguments List(Int) + +## WeakTypeTags + +`WeakTypeTag[T]` は `TypeTag[T]` を一般化する。普通の +`TypeTag` と違ってそれが表す型の構成要素は型パラメータか抽象型への参照であることもできる。 +しかし、`WeakTypeTag[T]` は可能な限り具象的であろうとするため、 +参照された型引数か抽象型の型タグが入手可能ならばそれを使って +`WeakTypeTag[T]` に具象型を埋め込む。 + +先ほどからの具体例を続けよう: + + def weakParamInfo[T](x: T)(implicit tag: WeakTypeTag[T]): Unit = { + val targs = tag.tpe match { case TypeRef(_, _, args) => args } + println(s"type of $x has type arguments $targs") + } + + scala> def foo[T] = weakParamInfo(List[T]()) + foo: [T]=> Unit + + scala> foo[Int] + type of List() has type arguments List(T) + +## 型タグとマニフェストの比較 + +型タグ (`TypeTag`) は 2.10 以前のマニフェスト (`scala.reflect.Manifest`) の概念に相当する。 +`scala.reflect.ClassTag` は `scala.reflect.ClassManifest` +に相当するもので、 +`scala.reflect.api.TypeTags#TypeTag` は `scala.reflect.Manifest` +に相当するものだが、他の 2.10 以前のマニフェスト型には直接対応する 2.10 のタグ型は存在しない。 + +
        +
      • scala.reflect.OptManifest はサポートされない。 +これはタグが任意の型をレイファイすることができるため、必要無くなったからだ。
      • + +
      • scala.reflect.AnyValManifest に相当するものは無い。 +ある型がプリミティブ値クラスであるかを調べるには、型タグを (基底タグのコンパニオンオブジェクトで定義されている) 基底タグと比較することができる。もしくは、 +<tag>.tpe.typeSymbol.isPrimitiveValueClass を使うこともできる。
      • + +
      • マニフェストのコンパニオンオブジェクトに定義されるファクトリ・メソッドに相当するものは無い。 +代わりに、(クラスの場合は) Java か (型の場合は) Scala によって提供されるリフレクション API を使って対応する型を生成することができる。
      • + +
      • いくつかのマニフェスト演算 (具体的には、<:<>:>、と typeArguments) はサポートされない。 +代わりに、(クラスの場合は) Java か (型の場合は) Scala によって提供されるリフレクション API を使うことができる。
      • +
      + +`scala.reflect.ClassManifests` は Scala 2.10 から廃止予定となり、将来のマイナーリリースにおいて +`scala.reflect.Manifest` も廃止予定として `TypeTag` と `ClassTag` に道を開けることを予定している。 +そのため、マニフェストを使ったコードは型タグを使うものに移行することを推奨する。 diff --git a/_ko/tour/abstract-types.md b/_ko/tour/abstract-types.md new file mode 100644 index 0000000000..cd68969e81 --- /dev/null +++ b/_ko/tour/abstract-types.md @@ -0,0 +1,75 @@ +--- +layout: tour +title: 추상 타입 + +discourse: false + +partof: scala-tour + +num: 22 + +language: ko + +next-page: compound-types +previous-page: inner-classes +--- + +스칼라에선 값(생성자 파라미터)과 타입(클래스가 [제네릭](generic-classes.html)일 경우)으로 클래스가 매개변수화된다. 규칙성을 지키기 위해, 값이 객체 멤버가 될 수 있을 뿐만 아니라 값의 타입 역시 객체의 멤버가 된다. 또한 이런 두 형태의 멤버 모두 다 구체화되거나 추상화될 수 있다. + +이 예제에선 [클래스](traits.html) `Buffer`의 멤버로써 완전히 확정되지 않은 값과 추상 타입을 정의하고 있다. + + trait Buffer { + type T + val element: T + } + +*추상 타입*은 본성이 완전히 식별되지 않은 타입이다. 위의 예제에선 클래스 `Buffer`의 각 객체가 T라는 타입 멤버를 갖고 있다는 점만 알 수 있으며, 클래스 `Buffer`의 정의는 멤버 타입 `T`에 해당하는 특정 타입이 무엇이지 밝히고 있지 않다. 값 정의와 같이 타입 정의도 서브클래스에서 재정의(override) 할 수 있다. 이것은 타입 경계(추상 타입에 해당하는 예시를 나타내는)를 좀더 엄격하게 함으로써 추상 타입에 대해 좀더 많은 정보를 얻을수 있게 해준다. + +다음 프로그램에선 `T` 타입이 새로운 추상 타입 `U`로 표현된 `Seq[U]`의 서브타입이어야 함을 나타내서, 버퍼에 시퀀스 만을 저장하는 클래스 `SeqBuffer`를 만들었다. + + abstract class SeqBuffer extends Buffer { + type U + type T <: Seq[U] + def length = element.length + } + +추상 타입 멤버를 포함한 트레잇이나 [클래스](classes.html)는 종종 익명 클래스 인스턴스화와 함께 사용된다. 이를 알아보기 위해 정수의 리스트를 참조하는 시퀀스 버퍼를 다루는 프로그램을 살펴보자. + + 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) + } + +메소드 `newIntSeqBuf`의 반환 타입은 트레잇 `Buffer`의 특수화를 따르며, 타입 `U`가 `Int`와 같아진다. 메소드 `newIntSeqBuf` 내부의 익명 클래스 인스턴스화에서도 비슷한 타입 별칭이 있다. 여기선 `T` 타입이 `List[Int]`를 가리키는 `IntSeqBuf`의 새로운 인스턴스를 생성한다. + +추상 타입 멤버를 클래스의 타입 파라미터로 하거나 클래스의 타입 파라미터로 추상 타입 멤버로를사용할 수 있음에 주목하자. 다음은 타입 파라미터만을 사용한, 앞서 살펴본 코드의 새로운 버전이다. + + 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) + } + +여기선 [가변성 어노테이션](variances.html)을 사용해야만 한다는 점에 유의하자. 이를 사용하지 않으면 메소드 `newIntSeqBuf`에서 반환되는 객체의 특정 시퀀스 구현 타입을 감출 수 없게 된다. 뿐만 아니라 추상 타입을 타입 파라미터로 대체할 수 없는 경우도 있다. + +윤창석, 이한욱 옮김, 고광현 수정 diff --git a/_ko/tour/annotations.md b/_ko/tour/annotations.md new file mode 100644 index 0000000000..a846ddf373 --- /dev/null +++ b/_ko/tour/annotations.md @@ -0,0 +1,128 @@ +--- +layout: tour +title: 어노테이션 + +discourse: false + +partof: scala-tour + +num: 31 +language: ko + +next-page: default-parameter-values +previous-page: automatic-closures +--- + +어노테이션은 메타 정보와 정의 내용을 연결해준다. + +간단한 어노테이션 절은 `@C`나 `@C(a1, .., an)`와 같은 형태다. 여기서 `C`는 `C` 클래스의 생성자이며, `scala.Annotation`에 맞는 클래스여야만 한다. `a1, .., an`으로 주어지는 모든 생성자의 인수는 반드시 상수 표현식이여야 한다(예, 숫자 리터럴, 문자열, 클래스 리터럴, 자바 열거형, 그리고 이들의 1차원 배열). + +어노테이션 절은 첫 번째 정의나, 그 다음에 이어지는 선언에 적용된다. 정의와 선언에는 하나 이상의 어노테이션 절이 붙을 수 있다. 이런 절이 표현되는 순서는 영향을 미치지 않는다. + +어노테이션 절의 의미는 _구현 종속적_ 이다. 자바 플랫폼에선 다음의 스칼라 어노테이션이 표준에 해당하는 의미를 갖고 있다. + +| Scala | Java | +| ------ | ------ | +| [`scala.SerialVersionUID`](https://www.scala-lang.org/api/current/scala/SerialVersionUID.html) | [`serialVersionUID`](http://java.sun.com/j2se/1.5.0/docs/api/java/io/Serializable.html#navbar_bottom) (필드) | +| [`scala.deprecated`](https://www.scala-lang.org/api/current/scala/deprecated.html) | [`java.lang.Deprecated`](http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Deprecated.html) | +| [`scala.inline`](https://www.scala-lang.org/api/current/scala/inline.html) (2.6.0 부터) | 해당 없음 | +| [`scala.native`](https://www.scala-lang.org/api/current/scala/native.html) (2.6.0 부터) | [`native`](http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html) (키워드) | +| [`scala.remote`](https://www.scala-lang.org/api/current/scala/remote.html) | [`java.rmi.Remote`](http://java.sun.com/j2se/1.5.0/docs/api/java/rmi/Remote.html) | +| [`scala.throws`](https://www.scala-lang.org/api/current/scala/throws.html) | [`throws`](http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html) (키워드) | +| [`scala.transient`](https://www.scala-lang.org/api/current/scala/transient.html) | [`transient`](http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html) (키워드) | +| [`scala.unchecked`](https://www.scala-lang.org/api/current/scala/unchecked.html) (2.4.0 부터) | 해당 없음 | +| [`scala.volatile`](https://www.scala-lang.org/api/current/scala/volatile.html) | [`volatile`](http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html) (키워드) | +| [`scala.beans.BeanProperty`](https://www.scala-lang.org/api/current/scala/beans/BeanProperty.html) | [`디자인 패턴`](http://docs.oracle.com/javase/tutorial/javabeans/writing/properties.html) | + +다음 예제에선 자바의 메인 프로그램에서 던지는 예외를 잡기 위해, `read` 메소드에 `throws` 어노테이션을 추가했다. + +> 자바 컴파일러는 메소드나 생성자를 실행할 때 어떤 확인 예외가 발생할 수 있는지 분석해, 프로그램이 [확인이 필요한 예외](http://docs.oracle.com/javase/specs/jls/se5.0/html/exceptions.html)를 처리할 핸들러를 포함하고 있는지 검사한다. 메소드나 생성자의 **throws** 절에선 발생할 가능성이 있는 확인 예외마다, 해당 예외의 클래스나 해당 예외 클래스의 상위 클래스를 반드시 명시해야 한다. +> 스칼라는 확인 예외가 없기 때문에 스칼라 메소드는 스칼라 메소드가 던지는 예외를 자바 코드가 잡을 수 있도록 반드시 하나 이상의 `throws` 어노테이션을 붙여야 한다. + + package examples + import java.io._ + class Reader(fname: String) { + private val in = new BufferedReader(new FileReader(fname)) + @throws(classOf[IOException]) + def read() = in.read() + } + +다음의 자바 프로그램은 `main` 메소드의 첫 번째 인수로 전달된 이름의 파일을 열어 내용을 출력한다. + + package test; + import examples.Reader; // Scala class !! + 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()); + } + } + } + +Reader 클래스의 `throws` 어노테이션을 주석으로 처리하면 자바 메인 프로그램을 컴파일 할 때 다음과 같은 오류 메시지가 나타난다. + + Main.java:11: exception java.io.IOException is never thrown in body of + corresponding try statement + } catch (java.io.IOException e) { + ^ + 1 error + +### 자바 어노테이션 ### + +**주의:** 자바 어노테이션과 함께 `-target:jvm-1.5` 옵션을 사용해야 한다. + +자바 1.5에선 [어노테이션](http://java.sun.com/j2se/1.5.0/docs/guide/language/annotations.html)이란 형태로 사용자 지정 메타데이터가 추가됐다. 어노테이션의 핵심 기능은 키와 값의 쌍을 지정해 자신의 항목을 초기화하는 데 기반하고 있다. 예를 들어 클래스의 출처를 추적하고 싶다면 다음과 같이 정의할 수 있다. + + @interface Source { + public String URL(); + public String mail(); + } + +그리고 이를 다음과 같이 적용한다. + + @Source(URL = "http://coders.com/", + mail = "support@coders.com") + public class MyClass extends HisClass ... + +스칼라에선 어노테이션을 적용하는 방식은 생성자 호출과 비슷한 모습을 갖고 있으며 자바 어노테이션을 인스턴스화 하기 위해선 이름을 지정한 인수를 사용해야 한다. + + @Source(URL = "http://coders.com/", + mail = "support@coders.com") + class MyScalaClass ... + +어노테이션에 단 하나의 항목(기본 값이 없는)만 있다면 이 구문은 상당히 장황하게 느껴지기 때문에, 자바에선 그 이름이 `value`로 지정됐다면 편의를 위해 생성자와 유사한 구문을 사용할 수도 있다. + + @interface SourceURL { + public String value(); + public String mail() default ""; + } + +그리고 이를 다음과 같이 적용한다. + + @SourceURL("http://coders.com/") + public class MyClass extends HisClass ... + +이 경우엔 스칼라도 같은 기능을 제공한다. + + @SourceURL("http://coders.com/") + class MyScalaClass ... + +`mail` 항목은 기본 값과 함께 설정됐기 때문에 이 항목에 반드시 값을 명시적으로 할당할 필요는 없다. 하지만 만약 해야만 한다면, 자바의 두 스타일을 함께 섞어서 사용할 순 없다. + + @SourceURL(value = "http://coders.com/", + mail = "support@coders.com") + public class MyClass extends HisClass ... + +스칼라에선 이를 사용하는 더 유연한 방법을 제공한다. + + @SourceURL("http://coders.com/", + mail = "support@coders.com") + class MyScalaClass ... + +윤창석, 이한욱 옮김 diff --git a/_ko/tour/anonymous-function-syntax.md b/_ko/tour/anonymous-function-syntax.md new file mode 100644 index 0000000000..7e6fb5dd42 --- /dev/null +++ b/_ko/tour/anonymous-function-syntax.md @@ -0,0 +1,46 @@ +--- +layout: tour +title: 익명 함수 구문 + +discourse: false + +partof: scala-tour + +num: 6 +language: ko + +next-page: higher-order-functions +previous-page: mixin-class-composition +--- + +스칼라를 사용하면 비교적 간결한 구문을 통해 익명 함수를 정의할 수 있다. 다음 표현식은 정수의 지정 함수를 만들어준다. + + (x: Int) => x + 1 + +이는 다음의 익명 클래스 정의를 축약한 표현이다. + + new Function1[Int, Int] { + def apply(x: Int): Int = x + 1 + } + +마찬가지로 여러 파라미터의 함수를 정의하거나: + + (x: Int, y: Int) => "(" + x + ", " + y + ")" + +파라미터가 없는 함수를 정의할 수도 있다: + + () => { System.getProperty("user.dir") } + +매우 간결하게 함수 타입을 작성하는 방법도 있다. 다음은 위에서 정의한 세 함수의 타입이다. + + Int => Int + (Int, Int) => String + () => String + +이 구문은 다음 타입을 축약한 표현이다. + + Function1[Int, Int] + Function2[Int, Int, String] + Function0[String] + +윤창석, 이한욱 옮김 \ No newline at end of file diff --git a/_ko/tour/automatic-closures.md b/_ko/tour/automatic-closures.md new file mode 100644 index 0000000000..17d07625db --- /dev/null +++ b/_ko/tour/automatic-closures.md @@ -0,0 +1,70 @@ +--- +layout: tour +title: 타입 의존 클로저의 자동 구성 + +discourse: false + +partof: scala-tour + +num: 30 +language: ko + +next-page: annotations +previous-page: operators +--- + +스칼라에선 파라미터가 없는 함수의 이름을 메소드의 파라미터로 사용할 수 있다. 이런 메소드가 호출되면 파라미터가 없는 함수의 이름에 해당하는 실제 파라미터를 찾지 않고, 대신 해당 파라미터의 계산을 캡슐화한 무항 함수를 전달하게 된다(소위 말하는 *이름에 의한 호출* 연산). + +다음 코드는 이 방식을 사용하는 방법을 보여준다. + + 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 + } + } + +`whileLoop` 함수는 `cond`와 `body`라는 두 파라미터를 받는다. 이 함수가 적용될 때 실제 파라미터는 계산되지 않는다. 대신 `whileLoop`의 내부에서 이 정형 파라미터를 사용할 때마다 암시적으로 생성된 무항 함수로 처리한다. 따라서 `whileLoop` 메소드는 재귀 구현의 방식에 맞춰 자바와 같은 while 반복문을 구현한다. + +[중위/후위 연산자](operators.html)와 이 기법을 함께 사용해 좀 더 복잡한 명령문(보기 좋게 작성된)을 생성할 수 있다. + +다음은 반복문을 제거한 명령문 구현이다. + + 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) + } + +`loop` 함수는 단순히 반복문의 내용을 받아서 `LoopUnlessCond` 클래스의 인스턴스(반복문 내용에 해당하는 객체를 캡슐화한)를 반환한다. 해당 내용이 아직 계산되지 않았음을 유념하자. `LoopUnlessCond` 클래스는 *중위 연산자*로 사용할 수 있는 `unless`라는 메소드를 포함하고 있다. 이런 접근을 통해 상당히 자연스럽게 표현된 새로운 반복문을 완성하게 된다: `loop { < stats > } unless ( < cond > )`. + +다음은 `TargetTest2`를 실행한 출력 결과다. + + i = 10 + i = 9 + i = 8 + i = 7 + i = 6 + i = 5 + i = 4 + i = 3 + i = 2 + i = 1 + +윤창석, 이한욱 옮김 diff --git a/_ko/tour/case-classes.md b/_ko/tour/case-classes.md new file mode 100644 index 0000000000..a68a61d7a0 --- /dev/null +++ b/_ko/tour/case-classes.md @@ -0,0 +1,125 @@ +--- +layout: tour +title: 케이스 클래스 + +discourse: false + +partof: scala-tour + +num: 10 +language: ko + +next-page: pattern-matching +previous-page: currying +--- + +스칼라는 _케이스 클래스_ 개념을 지원한다. 케이스 클래스는 아래와 같은 특징을 가지는 일반 클래스이다. + +* 기본적으로 불변 +* [패턴 매칭](pattern-matching.html)을 통해 분해가능 +* 레퍼런스가 아닌 구조적인 동등성으로 비교됨 +* 초기화와 운영이 간결함 + +추상 상위 클래스 Notification과 세개의 특정 Notification 타입들(케이스 클래스 Email, SMS, VoiceRecording으로 구현됨)로 구성된 Notification타입 계층구조를 위한 예제가 하나 있다. + + 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 + +케이스클래스를 인스턴스화 하는 것은 쉽다 (new 키워드를 사용할 필요가 없음을 주목하자.) + + val emailFromJohn = Email("john.doe@mail.com", "Greetings From John!", "Hello World!") + +케이스 클래스의 생성자 파라미터들은 public 값으로 다뤄지며, 직접 접근이 가능하다. + + val title = emailFromJohn.title + println(title) // prints "Greetings From John!" + +여러분은 케이스 클래스의 필드를 직접 수정할 수 없다. (필드 앞에 var를 넣으면 가능하지만, 권장되지는 않는다.) + + emailFromJohn.title = "Goodbye From John!" // 이것은 컴파일시에 에러가 난다. 우리는 val인 필드에 다른 값을 할당할수 없으며, 모든 케이스 클래스 필드는 기본적으로 val이다. + +대신에, 당신은 copy메서드를 사용해서 복사본을 만들수 있다. 아래에서 보듯, 당신은 몇몇 필드를 대체할수 있다. + + val editedEmail = emailFromJohn.copy(title = "I am learning Scala!", body = "It's so cool!") + println(emailFromJohn) // prints "Email(john.doe@mail.com,Greetings From John!,Hello World!)" + println(editedEmail) // prints "Email(john.doe@mail.com,I am learning Scala,It's so cool!)" + +모든 케이스 클래스에 대해서 스칼라 컴파일러는 구조적 동등성을 구현한 equals 메서드와, toString 메서드를 생성한다. + + val firstSms = SMS("12345", "Hello!") + val secondSms = SMS("12345", "Hello!") + + if (firstSms == secondSms) { + println("They are equal!") + } + + println("SMS is: " + firstSms) + +위 코드는 아래과 같이 출력한다 + + They are equal! + SMS is: SMS(12345, Hello!) + +케이스 클래스를 통해, 데이터와 함께 동작하는 패턴매칭을 사용할수 있다. 어떤Notification 타입을 받느냐에 따라 다른 메시지를 출력하는 함수가 있다. + + 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)) + + // prints: + // 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 + +아래에 if 방어구문을 사용한 다른 예제가 있다. if 방어구문을 통해, 패턴이 일치하는 분기문은 방어구문안의 조건이 false를 리턴하면 실패한다. + + 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) // nothing special, delegate to our original showNotification function + } + } + + 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)) + + // prints: + // 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! + +스칼라 프로그래밍에 있어서, 보통 model/group 데이터에 케이스 클래스를 사용하도록 권장되는데, 당신이 더욱 표현적이거나 유지보수가능한 코드를 작성할 때 도움이 된다. + +* 불변성은 당신이 언제 어디서 그것들이 수정되는지 신경쓸 필요 없게 만들어 준다. +* 값을 통한 비교는 여러분이 인스턴스들을 원시 값들인 것처럼 비교할수 있게 만들어 준다 - 클래스의 인스턴스들이 값 또는 참조를 통해 비교되는지와 같은 불확실성을 제거 +* 패턴매칭은 로직의 분기를 심플하게 만들어주며, 결국 적은 버그와 가독성 높은 코드로 이어진다. + +고광현 옮김 \ No newline at end of file diff --git a/_ko/tour/classes.md b/_ko/tour/classes.md new file mode 100644 index 0000000000..47a738c11b --- /dev/null +++ b/_ko/tour/classes.md @@ -0,0 +1,51 @@ +--- +layout: tour +title: 클래스 + +discourse: false + +partof: scala-tour + +num: 3 +language: ko + +next-page: traits +previous-page: unified-types +--- + +스칼라의 클래스는 런타임에 많은 객체로 인스턴스화 될 수 있는 정적 템플릿이다. +아래는 'Point' 클래스의 정의이다. + + class Point(xc: Int, yc: Int) { + var x: Int = xc + var y: Int = yc + def move(dx: Int, dy: Int) { + x = x + dx + y = y + dy + } + override def toString(): String = "(" + x + ", " + y + ")"; + } + +이 클래스는 변수 'x' 와 'y' 그리고 두 메서드 `move` 와 `toString` 을 정의한다. `move`는 두 개의 정수를 인자로 받지만 값을 반환하지는 않는다 (암시적 반환 타입인 `Unit` 은 Java와 같은 언어의 `void` 에 해당한다). 반면 `toString`은 아무 파라미터도 입력받지 않지만 `String` 값을 반환한다. `toString`은 기정의된 `toString` 메서드를 오버라이드 하기 때문에 `override` 플래그로 표시되어야 한다. + +스칼라의 클래스들은 생성자의 인자로 파라미터화 된다. 위의 코드는 두 개의 생성자 인자 `xc`와 `yc`를 정의한다. 이 두 인자는 클래스 전체에서 접근 가능하다. 예제에서 이들은 변수 `x`와 `y`를 초기화하는 데에 사용된다. + +클래스들은 아래의 예제가 보여주는 것처럼 새로운 기본형으로 초기화된다. + + object Classes { + def main(args: Array[String]) { + val pt = new Point(1, 2) + println(pt) + pt.move(10, 10) + println(pt) + } + } + +이 프로그램은 `main` 메서드를 가지고 있는 최상위 싱글톤의 형태로 실행가능한 어플리케이션 클래스를 정의한다. `main` 메서드는 새로운 `Point`를 생성하고 변수 `pt`에 저장한다. `val` 키워드로 정의된 값은 변경을 허용하지 않는다는 점에서 `var` 키워드로 정의된 값(`Point` 클래스 참)과는 다르다는 점에 주의하자. 즉, 이 값은 상수이다. + +이 프로그램의 결과는 아래와 같다: + + (1, 2) + (11, 12) + +윤창석, 이한욱 옮김 diff --git a/_ko/tour/compound-types.md b/_ko/tour/compound-types.md new file mode 100644 index 0000000000..218d27713d --- /dev/null +++ b/_ko/tour/compound-types.md @@ -0,0 +1,49 @@ +--- +layout: tour +title: 합성 타입 + +discourse: false + +partof: scala-tour + +num: 23 +language: ko + +next-page: explicitly-typed-self-references +previous-page: abstract-types +--- + +때론 객체의 타입을 여러 다른 타입의 서브타입으로 표현해야 할 때가 있다. 스칼라에선 *합성 타입(Compound Types)*으로 표현될 수 있는데, 이는 객체 타입들의 교차점을 의미한다. + +`Cloneable` 과 `Resetable`이라는 두 트레잇을 생각해보자: + + trait Cloneable extends java.lang.Cloneable { + override def clone(): Cloneable = { + super.clone().asInstanceOf[Cloneable] + } + } + trait Resetable { + def reset: Unit + } + +그리고 어떤 객체를 파라미터로 받아 복제한 뒤 원래의 객체를 초기화하는 `cloneAndReset` 이라는 함수를 작성하려고 한다: + + def cloneAndReset(obj: ?): Cloneable = { + val cloned = obj.clone() + obj.reset + cloned + } + +여기에서 파라미터 `obj`의 타입이 무엇인가 하는 의문을 가질 수 있다. 만약 타입이 `Clonable`이라면 이 객체는 복제될(`Cloned`) 수 있지만 리셋될(`reset`) 수는 없다. 반면 타입이 `Resetable`이라면 이 객체를 리셋할(`reset`) 수는 있지만 `clone` 기능을 사용할 수는 없다. 이러한 상황에서 타입 캐스팅을 피하기 위해 두개의 타입 `Clonable`과 `Resetable` 모두를 지정해 줄 수 있다. 스칼라의 이러한 합성타입은 다음과 같이 쓰인다. : `Clonable with Resetable` + +위의 함수를 다시 작성하면 다음과 같다. + + def cloneAndReset(obj: Cloneable with Resetable): Cloneable = { + //... + } + +합성 타입은 여러 객체 타입으로 구성될 수 있고, 단일 리파인먼트를 가짐으로써 객체 멤버의 시그니처의 범위를 좁힐 수도 있다. 일반적인 형태는 `A with B with C ... { 리파인먼트 }`이다. + +리파인먼트 사용 예제는 [추상 타입](abstract-types.html) 에 있다. + +윤창석, 이한욱 옮김, 고광현 수정 diff --git a/_ko/tour/currying.md b/_ko/tour/currying.md new file mode 100644 index 0000000000..03d6e171ec --- /dev/null +++ b/_ko/tour/currying.md @@ -0,0 +1,41 @@ +--- +layout: tour +title: 커링 + +discourse: false + +partof: scala-tour + +num: 9 +language: ko + +next-page: case-classes +previous-page: nested-functions +--- + +메소드에는 파라미터 목록을 여럿 정의할 수 있다. 파라미터 목록의 수 보다 적은 파라미터로 메소드가 호출되면, 해당 함수는 누락된 파라미터 목록을 인수로 받는 새로운 함수를 만든다. + +다음의 예제를 살펴보자. + + 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))) + } + +_주의: `modN` 메소드는 두 번의 `filter` 호출에서 부분적으로 사용됐다. 즉, 오직 첫 번째 인수만이 실제로 사용됐다. `modN(2)`라는 구문은 `Int => Boolean` 타입의 함수를 만들기 때문에 `filter` 함수의 두 번째 인수로 사용할 수 있게 된다._ + +다음은 위 프로그램의 결과다. + + List(2,4,6,8) + List(3,6) + +윤창석, 이한욱 옮김 \ No newline at end of file diff --git a/_ko/tour/default-parameter-values.md b/_ko/tour/default-parameter-values.md new file mode 100644 index 0000000000..7917647f68 --- /dev/null +++ b/_ko/tour/default-parameter-values.md @@ -0,0 +1,66 @@ +--- +layout: tour +title: 기본 파라미터 값 + +discourse: false + +partof: scala-tour + +num: 32 +language: ko + +next-page: named-parameters +previous-page: annotations +--- + +스칼라는 파라미터에 기본 값을 부여해서 호출자가 해당 파라미터를 생략할 수 있는 편리함을 제공한다. + +자바에선 거대한 메소드의 특정 파라미터에 기본 값을 제공하기 위해 수 많은 메소드를 오버로드하는 상황을 어렵지 않게 찾을 수 있다. 이는 특히 생성자의 경우에 그러하다. + + public class HashMap { + public HashMap(Map m); + /** 기본 크기가 (16)이고 로드 팩터가 (0.75)인 새로운 HashMap의 생성 */ + public HashMap(); + /** 기본 로드 팩터가 (0.75)인 새로운 HashMap의 생성 */ + public HashMap(int initialCapacity); + public HashMap(int initialCapacity, float loadFactor); + } + +여기선 실제론 다른 맵을 받는 생성자와 크기와 로드 팩터를 받는 생성자, 단지 이 두 생성자가 있을 뿐이다. 세 번째와 네 번째 생성자는 HashMap의 사용자가 대부분의 경우에서 아마도 유용하게 사용할 기본 값으로 로드 팩터와 크기를 설정해 인스턴스를 생성할 수 있도록 해준다. + +더 큰 문제는 기본으로 사용되는 값이 자바독과 코드 *모두*에 존재한다는 점이다. 이를 최신으로 유지하는 일은 쉽게 잊어버리게 된다. 이런 문제를 피하기 위해선 자바독에 해당 값이 표시될 퍼블릭 상수를 추가하는 접근을 주로 사용한다. + + public class HashMap { + public static final int DEFAULT_CAPACITY = 16; + public static final float DEFAULT_LOAD_FACTOR = 0.75; + + public HashMap(Map m); + /** 기본 크기가 (16)이고 로드 팩터가 (0.75)인 HashMap을 생성 */ + public HashMap(); + /** 기본 로드 팩터가 (0.75)인 새로운 HashMap의 생성 */ + public HashMap(int initialCapacity); + public HashMap(int initialCapacity, float loadFactor); + } + +이 때문에 우리가 계속 반복하는 상황은 줄지만, 표현력은 더욱 줄어든다. + +스칼라는 이에 관한 직접적인 지원을 추가했다. + + class HashMap[K,V](initialCapacity:Int = 16, loadFactor:Float = 0.75) { + } + + // 기본 값을 사용 + val m1 = new HashMap[String,Int] + + // 초기 크기는 20, 로드 팩터는 기본 값 + val m2= new HashMap[String,Int](20) + + // 둘 모드를 오버라이드 + val m3 = new HashMap[String,Int](20,0.8) + + // 이름을 지정한 인수를 통해 로드 팩터만을 오버라이드 + val m4 = new HashMap[String,Int](loadFactor = 0.8) + +*모든* 기본 값에 [이름을 지정한 파라미터]({{ site.baseurl }}/tutorials/tour/named-parameters.html)를 활용할 수 있음을 기억하자. + +윤창석, 이한욱 옮김 \ No newline at end of file diff --git a/_ko/tour/explicitly-typed-self-references.md b/_ko/tour/explicitly-typed-self-references.md new file mode 100644 index 0000000000..1cd164337f --- /dev/null +++ b/_ko/tour/explicitly-typed-self-references.md @@ -0,0 +1,104 @@ +--- +layout: tour +title: 명시적으로 타입이 지정된 자기 참조 + +discourse: false + +partof: scala-tour + +num: 24 +language: ko + +next-page: implicit-parameters +previous-page: compound-types +--- + +확장 가능한 소프트웨어를 개발할 땐 `this` 값의 타입을 명시적으로 선언하는 편이 편리할 수도 있다. 이를 이해하기 위해 스칼라로 작성된 작고 확장 가능한 그래프 데이터 구조를 만들어 보기로 하자. + +다음은 그래프가 무엇인지 설명해주는 정의이다. + + 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 + } + +그래프는 노드와 엣지의 리스트로 구성되며, 노드와 엣지의 타입은 모두 추상적으로 남겨뒀다. [추상 타입](abstract-types.html)을 사용해서 트레잇 Graph를 구현할 수 있도록 했고, 이를 통해 노드와 엣지에 해당하는 자신의 콘크리트 클래스를 만들 수 있다. 뿐만 아니라 `addNode`라는 메소드는 그래프에 새로운 노드를 추가해준다. 메소드 `connectWith`를 사용해 노드를 연결한다. + +다음 클래스는 클래스 `Graph`를 구현하는 한 예다. + + 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 + } + } + +클래스 `DirectedGraph`는 부분적 구현을 제공해서 `Graph` 클래스를 특수화한다. `DirectedGraph`가 더욱 확장 가능하길 원하기 때문에 그 일부만을 구현했다. 그 결과, 이 클래스의 구현 세부 사항은 모두가 확정되지 않은 상태로 열려있고, 엣지와 노드 타입은 추상적으로 처리됐다. 반면에, 클래스 `DirectedGraph`는 클래스 `EdgeImpl`과의 결합을 강화함으로써 엣지 타입의 구현에 관한 내용 일부를 추가적으로 표현하고 있다. 또한 클래스 `EdgeImpl`과 `NodeImpl`을 통해 엣지와 노드의 구현 일부를 먼저 정의했다. 새로운 노드와 엣지 객체를 부분 클래스 구현 안에서 생성해야만 하기 때문에 팩토리 메소드 `newNode`와 `newEdge`도 추가해야 한다. 메소드 `addNode`와 `connectWith`는 이 팩토리 메소드를 사용해 정의했다. 메소드 `connectWith`의 구현을 좀 더 자세히 살펴보면, 엣지를 생성하기 위해선 반드시 자기 참조 `this`를 팩토리 메소드 `newEdge`로 전달해야 함을 알 수 있다. 하지만 `this`에는 타입 `NodeImpl`이 할당되고, 이는 팩토리 메소드에서 요구하는 타입 `Node`와 호환되지 않는다. 결국, 위의 프로그램은 제대로 만들어지지 않았으며, 스칼라 컴파일러는 오류 메시지를 표시한다. + +스칼라에선 자기 참조 `this`에 다른 타입을 명시적으로 부여함으로써 클래스를 다른 타입(향후에 구현될)과 묶을 수 있다. 이 기법을 사용하면 위의 코드를 올바르게 고칠 수 있다. 명시적 자기 타입은 클래스 `DirectedGraph`의 내부에서 지정된다. + +다음은 고쳐진 프로그램이다. + + abstract class DirectedGraph extends Graph { + ... + class NodeImpl extends NodeIntf { + self: Node => + def connectWith(node: Node): Edge = { + val edge = newEdge(this, node) // now legal + edges = edge :: edges + edge + } + } + ... + } + +새롭게 정의한 클래스 `NodeImpl`에선 `this`의 타입이 `Node`다. 타입 `Node`가 추상적이기 때문에 `NodeImpl`이 정말 `Node`의 서브타입인지 알 수 없으며, 스칼라의 타입 시스템은 이 클래스의 인스턴스화를 허용하지 않는다. 하지만 인스턴스화를 위해선 언젠간 `NodeImpl`(의 서브클래스)이 타입 `Node`의 서브타입을 지정해주도록 명시적 타입 어노테이션을 표시했다. + +다음은 모든 추상 클래스 멤버가 콘크리트하게 변경된, `DirectedGraph`의 콘크리트한 특수화다. + + 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) + } + +이젠 `NodeImpl`에서 `Node`(단순히 `NodeImpl`의 또 다른 이름일 뿐이다)의 서브타입을 지정했기 때문에, 이 클래스에선 `NodeImpl`을 인스턴스화 할 수 있음을 기억하자. + +다음은 클래스 `ConcreteDirectedGraph`를 사용하는 예다. + + 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) + } + +윤창석, 이한욱 옮김 \ No newline at end of file diff --git a/_ko/tour/extractor-objects.md b/_ko/tour/extractor-objects.md new file mode 100644 index 0000000000..f72bf933f7 --- /dev/null +++ b/_ko/tour/extractor-objects.md @@ -0,0 +1,44 @@ +--- +layout: tour +title: 추출자 오브젝트 + +discourse: false + +partof: scala-tour + +num: 15 +language: ko + +next-page: sequence-comprehensions +previous-page: regular-expression-patterns +--- + +스칼라에선 캐이스 클래스와 상관 없이 패턴을 정의할 수 있다. 이런 측면에서 추출자라 불리는 unapply라는 이름의 메소드를 정의한다. 예를 들어, 다음의 코드는 추출자 오브젝트 Twice를 정의한다. + + 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) } // prints 21 + } + +여기선 두 가지 구문적 컨벤션을 사용했다. + +패턴 `case Twice(n)`는 `Twice.unapply`를 호출하는데, 이는 짝수와의 매칭에 사용된다. `unapply`의 반환 값은 인수가 매칭됐는지 여부와 다른 하위 값을 더 매칭해 나가야 할지를 알려준다. 여기선 하위 값이 `z/2`이다. + +`apply` 메소드는 패턴 매칭에 필수 요소가 아니며, 단지 생성자를 흉내내기 위해 사용된다. `val x = Twice(21)`는 `val x = Twice.apply(21)`로 확장된다. + +`unapply`의 반환 값은 반드시 다음 중에서 선택해야 한다. + +* 단순한 테스트라면 `Boolean`을 반환한다. `case even()`이 한 예다. +* 타입 T의 단일 하위 값을 반환한다면, `Option[T]`를 반환한다. +* 여러 하위 값 `T1,...,Tn`를 반환하고 싶다면, 이를 `Option[(T1,...,Tn)]`과 같이 튜플로 묶어준다. + +때론 하위 값의 개수가 미리 고정돼 시퀀스를 반환하고 싶을 때도 있다. 이런 이유로 `unapplySeq`를 통해 패턴을 정의할 수 있다. 마지막 하위 값의 타입 `Tn`은 반드시 `Seq[S]`여야 한다. 이 기법은 `case List(x1, ..., xn)`과 같은 패턴에 사용된다. + +추출자는 코드의 유지 관리성을 향상시켜준다. 더욱 자세한 내용은 Emir, Odersky, Willians의 ["패턴을 통한 오브젝트의 매칭"](https://infoscience.epfl.ch/record/98468/files/MatchingObjectsWithPatterns-TR.pdf)(2007년 1월) 4장을 읽어보도록 하자. + +윤창석, 이한욱 옮김 diff --git a/_ko/tour/generic-classes.md b/_ko/tour/generic-classes.md new file mode 100644 index 0000000000..66d27163e2 --- /dev/null +++ b/_ko/tour/generic-classes.md @@ -0,0 +1,45 @@ +--- +layout: tour +title: 제네릭 클래스 + +discourse: false + +partof: scala-tour + +num: 17 +language: ko + +next-page: variances +previous-page: sequence-comprehensions +--- + +자바 5(다른 이름은 [JDK 1.5](http://java.sun.com/j2se/1.5/))와 같이, 스칼라는 타입으로 파라미터화된 클래스의 빌트인 지원을 제공한다. 이런 제네릭 클래스는 특히 컬렉션 클래스의 개발에 유용하다. 이에 관한 예제를 살펴보자. + + 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 } + } + +클래스 `Stack`은 임의의 타입 `T`를 항목의 타입으로 하는 명령형(변경 가능한) 스택이다. 타입 파라미터는 올바른 항목(타입 `T` 인)만을 스택에 푸시하도록 강제한다. 마찬가지로 타입 파라미터를 사용해서 메소드 `top`이 항상 지정된 타입만을 반환하도록 할 수 있다. + +다음은 스택을 사용하는 예다. + + object GenericsTest extends App { + val stack = new Stack[Int] + stack.push(1) + stack.push('a') + println(stack.top) + stack.pop() + println(stack.top) + } + +이 프로그램의 결과는 다음과 같다. + + 97 + 1 + +_주의: 제네릭 클래스의 서브타입은 *불가변*이다. 즉, 캐릭터 타입의 스택인 `Stack[Char]`를 정수형 스택인 `Stack[Int]`처럼 사용할 수는 없다. 실제로 캐릭터 스택에는 정수가 들어가기 때문에 이런 제약은 이상하게 보일 수도 있다. 결론적으로 `S = T`일 때만 `Stack[T]`가 `Stack[S]`의 서브타입일 수 있으며, 반대의 관계도 마찬가지로 성립해야 한다. 이런 특징이 상당히 큰 제약일 수 있기 때문에, 스칼라는 제네릭 타입의 서브타입을 지정하는 행위를 제어하기 위해 [타입 파라미터 어노테이션 방법](variances.html)을 제공한다._ + +윤창석, 이한욱 옮김 \ No newline at end of file diff --git a/_ko/tour/higher-order-functions.md b/_ko/tour/higher-order-functions.md new file mode 100644 index 0000000000..fc05e4b802 --- /dev/null +++ b/_ko/tour/higher-order-functions.md @@ -0,0 +1,40 @@ +--- +layout: tour +title: 고차 함수 + +discourse: false + +partof: scala-tour + +num: 7 +language: ko + +next-page: nested-functions +previous-page: anonymous-function-syntax +--- + +스칼라는 고차 함수의 정의를 허용한다. 이런 함수는 _다른 함수를 파라미터로 받거나_, 수행의 _결과가 함수다_. 다음과 같은 함수 `apply`는 다른 함수 `f`와 값 `v`를 받아서 함수 `f`를 `v`에 적용한다. + + def apply(f: Int => String, v: Int) = f(v) + +_주의: 문맥적으로 함수가 필요하다면, 메소드는 자동으로 이에 맞게 강제된다._ + +다음은 또 다른 예제다. + + 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)) + } + +실행 결과는 다음과 같다. + + [7] + +이 예제에서 메소드 `decorator.layout`은 메소드 `apply`에서 요구하는 바와 같이 타입 `Int => String`의 값으로 자동 강제된다. 메소드 `decorator.layout`이 _다형성 메소드_(즉, 자신의 서명 타입 중 일부를 추상화하는)이고, 스칼라 컴파일러는 가장 적합한 메소드 타입을 인스턴스화 해야만 한다는 점을 명심하자. + +윤창석, 이한욱 옮김 \ No newline at end of file diff --git a/_ko/tour/implicit-conversions.md b/_ko/tour/implicit-conversions.md new file mode 100644 index 0000000000..eb42d4a0fb --- /dev/null +++ b/_ko/tour/implicit-conversions.md @@ -0,0 +1,62 @@ +--- +layout: tour +title: 암시적 변환 + +discourse: false + +partof: scala-tour + +num: 26 +language: ko + +next-page: polymorphic-methods +previous-page: implicit-parameters +--- + +타입 `S`로부터 타입 `T`로의 암시적 변환는 함수 타입 `S => T`의 암시적 값이나 해당 타입으로 변환 가능한 암시적 메소드로 정의된다. + +암시적 변환은 두 가지 상황에 적용된다. + +* 표현식 `e`의 타입이 `S`이고, `S`는 표현식의 기대 타입 `T`를 따르지 않을 때. +* `e`의 타입이 `T`인 `e.m`를 선택한 상황에서, 선택자 `m`이 `T`의 멤버가 아닐 때. + + +첫 번째 경우에서 변환 `c`가 `e`에 적용되며, 결과 타입이 `T`를 따르는지 탐색한다. +두 번째 경우에선 변환 `c`가 `e`에 적용되며, 결과가 `m`이라는 이름의 멤버를 포함하고 있는지 탐색한다. + +타입이 `List[Int]`인 두 리스트 xs와 ys의 아래 연산은 허용된다: + + xs <= ys + +아래에 정의된 암시적 메소드 `list2ordered`와 `int2ordered`가 범위 안에 있다고 가정한다. + + 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] { /* .. */ } + +암시적으로 임포트되는 오브젝트 `scala.Predef`는 미리 정의된 여러 타입(예: `Pair`)과 메소드(예: `assert`)뿐만 아니라 여러 뷰도 함께 선언한다. + +예를들면, `java.lang.Integer`를 기대하는 자바 메서드를 호출할때, `scala.Int`를 대신 넘겨도 무방하다. 그 이유는 Predef가 아래 암시적 변환들을 포함하기 때문이다. + +```tut +import scala.language.implicitConversions + +implicit def int2Integer(x: Int) = + java.lang.Integer.valueOf(x) +``` + +암시적 변환이 무분별하게 사용될 경우 잠재적인 위험을 가질 수 있기 때문에, 컴파일러는 암시적 변환의 선언을 컴파일할때 경고한다. + +To turn off the warnings take either of these actions: +경고를 끄기 위해서는 아래 중 하나를 선택해야 한다. + +* `scala.language.implicitConversions` 를 암시적 변환의 선언이 있는 범위로 import +* `-language:implicitConversions` 옵션으로 컴파일러 실행 + +변환이 컴팡일러에 의해 적용될때 경고가 발생하지 않는다. + + +윤창석, 이한욱 옮김, 고광현 업데이트 diff --git a/_ko/tour/implicit-parameters.md b/_ko/tour/implicit-parameters.md new file mode 100644 index 0000000000..8132d96ad6 --- /dev/null +++ b/_ko/tour/implicit-parameters.md @@ -0,0 +1,59 @@ +--- +layout: tour +title: 암시적 파라미터 + +discourse: false + +partof: scala-tour + +num: 25 +language: ko + +next-page: implicit-conversions +previous-page: explicitly-typed-self-references +--- + +_암시적 파라미터_ 를 갖는 메서드 역시 다른 일반적인 메서드와 마찬가지로 인수를 적용할 수 있다. 이런 경우 implicit 키워드는 아무런 영향을 미치지 않는다. 하지만, 이 경우에 암시적 파라미터에 주는 인수를 생략한다면, 그 생략된 인수는 자동적으로 제공될 것이다. + +실제로 암시적 파라미터가 넘겨받을 수 있는 인수의 종류는 두 가지로 나눌 수 있다: + +* 첫째, 메서드가 호출되는 시점에서 prefix 없이 접근할 수 있고 암시적 정의나 암시적 파라미터로 표시된 모든 식별자 x +* 둘째, 암시적(implicit)이라고 표시된 암시적 파라미터의 타입과 관련된 모듈의 모든 멤버 + +아래 예제에서는 모노이드의 `add`와 `unit`메서드를 이용해서 리스트 항목들의 합을 구하는 `sum` 메서드를 정의한다. +암시적 값은 최상위 레벨이 될 수 없고 템플릿의 멤버여야만 한다는 점에 주의하자. + + /** 이 예제는 어떻게 암묵적인 파라미터들이 동작하는지 보여주기 위해, 추상적인 수학으로부터 나온 구조를 사용한다. 하위 그룹은 관련된 연산(add를 말함/한쌍의 A를 합치고 또다른 A를 리턴)을 가진 집합 A에 대한 수학적인 구조이다. */ + abstract class SemiGroup[A] { + def add(x: A, y: A): A + } + /** monoid는 A의 구분된 요소(unit을 말함/A의 어떤 다른 요소와 합산될때, 다른 요소를 다시 리턴하는)를 가진 하위 그룹이다 */ + abstract class Monoid[A] extends SemiGroup[A] { + def unit: A + } + object ImplicitTest extends App { + /** 암묵적 파라미터들의 동작을 보여주기 위해서, 우리는 먼저 문자열과 숫자에 대한 monoid를 정의했다. implicit 키워드는 해당하는 object가 암묵적으로 어떤 함수의 implicit으로 표시된 파라미터에 이 scope안에서 사용될수 있음을 나타낸다. */ + 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 + } + /** 이 메서드는 List[A]를 가지며 A를 리턴한다. 그리고 리턴된 A는 전체 리스트에 걸쳐, 지속적으로 monoid 연산이 적용되 합쳐진 값을 이야기 한다. 파라미터 m을 암시적으로 만든다는 것은, 우리가 반드시 호출 시점에 xs파라미터를 제공해야한다는 것을 의미하고, 그 이후에 우리가 List[A]를 가진다면, A가 실제로 어떤 타입인지, 어떤타입의 Monoid[A]가 필요한지도 알게 된다. 그 후, 현재 scope에서 어떤 val 또는 object가 해당 타입을 가지고 있는지 암묵적으로 알게 되며, 명시적으로 표현할 필요 없이 그것을 사용할 수 있다. */ + def sum[A](xs: List[A])(implicit m: Monoid[A]): A = + if (xs.isEmpty) m.unit + else m.add(xs.head, sum(xs.tail)) + + /** 아래코드에서 우리는 각각 하나의 파라미터로 sum을 두번 호출한다. sum의 두번째 파라미터인 m이 암시적이기 때문에 그 값은 현재 scope에서 각 케이스마다 요구되는 monoid의 타입을 기준으로 탐색된다. 두 표현은 완전히 계산될 수 있음을 의미한다. */ + println(sum(List(1, 2, 3))) // uses IntMonoid implicitly + println(sum(List("a", "b", "c"))) // uses StringMonoid implicitly + } + +아래는 이 스칼라 프로그램의 결과이다. + + 6 + abc + +윤창석, 이한욱, 고광현 옮김 diff --git a/_ko/tour/inner-classes.md b/_ko/tour/inner-classes.md new file mode 100644 index 0000000000..ac0f2bba2c --- /dev/null +++ b/_ko/tour/inner-classes.md @@ -0,0 +1,91 @@ +--- +layout: tour +title: 내부 클래스 + +discourse: false + +partof: scala-tour + +num: 21 +language: ko + +next-page: abstract-types +previous-page: lower-type-bounds +--- + +스칼라의 클래스는 다른 클래스를 멤버로 가질 수 있다. 자바와 같은 언어의 내부 클래스는 자신을 감싸고 있는 클래스의 멤버인 반면에, 스칼라에선 내부 클래스가 외부 객체의 경계 안에 있다. 이런 차이점을 분명히 하기 위해 그래프 데이터타입의 구현을 간단히 그려보자. + + 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 + } + } + +이 프로그램에선 노드의 리스트로 그래프를 나타냈다. 노드는 내부 클래스 `Node`의 객체다. 각 노드는 리스트 `connectedNodes`에 저장되는 이웃의 목록을 갖고 있다. 이제 몇몇 노드를 선택하고 이에 연결된 노드를 추가하면서 점진적으로 그래프를 구축할 수 있다. + + 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) + } + +정의된 여러 엔티티의 타입이 무엇인지 명시적으로 알려주는 타입 정보를 사용해 위의 예제를 확장해보자. + + 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) + } + +이 코드는 외부 인스턴스(이 예제의 객체 `g`)를 접두어로 지정해 노드 타입을 분명히 나타내고 있다. 두 그래프가 있는 상황에서, 스칼라의 타입 시스템은 한 그래프에 정의된 노드를 다른 그래프에서도 정의해 공유하는 상황을 허용하지 않는다. 이는 다른 그래프의 노드는 다른 타입을 갖기 때문이다. +다음은 잘못된 프로그램이다. + + object IllegalGraphTest extends App { + val g: Graph = new Graph + val n1: g.Node = g.newNode + val n2: g.Node = g.newNode + n1.connectTo(n2) // legal + val h: Graph = new Graph + val n3: h.Node = h.newNode + n1.connectTo(n3) // illegal! + } + +자바에선 이 예제 프로그램의 마지막 줄이 올바른 표현임을 상기하자. 자바는 두 그래프의 노드에 `Graph.Node`라는 동일한 타입을 할당한다. 즉, `Node`에는 클래스 `Graph`가 접두어로 붙는다. 스칼라에서도 이런 타입을 표현할 수 있으며, 이를 `Graph#Node`로 나타낸다. 서로 다른 그래프 간에 노드를 연결할 수 있길 원한다면 초기 그래프 구현의 정의를 다음과 같이 변경해야 한다. + + 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 + } + } + +> 이 프로그램에선 하나의 노드를 서로 다른 두 그래프에 추가할 수 없음에 주의하자. 이 제약도 함께 제거하기 위해선 변수 nodes의 타입을 `Graph#Node`로 바꿔야 한다. + +윤창석, 이한욱 옮김 \ No newline at end of file diff --git a/_ko/tour/local-type-inference.md b/_ko/tour/local-type-inference.md new file mode 100644 index 0000000000..421fb41036 --- /dev/null +++ b/_ko/tour/local-type-inference.md @@ -0,0 +1,57 @@ +--- +layout: tour +title: 로컬 타입 추론 + +discourse: false + +partof: scala-tour + +num: 28 +language: ko + +next-page: operators +previous-page: polymorphic-methods +--- + +스칼라는 프로그래머가 특정한 타입 어노테이션을 생략할 수 있도록 해주는 빌트인 타입 추론 기능을 갖추고 있다. 예를 들어, 스칼라에선 컴파일러가 변수의 초기화 표현식으로부터 타입을 추론할 수 있기 때문에 변수의 타입을 지정할 필요가 없을 때가 많다. 또한 메소드의 반환 타입은 본문의 타입과 일치하기 때문에 이 반환 타입 역시 컴파일러가 추론할 수 있고, 주로 생략된다. + +다음 예제를 살펴보자. + + object InferenceTest1 extends App { + val x = 1 + 2 * 3 // x의 타입은 Int다. + val y = x.toString() // y의 타입은 String이다. + def succ(x: Int) = x + 1 // 메소드 succ는 Int 값을 반환한다. + } + +재귀 메소드의 경우는 컴파일러가 결과 타입을 추론할 수 없다. 다음 프로그램에선 이와 같은 이유로 컴파일러가 추론할 수 없다. + + object InferenceTest2 { + def fac(n: Int) = if (n == 0) 1 else n * fac(n - 1) + } + +[다형성 메소드](polymorphic-methods.html)를 호출하거나 [제네릭 클래스](generic-classes.html)를 인스턴스화 할 때 반드시 타입을 지정할 의무는 없다. 스칼라 컴파일러는 컨텍스트와 실제 메소드/생성자 파라미터로부터 생략된 타입 파라미터를 추론한다. + +다음 예제는 이를 나타낸 예제다. + + case class MyPair[A, B](x: A, y: B); + object InferenceTest3 extends App { + def id[T](x: T) = x + val p = MyPair(1, "scala") // 타입: MyPair[Int, String] + val q = id(1) // 타입: Int + } + +이 프로그램의 마지막 두 줄은 추론된 타입을 명시적으로 나타낸 다음 코드와 동일하다. + + val x: MyPair[Int, String] = MyPair[Int, String](1, "scala") + val y: Int = id[Int](1) + +다음 프로그램에서 나타나는 문제와 같이, 일부 상황에선 스칼라의 타입 추론 기능에 의존하는 것은 상당히 위험할 수 있다. + + object InferenceTest4 { + var obj = null + obj = new Object() + } + +이 프로그램은 변수 `obj`의 타입 추론이 `Null`이기 때문에 컴파일되지 않는다. 해당 타입의 유일한 값이 `null`이기 때문에 이 변수는 다른 값을 나타낼 수 없다. + +윤창석, 이한욱 옮김 \ No newline at end of file diff --git a/_ko/tour/lower-type-bounds.md b/_ko/tour/lower-type-bounds.md new file mode 100644 index 0000000000..c36d5a43c3 --- /dev/null +++ b/_ko/tour/lower-type-bounds.md @@ -0,0 +1,53 @@ +--- +layout: tour +title: 하위 타입 경계 + +discourse: false + +partof: scala-tour + +num: 20 +language: ko + +next-page: inner-classes +previous-page: upper-type-bounds +--- + +[상위 타입 경계](upper-type-bounds.html)가 특정 타입의 서브타입으로 타입을 제한한다면, *하위 타입 경계*는 대상 타입을 다른 타입의 슈퍼타입으로 선언한다. `T>:A`는 타입 파라미터 `T`나 추상 타입 `T`가 타입 `A`의 슈퍼타입임을 나타낸다. + +상위 타입 경계를 유용하게 활용할 수 있는 예제를 살펴보자. + + 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) + } + +이 프로그램은 앞쪽에 항목을 추가하는 동작을 제공하는 링크드 리스트를 구현하고 있다. 안타깝게도 클래스 `ListNode`의 타입 파라미터로 사용된 타입은 불변자이고, 타입 `ListNode[String]`은 `타입 List[Object]`의 서브타입이 아니다. 가변성 어노테이션의 도움을 받아서 이런 서브타입 관계의 시맨틱을 표현할 수 있다. + + case class ListNode[+T](h: T, t: ListNode[T]) { ... } + +하지만 순가변성 어노테이션은 반드시 순가변 위치의 타입 변수로 사용돼야만 하기 때문에, 이 프로그램은 컴파일되지 않는다. 타입 변수 `T`가 메소드 `prepend`의 파라미터 타입으로 쓰였기 때문에 이 규칙이 깨지게 된다. 그렇지만 *하위 타입 경계*의 도움을 받는다면 `T`가 순가변 위치에만 나타나도록 `prepend` 메소드를 구현할 수 있다. + +다음이 이를 적용한 코드다. + + 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) + } + +_주의:_ 새로운 `prepend` 메소드에선 타입의 제약이 조금 줄어들게 된다. 예를 들어 이미 만들어진 리스트에 슈퍼타입의 객체를 집어 넣을 수도 있다. 그 결과로 만들어지는 리스트는 이 슈퍼타입의 리스트다. + +이에 관한 코드를 살펴보자. + + 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) + } + +윤창석, 이한욱 옮김 \ No newline at end of file diff --git a/_ko/tour/mixin-class-composition.md b/_ko/tour/mixin-class-composition.md new file mode 100644 index 0000000000..c290082dca --- /dev/null +++ b/_ko/tour/mixin-class-composition.md @@ -0,0 +1,51 @@ +--- +layout: tour +title: 믹스인 클래스 컴포지션 + +discourse: false + +partof: scala-tour + +num: 5 +language: ko + +next-page: anonymous-function-syntax +previous-page: traits +--- + +_단일 상속_ 만을 지원하는 여러 언어와는 달리, 스칼라는 더욱 일반화된 시각에서 클래스를 재사용한다. 스칼라는 새로운 클래스를 정의할 때 _클래스의 새로운 멤버 정의_ (즉, 슈퍼클래스와 비교할 때 변경된 부분)를 재사용할 수 있다. 이를 _믹스인 클래스 컴포지션_ 이라고 부른다. 이터레이터를 추상화한 다음 예제를 살펴보자. + + abstract class AbsIterator { + type T + def hasNext: Boolean + def next: T + } + +이어서, 이터레이터가 반환하는 모든 항목에 주어진 함수를 적용해주는 `foreach` 메소드로 `AbsIterator`를 확장한 믹스인 클래스를 살펴보자. 믹스인으로 사용할 수 있는 클래스를 정의하기 위해선 `trait`이란 키워드를 사용한다. + + trait RichIterator extends AbsIterator { + def foreach(f: T => Unit) { while (hasNext) f(next) } + } + +다음은 주어진 문자열의 캐릭터를 차례로 반환해주는 콘크리트 이터레이터 클래스다. + + 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 } + } + +`StringIterator`와 `RichIterator`를 하나의 클래스로 합치고 싶다면 어떻게 할까. 두 클래스 모두는 코드가 포함된 멤버 구현을 담고 있기 때문에 단일 상속과 인터페이스 만으론 불가능한 일이다. 스칼라는 _믹스인 클래스 컴포지션_ 으로 이런 상황을 해결해준다. 프로그래머는 이를 사용해 클래스 정의에서 변경된 부분을 재사용할 수 있다. 이 기법은 주어진 문자열에 포함된 모든 캐릭터를 한 줄로 출력해주는 다음의 테스트 프로그램에서와 같이, `StringIterator`와 `RichIterator`를 통합할 수 있도록 해준다. + + object StringIteratorTest { + def main(args: Array[String]) { + class Iter extends StringIterator(args(0)) with RichIterator + val iter = new Iter + iter foreach println + } + } + +`main` 함수의 `Iter` 클래스는 부모인 `StringIterator`와 `RichIterator`를 `with` 키워드를 사용해 믹스인 컴포지션해서 만들어졌다. 첫 번째 부모를 `Iter`의 _슈퍼클래스_ 라고 부르며, 두 번째 부모를(더 이어지는 항목이 있다면 이 모두를 일컬어) _믹스인_ 이라고 한다. + +윤창석, 이한욱 옮김 diff --git a/_ko/tour/named-parameters.md b/_ko/tour/named-parameters.md new file mode 100644 index 0000000000..7f13650e38 --- /dev/null +++ b/_ko/tour/named-parameters.md @@ -0,0 +1,39 @@ +--- +layout: tour +title: 이름을 지정한 파라미터 + +discourse: false + +partof: scala-tour + +num: 33 +language: ko + +previous-page: default-parameter-values +--- + +메소드와 함수를 호출할 땐 다음과 같이 해당 호출에서 명시적으로 변수의 이름을 사용할 수 있다. + + def printName(first:String, last:String) = { + println(first + " " + last) + } + + printName("John","Smith") + // "John Smith"를 출력 + printName(first = "John",last = "Smith") + // "John Smith"를 출력 + printName(last = "Smith",first = "John") + // "John Smith"를 출력 + +일단 호출에서 파라미터 이름을 사용했다면 모든 파라미터에 이름이 붙어 있는 한 그 순서는 중요치 않다. 이 기능은 [기본 파라미터 값]({{ site.baseurl }}/tutorials/tour/default-parameter-values.html)과 잘 어울려 동작한다. + + def printName(first:String = "John", last:String = "Smith") = { + println(first + " " + last) + } + + printName(last = "Jones") + // "John Jones"를 출력 + +여러분이 원하는 순서대로 파라미터를 위치시킬 수 있기 때문에 파라미터 목록 중에서 가장 중요한 파라미터부터 기본 값을 사용할 수 있다. + +윤창석, 이한욱 옮김 \ No newline at end of file diff --git a/_ko/tour/nested-functions.md b/_ko/tour/nested-functions.md new file mode 100644 index 0000000000..d7eed785cf --- /dev/null +++ b/_ko/tour/nested-functions.md @@ -0,0 +1,35 @@ +--- +layout: tour +title: 중첩 함수 + +discourse: false + +partof: scala-tour + +num: 8 +language: ko + +next-page: currying +previous-page: higher-order-functions +--- + +스칼라에선 중첩 함수를 정의할 수 있다. 다음 오브젝트는 정수의 리스트에서 지정된 값보다 작은 값을 값을 추출해주는 `filter` 함수를 제공한다. + + 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)) + } + +_주의: 중첩 함수 `process`는 `filter`의 파라미터 값으로써 외부 범위에서 정의된 `threshold`를 참조한다._ + +이 프로그램의 실행 결과는 다음과 같다. + + List(1,2,3,4) + +윤창석, 이한욱 옮김 \ No newline at end of file diff --git a/_ko/tour/operators.md b/_ko/tour/operators.md new file mode 100644 index 0000000000..1eb3c87030 --- /dev/null +++ b/_ko/tour/operators.md @@ -0,0 +1,36 @@ +--- +layout: tour +title: 연산자 + +discourse: false + +partof: scala-tour + +num: 29 +language: ko + +next-page: automatic-closures +previous-page: local-type-inference +--- + +스칼라에선 단일 파라미터를 취하는 모든 메소드를 *중위 연산자*로 사용할 수 있다. 다음은 `and`와 `or`, `negate` 등의 세 가지 메소드를 정의하고 있는 클래스 `MyBool`의 정의다. + + 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 = new MyBool(!x) + } + +이제 `and`와 `or`를 중위 연산자로 사용할 수 있다. + + def not(x: MyBool) = x negate; // 여기엔 세미콜론이 필요함 + def xor(x: MyBool, y: MyBool) = (x or y) and not(x and y) + +이 코드의 첫 번째 줄에서 알 수 있듯이, 무항 메소드는 후위 연산자로 사용할 수도 있다. 두 번째 줄에선 `and`와 `or` 메소드와 함께 새로운 함수 `not`을 사용해 `xor` 함수를 정의했다. 이 예제에선 _중위 연산자_를 사용해 `xor` 정의의 가독성을 높일 수 있다. + +다음은 이와 같은 코드를 좀 더 전통적인 객체지향 언어 구문에 따라 작성해본 코드다. + + def not(x: MyBool) = x.negate; // 여기엔 세미콜론이 필요함 + def xor(x: MyBool, y: MyBool) = x.or(y).and(x.and(y).negate) + +윤창석, 이한욱 옮김 \ No newline at end of file diff --git a/_ko/tour/pattern-matching.md b/_ko/tour/pattern-matching.md new file mode 100644 index 0000000000..9be1ac47e4 --- /dev/null +++ b/_ko/tour/pattern-matching.md @@ -0,0 +1,46 @@ +--- +layout: tour +title: 패턴 매칭 + +discourse: false + +partof: scala-tour + +num: 11 +language: ko + +next-page: singleton-objects +previous-page: case-classes +--- + +스칼라는 범용적 빌트인 패턴 매칭 기능을 제공한다. 이는 우선 매칭 정책에 따라 어떤 종류의 데이터든 매칭할 수 있도록 해준다. +다음은 정수 값을 매칭하는 방법에 관한 간단한 예제다. + + object MatchTest1 extends App { + def matchTest(x: Int): String = x match { + case 1 => "one" + case 2 => "two" + case _ => "many" + } + println(matchTest(3)) + } + +`case` 명령문을 포함하고 있는 블록은 정수를 문자열로 매핑하는 함수를 정의한다. `match`라는 키워드는 함수(위에서 살펴본 패턴 매칭 함수와 같은)를 오브젝트에 적용하는 편리한 방법을 제공한다. + +다음은 두 번째 예제로 여러 타입의 패턴에 맞춰 값을 매칭한다. + + 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")) + } + +첫 번째 `case`는 `x`가 정수 값 `1`일 때 매칭된다. 두 번째 `case`는 `x`가 문자열 `"two"`일 때 매칭된다. 세 번째 케이스는 타입이 지정된 패턴으로 구성되며, 모든 정수와 매칭돼 선택자 값 `x`를 정수 타입 변수 `y`로 연결한다. + +스칼라의 패턴 매칭 명령문은 [케이스 클래스](case-classes.html)를 통해 표현되는 대수 타입과 매칭할 때 가장 유용하다. +또한 스칼라는 [추출자 오브젝트](extractor-objects.html)의 `unapply` 메소드를 사용해, 독립적인 케이스 클래스로 패턴을 정의할 수 있도록 해준다. + +윤창석, 이한욱 옮김 \ No newline at end of file diff --git a/_ko/tour/polymorphic-methods.md b/_ko/tour/polymorphic-methods.md new file mode 100644 index 0000000000..4d1cbd177e --- /dev/null +++ b/_ko/tour/polymorphic-methods.md @@ -0,0 +1,35 @@ +--- +layout: tour +title: 다형성 메소드 + +discourse: false + +partof: scala-tour + +num: 27 +language: ko + +next-page: local-type-inference +previous-page: implicit-conversions +--- + +스칼라의 메소드는 값과 타입 모두가 파라미터화 될 수 있다. 클래스 수준에서와 같이, 값 파라미터는 괄호의 쌍으로 묶이며 타입 파라미터는 브래킷의 쌍 안에 위치한다. + +다음의 예제를 살펴보자. + + object PolyTest extends App { + 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)) + } + +오브젝트 `PolyTest`의 메소드 `dup`는 타입 `T`와 값 파라미터인 `x: T` 및 `n: Int`로 파라미터화 됐다. 프로그래머는 메소드 `dup`를 호출하며 필요한 파라미터를 전달하지만_(위의 프로그램 8번째 줄을 보자)_, 9번째 줄에서와 같이 프로그래머는 타입 파라미터를 명시적으로 전달할 필요가 없다. 메소드가 호출되는 시점에서 주어진 값 파라미터의 타입과 컨텍스트를 살펴봄으로써 이를 해결해준다. + +트레잇 `App`은 간단한 테스트 프로그램을 작성하도록 설계됐으며, JVM의 코드 최적화 능력에 영향을 주기 때문에 프로덕션 코드에선 사용할 수 없음을 상기하자(스칼라 버전 2.8.x와 그 이전 버전). 그 대신 `def main()`을 사용하도록 하자. + +윤창석, 이한욱 옮김 \ No newline at end of file diff --git a/_ko/tour/regular-expression-patterns.md b/_ko/tour/regular-expression-patterns.md new file mode 100644 index 0000000000..4d1a0c00ee --- /dev/null +++ b/_ko/tour/regular-expression-patterns.md @@ -0,0 +1,46 @@ +--- +layout: tour +title: 정규 표현식 패턴 + +discourse: false + +partof: scala-tour + +num: 14 +language: ko + +next-page: extractor-objects +previous-page: singleton-objects +--- + +## 우측 무시 시퀀스 패턴 ## + +우측 무시 패턴은 `Seq[A]`의 서브 타입이나 반복되는 정형 파라미터를 가진 케이스 클래스의 데이터를 분해할 때 유용한 기능이다. 다음 예를 살펴보자. + + Elem(prefix:String, label:String, attrs:MetaData, scp:NamespaceBinding, children:Node*) + +이런 경우, 스칼라는 패턴의 가장 오른쪽 위치에 별 와일드카드 `_*`를 사용해 임의의 긴 시퀀스를 대신할 수 있도록 해준다. +다음 예제는 시퀀스의 앞 부분을 매칭하고 나머지는 변수 `rest`와 연결하는 패턴 매칭을 보여준다. + + 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 + } + } + } + +이전의 스칼라 버전과는 달리, 다음과 같은 이유로 스칼라는 더 이상 임의의 정규 표현식을 허용하지 않는다. + +###일반적인 `RegExp` 패턴은 일시적으로 스칼라에서 제외됐다### + +정확성에 문제를 발견했기 때문에 이 기능은 스칼라 언어에서 일시적으로 제외됐다. 사용자 커뮤니티에서 요청한다면 개선된 형태로 이를 다시 활성화할 수도 있다. + +정규 표현식 패턴이 XML 처리에 예상에 비해 그다지 유용하지 않다는 것이 우리의 의견이다. 실제 상황에서 XML를 처리해야하는 애플리케이션이라면 XPath가 더 나은 선택으로 보인다. 우리의 변환이나 정규 표현식 패턴에 드물지만 제거하기 어려운 난해한 버그가 있다는 점을 발견했을 때, 우리는 이 기회에 언어를 좀 더 단순하게 만들기로 결정했다. + +윤창석, 이한욱 옮김 \ No newline at end of file diff --git a/_ko/tour/sequence-comprehensions.md b/_ko/tour/sequence-comprehensions.md new file mode 100644 index 0000000000..3e64c37169 --- /dev/null +++ b/_ko/tour/sequence-comprehensions.md @@ -0,0 +1,62 @@ +--- +layout: tour +title: 시퀀스 컴프리헨션 + +discourse: false + +partof: scala-tour + +num: 16 +language: ko + +next-page: generic-classes +previous-page: extractor-objects +--- + +스칼라는 *시퀀스 컴프리헨션(sequence comprehensions)*을 표현하기 위한 간편한 문법을 제공한다. 컴프리헨션은 `for (enumerators) yield e`와 같은 형태를 가지며, 여기서 `enumerators`는 세미콜론으로 구분된 이뉴머레이터들을 뜻한다. *이뉴머레이터*는 새로운 변수를 정의하는 생성자이거나 필터이다. 컴프리헨션은 생성된 각각의 새로운 변수에 대해서 그 몸체인 `e`를 계산하여 그 값들의 시퀀스를 반환한다. + +예제 : + + 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)) + } + +위의 함수 안에 있는 for-구문은 `int` 타입의 변수 `i`를 정의하고 이어 `i`는 리스트 `List(from, from+1, ..., to - 1)`의 모든 값에 각각 대응된다. 가드 `if i % 2 == 0`는 홀수를 필터링하여 제외하여 (i만으로 이루어진) 몸체가 짝수에 대해서만 계산되도록 한다. 그 결과, 전체 for-구문은 짝수 리스트를 반환하게 된다. + +이 프로그램의 결과값은 다음과 같다 : + + List(0, 2, 4, 6, 8, 10, 12, 14, 16, 18) + +좀 더 복잡한 예제를 보자. 아래는 `0`과 `n-1`사이의 숫자로 이루어진 모든 순서쌍 중에서 그 합이 주어진 값 `v`와 같은 순서쌍을 계산하는 예제이다. + + 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)") + } + } + +이 예제는 컴프리헨션이 리스트에만 국한되지 않는다는 것을 보여준다. 리스트 대신에 이 프로그램에서는 이터레이터를 사용하였다. `withFilter`, `map`, `flatMap`기능을 지원하는 모든 데이터타입이 시퀀스 컴프리헨션에 이용될 수 있다. + +아래는 이 프로그램의 결과값이다: + + (13, 19) + (14, 18) + (15, 17) + (16, 16) + +시퀀스 컴프리헨션의 특별한 형태로 `Unit`을 반환하는 경우도 있다. (???어려워요. binding 뭐라고하지...) 이러한 시퀀스 익스프레션을 사용할 때에는 `yield` 키워드를 사용하지 않아야 한다. 아래 예제는 위의 예제와 같은 결과값을 출력하지만 `Unit`을 반환하는, 시퀀스 컴프리헨션의 특수형태를 사용하였다. + + 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/_ko/tour/singleton-objects.md b/_ko/tour/singleton-objects.md new file mode 100644 index 0000000000..cd1ddf0a57 --- /dev/null +++ b/_ko/tour/singleton-objects.md @@ -0,0 +1,77 @@ +--- +layout: tour +title: 싱글톤 객체 + +discourse: false + +partof: scala-tour + +num: 12 +language: ko + +next-page: regular-expression-patterns +previous-page: pattern-matching +--- + +[클래스](classes.html)의 각 인스턴스와 연관되지 않은 메서드와 값들은 싱글톤 객체에 속하며, `class`대신에 `object`를 사용해 표시된다. + +``` +package test + +object Blah { + def sum(l: List[Int]): Int = l.sum +} +``` + +위 `sum`메서드는 전역적으로 접근가능하고 참조될수 있으며, `test.Blah.sum`로 import될수 있다. + +싱글턴 객체는 직접 인스턴스화 될 수 없는 싱글턴 클래스를 정의하기 위한 축약형 같은 것이며, `object`의 정의 시점의 같은 이름을 가진 `val` 멤버 같은 것이다. 사실 `val`과 같이, 싱글턴 객체는 변칙적이긴 하지만 [트레잇](traits.html)이나 클래스의 멤버로서 정의될수 있다. + +하나의 싱글턴 객체는 클래스와 트레잇으로 확장할수 있다. 사실, [타입 파라미터](generic-classes.html)가 없는 [케이스 클래스](case-classes.html)는 기본적으로 같은 이름의 싱글턴 객체를 생성하며, 구현된 [`Function*`](http://www.scala-lang.org/api/current/scala/Function1.html)을 가진다. + +## 동반자(Companions) ## + +대부분의 싱글턴 객체는 독립적이지 않으며, 대신에 같은 이름의 클래스와 연관되어있다. 위에서 언급한 클래스의 "같은 이름의 싱글턴 객체"는 이 예이다. 이 현상이 발생할 때, 싱글턴 객체는 클래스의 *동반자 객체* 라고 하며, 그 클래스는 객체의 *동반자 클래스*라고 한다. + +[스칼라 문서](https://wiki.scala-lang.org/display/SW/Introduction)는 클래스와 그 동반자 사이의 이동을 위한 특별한 지원을 가지고 있다. 만약 큰 "C"나 "O" 원이 아래에서 위로 접힌 경계를 가진다면, 여러분은 동반자로 이동하기 위해 해당 원을 클릭할수 있다. + +하나의 클래스와 그 동반자 객체는 어떤 경우라도, 아래와 같이 *같은* 소스파일에 정의되어야 한다. + +```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)) +} +``` + +타입클래스 패턴을 따를때, 일반적으로 타입클래스 인스턴스들을 동반자 안에 정의된 `ipord`와 같은 [암시적 값들](implicit-parameters.html)로 생각한다. + +## 자바 프로그래머들이 주의할 점 ## + +`static`은 스칼라에서 키워드가 아니다. 대신에 class를 포함한 static일 수 있는 모든 멤버는 싱글턴 객체에 있어야 한다. 그것들은 부분적으로 또는 그룹 등등으로 import될수 있으며, 같은 문법으로 참조될수 있다. + +빈번하게, 자바프로그래머들은 그 인스턴스 멤버를 목적으로 구현 할때 `private`을 사용해 static 멤버를 정의한다. 이것들은 또한 동반자(companion)으로 이동되었다. 일반적인 패턴은 아래와 같이 동반자 객체(object)의 멤버들을 클래스 안으로 import하는 것이다. + +``` +class X { + import X._ + + def blah = foo +} + +object X { + private def foo = 42 +} +``` + +이것은 또 다른 특징을 설명한다. `private`의 문맥에서 클래스와 그 동반자는 친구다. `객체 X`는 `클래스 X`의 private 멤버들에 접근할수 있다. 하나의 멤버를 *정말로* private하게 만들고 싶다면 `private[this]`를 사용하라. + +For Java convenience, methods, including `var`s and `val`s, defined directly in a singleton object also have a static method defined in the companion class, called a *static forwarder*. Other members are accessible via the `X$.MODULE$` static field for `object X`. + +자바 편의를 위해서, `var`와 `val`의 것 모두, 싱글턴 객체에 정의된 메서드들은 *static forwarder* 라고 불리는 동반자 클래스안에 정의된 static메서드를 가진다. 다른 멤버들은 `객체 X`를 위한 static 필드 `X$.MODULE$`를 통해 접근할수 있다. + +만약 당신이 모든 것을 동반자 객체에 옮기고, 당신이 남겨놓은 모든것이 인스턴스화가 되길 바라지 않는 하나의 클래스라면, 간단하게 그 클래스를 삭제하라. Static forwarder는 여전히 생성된다. diff --git a/_ko/tour/tour-of-scala.md b/_ko/tour/tour-of-scala.md new file mode 100644 index 0000000000..6e2b1cfe56 --- /dev/null +++ b/_ko/tour/tour-of-scala.md @@ -0,0 +1,52 @@ +--- +layout: tour +title: 들어가며 + +discourse: false + +partof: scala-tour + +num: 1 +language: ko + +next-page: unified-types +--- + +스칼라는 정확하고 명쾌하며 타입 세이프한 방식으로 일반적인 유형의 프로그래밍 패턴을 표현하기 위해 설계된 새로운 다중 패러다임 프로그래밍 언어다. 스칼라는 객체지향과 함수형 언어를 자연스럽게 통합해준다. + +## 스칼라는 객체지향이다 ## +[모든 값이 객체](unified-types.html)라는 측면에서 스칼라는 순수 객체지향 언어다. 객체의 타입과 행위는 [클래스](classes.html)와 [트레잇](traits.html)으로 나타난다. 클래스는 서브클래스를 만들거나, 다중 상속을 깔끔하게 대체하는 유연한 [믹스인 기반 컴포지션](mixin-class-composition.html) 방법을 통해 확장된다. + +## 스칼라는 함수형이다 ## +또한, 스칼라는 [모든 함수가 값](unified-types.html)이라는 측면에서 함수형 언어다. 스칼라는 익명 함수를 위한 [경량 구문](anonymous-function-syntax.html)을 제공하고, [고차 함수](higher-order-functions.html)를 지원하며, 함수의 [중첩](nested-functions.html)을 허용하고, [커링](currying.html)을 지원한다. 스칼라의 [케이스 클래스](case-classes.html)와 케이스 클래스의 [패턴 매칭](pattern-matching.html) 빌트인 지원을 통해 여러 함수형 프로그래밍 언어에서 사용되는 대수 타입을 만들 수 있다. + +뿐만 아니라 스칼라의 패턴 매칭 개념은 [우측 무시 시퀀스 패턴](regular-expression-patterns.html)의 도움을 받아 자연스럽게 [XML 데이터의 처리](xml-processing.html)로 확장된다. 이런 맥락에서 [시퀀스 컴프리헨션](sequence-comprehensions.html)은 쿼리를 만들 때 유용하다. 이런 기능 때문에 스칼라는 웹 서비스와 같은 애플리케이션 개발에 있어서 이상적인 선택이 될 수 있다. + +## 스칼라는 정적 타입이다 ## +스칼라는 안전하고 일관성 있는 추상화를 정적으로 강제하는 풍부한 타입 시스템을 장착하고 있다. 특히 타입 시스템은 다음과 같은 사항을 지원한다. + +* [제네릭 클래스](generic-classes.html) +* [가변성 어노테이션](variances.html) +* [상위 타입 경계](upper-type-bounds.html)와 [하위 타입 경계](lower-type-bounds.html) +* 객체 멤버로써의 [내부 클래스](inner-classes.html)와 [추상 타입](abstract-types.html) +* [합성 타입](compound-types.html) +* [명시적으로 타입이 지정된 자기 참조](explicitly-typed-self-references.html) +* [뷰](views.html) +* [다형 메소드](polymorphic-methods.html) + +[로컬 타입 추론 방식](local-type-inference.html)은 사용자가 불필요한 타입 정보를 어노테이션해야 하는 불편함을 줄여준다. 이와 함께, 이런 기능은 프로그래밍 추상화를 안전하게 재사용하고 소프트웨어를 타입 세이프하게 확장하는 강력한 기반이 된다. + +## 스칼라는 확장성이 높다 ## +실제 개발 상황에선, 도메인 고유 애플리케이션의 개발에 도메인 고유 언어 확장이 필요할 때가 많다. 스칼라는 라이브러리의 형태로 새로운 언어 구성을 쉽고 자연스럽게 추가할 수 있도록 고유한 언어 기능 조합을 제공한다. + +* 어떤 메소드든 [중위나 후위 연산자](operators.html)로 사용될 수 있다. +* [클로저는 기대 타입에 따라 자동으로 생성된다](automatic-closures.html)(타입이 대상에 맞춰진다). + +두 기능을 함께 엮어서 사용하면 새로운 구문을 확장하거나 매크로 같은 메타 프로그래밍을 활용할 필요없이 명령문을 정의할 수 있도록 해준다. + +스칼라는 자바와 닷넷과 상호 호환된다. +스칼라는 잘 알려진 자바 2 런타임 환경(JRE)과 상호 호환되도록 설계됐다. 특히 객체지향의 주류인 자바 프로그래밍 언어와 굉장히 자연스럽게 상호작용한다. 스칼라는 자바와 같은 컴파일 모델(컴파일 분리, 동적 클래스 로딩)을 사용하며, 현재 사용되고 있는 수많은 높은 품질의 라이브러리를 그대로 활용할 수 있도록 해준다. + +다음 페이지로 이동하면 더 자세한 내용을 알아보게 된다. + +윤창석, 이한욱 옮김 diff --git a/_ko/tour/traits.md b/_ko/tour/traits.md new file mode 100644 index 0000000000..7972ed6754 --- /dev/null +++ b/_ko/tour/traits.md @@ -0,0 +1,48 @@ +--- +layout: tour +title: 트레잇 + +discourse: false + +partof: scala-tour + +num: 4 +language: ko + +next-page: mixin-class-composition +previous-page: classes +--- + +트레잇은 자바의 인터페이스와 유사하며, 지원되는 메소드의 서명을 지정해 객체의 타입을 정의하는 데 사용한다. 자바와는 달리, 스칼라에선 트레잇의 일부만 구현할 수도 있다. 다시 말해, 일부 메소드를 선택해 기본 구현 내용을 사전에 정의할 수 있다. 클래스와는 달리, 트레잇은 생성자 파라미터를 가질 수 없다. +다음의 예를 살펴보자. + + trait Similarity { + def isSimilar(x: Any): Boolean + def isNotSimilar(x: Any): Boolean = !isSimilar(x) + } + +이 트레잇은 `isSimilar`와 `isNotSimilar`라는 두 메소드로 구성된다. 메소드 `isSimilar`의 구현은 제공되지 않지만(자바의 abstract와 같다), 메소드 `isNotSimilar`에선 실제로 구현 내용을 정의하고 있다. 그 결과, 이 트레잇과 결합되는 클래스는 오직 `isSimilar`의 실제 구현만을 제공하면 된다. `isNotSimilar`의 행위는 트레잇에서 바로 상속받는다. 일반적으로 트레잇은 [믹스인 클래스 컴포지션](mixin-class-composition.html)을 통해 [클래스](classes.html)(또는 또 다른 트레잇)와 결합된다. + + 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) + println(p1.isNotSimilar(p2)) + println(p1.isNotSimilar(p3)) + println(p1.isNotSimilar(2)) + } + +이 프로그램의 실행 결과는 다음과 같다. + + false + true + true + +윤창석, 이한욱 옮김 diff --git a/_ko/tour/unified-types.md b/_ko/tour/unified-types.md new file mode 100644 index 0000000000..78d2aa37dc --- /dev/null +++ b/_ko/tour/unified-types.md @@ -0,0 +1,48 @@ +--- +layout: tour +title: 통합된 타입 + +discourse: false + +partof: scala-tour + +num: 2 +language: ko + +next-page: classes +previous-page: tour-of-scala +--- + +자바와는 달리, 스칼라에선 모든 값이 객체다(숫자 값과 함수를 포함해). 스칼라는 클래스 기반이기 때문에 모든 값은 클래스의 인스턴스다. 다음의 다이어그램은 클래스 계층구조를 나타낸다. + +![스칼라 타입 계층구조]({{ site.baseurl }}/resources/images/classhierarchy.img_assist_custom.png) + +## 스칼라 클래스 계층구조 ## + +모든 클래스의 슈퍼클래스인 `scala.Any`로부터 `scala.AnyVal`과 `scala.AnyRef`라는 두 서브클래스가 파생되며, 이 두 클래스는 각각 값 클래스와 참조 클래스를 대표한다. 모든 값 클래스는 미리 정의돼 있으며, 이는 자바와 같은 언어의 원시 타입에 해당한다. 다른 모든 클래스는 참조 타입으로 정의된다. 사용자 정의 클래스는 자동으로 참조 타입으로 정의되며, 이렇게 정의된 클래스는 항상 `scala.AnyRef`의 서브클래스(간접적)다. 스칼라의 모든 사용자 정의 클래스는 암시적으로 트레잇 `scala.ScalaObject`를 확장하고 있다. 스칼라가 실행되는 인프라(예, 자바 런타임 환경) 상의 클래스는 `scala.ScalaObject`를 확장하지 않는다. 스칼라를 자바 런타임 환경의 측면에 맞춰 생각해보자면 , `scala.AnyRef`는 `java.lang.Object`에 해당한다. 위의 다이어그램에는 값 클래스 사이의 암시적 변환을 의미하는 뷰도 함께 표현돼 있다. +다음은 다른 객체처럼 숫자와 문자와 불리언 값과 함수 또한 객체임을 보여주는 예제다. + + object UnifiedTypes extends App { + val set = new scala.collection.mutable.LinkedHashSet[Any] + set += "This is a string" // 문자열을 추가한다 + set += 732 // 숫자를 추가한다 + set += 'c' // 캐릭터를 추가한다 + set += true // 불리언 값을 추가한다 + set += main _ // 메인 함수를 추가한다 + val iter: Iterator[Any] = set.iterator + while (iter.hasNext) { + println(iter.next.toString()) + } + } + +이 프로그램은 `App`을 확장한 최상위 싱글턴 오브젝트로써 애플리케이션 `UnifiedTypes`를 선언했다. 애플리케이션에선 클래스 `LinkedHashSet[Any]`의 인스턴스를 가리키는 지역 변수 `set`을 정의했다. 프로그램은 이 집합에 여러 항목을 추가하는데, 해당 항목은 반드시 선언된 항목의 타입인 `Any`에 맞아야 한다. 마지막에는 모든 항목의 문자열 표현을 출력한다. + +다음은 이 프로그램의 실행 결과다. + + This is a string + 732 + c + true + + +윤창석, 이한욱 옮김 \ No newline at end of file diff --git a/_ko/tour/upper-type-bounds.md b/_ko/tour/upper-type-bounds.md new file mode 100644 index 0000000000..1060947f1b --- /dev/null +++ b/_ko/tour/upper-type-bounds.md @@ -0,0 +1,39 @@ +--- +layout: tour +title: 상위 타입 경계 + +discourse: false + +partof: scala-tour + +num: 19 +language: ko + +next-page: lower-type-bounds +previous-page: variances +--- + +스칼라에선 [타입 파라미터](generic-classes.html) 와 [추상 타입](abstract-types.html)의 타입 경계를 제한할 수 있다. 이런 타입 경계는 타입 변수의 콘크리트 값을 제한하고, 해당 타입의 멤버에 관한 정보를 추가할 수도 있다. _상위 타입 경계_ `T <: A`는 타입 변수 `T`를 선언하면서 서브타입 `A`를 참조하고 있다. 다음은 다형성 메소드 `findSimilar`의 구현을 위해 상위 타입 경계를 사용한 예제다. + + trait Similar { + def isSimilar(x: Any): Boolean + } + case class MyInt(x: Int) extends Similar { + def isSimilar(m: Any): Boolean = + m.isInstanceOf[MyInt] && + m.asInstanceOf[MyInt].x == x + } + object UpperBoundTest extends App { + def findSimilar[T <: Similar](e: T, xs: List[T]): Boolean = + if (xs.isEmpty) false + else if (e.isSimilar(xs.head)) true + else findSimilar[T](e, xs.tail) + val list: List[MyInt] = List(MyInt(1), MyInt(2), MyInt(3)) + println(findSimilar[MyInt](MyInt(4), list)) + println(findSimilar[MyInt](MyInt(2), list)) + } + +상위 타입 경계 어노테이션이 없었다면 메소드 `findSimilar`에서 `isSimilar` 메소드를 호출할 수 없었을 것이다. +하위 타입 경계의 사용법은 [이 곳](lower-type-bounds.html)에서 논의한다. + +윤창석, 이한욱 옮김 \ No newline at end of file diff --git a/_ko/tour/variances.md b/_ko/tour/variances.md new file mode 100644 index 0000000000..aa0c5102e2 --- /dev/null +++ b/_ko/tour/variances.md @@ -0,0 +1,46 @@ +--- +layout: tour +title: 가변성 + +discourse: false + +partof: scala-tour + +num: 18 +language: ko + +next-page: upper-type-bounds +previous-page: generic-classes +--- + +스칼라는 [제네릭 클래스](generic-classes.html)의 타입 파라미터에 관한 가변성 어노테이션을 지원한다. 자바 5(다른 이름은 [JDK 1.5](http://java.sun.com/j2se/1.5/))에선 추상화된 클래스가 사용될 때 클라이언트가 가변성 어노테이션을 결정하지만, 반면에 스칼라는 추상화된 클래스를 정의할 때 가변성 어노테이션을 추가할 수 있다. + +[제네릭 클래스](generic-classes.html)에 관한 페이지에선 변경 가능한 스택의 예제를 살펴보면서, 클래스 `Stack[T]`에서 정의한 타입은 타입 파라미터의 서브타입이 불변자여야 함을 설명했었다. 이는 추상화된 클래스의 재사용을 제한할 수 있다. 지금부턴 이런 제약이 없는 함수형(즉, 변경이 불가능한) 스택의 구현을 알아본다. 이 구현은 [다형성 메소드](polymorphic-methods.html), [하위 타입 경계](lower-type-bounds.html), 순가변 타입 파라미터 어노테이션 등의 중요 개념을 조합한 좀 더 어려운 예제임을 알아두자. 또한 [내부 클래스](inner-classes.html)를 사용해 명시적인 연결 없이도 스택의 항목을 서로 묶을 수 있도록 만들었다. + +```tut +class Stack[+T] { + def push[S >: T](elem: S): Stack[S] = new Stack[S] { + override def top: S = elem + override def pop: Stack[S] = Stack.this + override def toString: String = + elem.toString + " " + Stack.this.toString + } + def top: T = sys.error("no element on stack") + def pop: Stack[T] = sys.error("no element on stack") + override def toString: String = "" +} + +object VariancesTest extends App { + var s: Stack[Any] = new Stack().push("hello") + s = s.push(new Object()) + s = s.push(7) + println(s) +} +``` + +어노테이션 `+T`는 타입 순가변 위치에서만 사용할 수 있는 타입 `T`를 선언한다. 이와 유사하게, `-T`는 역가변 위치에서만 사용할 수 있는 `T`를 선언한다. 순가변 타입 파라미터의 경우, 타입 파라미터의 정의에 따라 서브타입과 순가변 관계를 형성한다. 즉, `T`가 `S`의 서브타입이라면 이 예제의 `Stack[T]`는 `Stack[S]`의 서브타입이다. `-`로 표시된 타입 파라미터 사이에는 이와는 반대의 관계가 맺어진다. + +스택의 예제에서 메소드 `push`를 정의하기 위해선 역가변 위치에 순가변 타입 파라미터 `T`를 사용해야만 한다. 스택의 서브타입이 순가변적여야 하기 때문에, 메소드 `push`의 파라미터 타입을 추상화하는 기법을 사용했다. 즉, 스택 항목의 타입인 `T`를 사용해, `T`가 `push`의 타입 변수 하위 경계로 설정된 다형성 메소드를 만들었다. 이는 `T` 선언에 따른 가변성이 순가변 타입 파라미터와 같도록 해준다. 이제 스택은 순가변적이지만, 지금까지 알아본 방법에 따르면 정수 스택에 문자열을 푸시하는 것과 같은 동작이 가능해진다. 이런 동작은 `Stack[Any]`라는 타입의 스택을 만드는데, 결국 정수 스택이 결과로 반환되길 기다리고 있는 상황에서 이 결과는 오류를 발생시킨다. 오류가 발생하지 않더라도, 항목의 타입이 더 일반화된 스택이 반환될 뿐이다. + + +윤창석, 이한욱 옮김 diff --git a/_ko/tutorials/scala-for-java-programmers.md b/_ko/tutorials/scala-for-java-programmers.md new file mode 100644 index 0000000000..4342d2e9c6 --- /dev/null +++ b/_ko/tutorials/scala-for-java-programmers.md @@ -0,0 +1,682 @@ +--- +layout: singlepage-overview +title: 자바 프로그래머를 위한 스칼라 튜토리얼 + +partof: scala-for-java-programmers + +discourse: false +language: ko +--- + +Michel Schinz, Philipp Haller 지음. +이희종 (heejong@gmail.com) 옮김. + + +## 시작하면서 + +이 문서는 Scala 언어와 그 컴파일러에 대해 간단히 소개한다. +어느 정도의 프로그래밍 경험이 있으며 Scala를 통해 무엇을 할 수 +있는지를 빠르게 배우고 싶은 사람들을 위해 만들어 졌다. +여기서는 독자가 객체 지향 프로그래밍, 특히 Java에 대한 지식을 +가지고 있다고 가정한다. + +## 첫 번째 예제 + +첫번째 예제로 흔히 쓰이는 *Hello world* 프로그램을 사용하자. +이 프로그램은 그다지 멋지지는 않지만 언어에 대한 많은 지식 없이도 +Scala 언어를 다루는데 필요한 도구들의 사용법을 쉽게 보여 줄 수 있다. +아래를 보자: + + object HelloWorld { + def main(args: Array[String]) { + println("Hello, world!") + } + } + +자바 프로그래머들은 이 프로그램의 구조가 익숙 할 것이다. +프로그램은 문자열 배열 타입의 명령줄 인자를 받는 이름이 `main`인 +함수 하나를 가지고 있다. 이 함수의 구현은 하나의 또 다른 함수 호출로 +이루어져 있는데 미리 정의 된 함수 `println`에 어디선가 많이 본 +바로 그 환영 메시지를 넘겨주어 호출 한다. `main` 함수는 값을 돌려주지 +않기 때문에 리턴 타입을 선언 할 필요가 없다. + +자바 프로그래머들에게 익숙하지 않은 부분은 `main` 함수를 감싸고 +있는 `object` 선언일 것이다. 이 선언은 **싱글턴 객체**를 생성하는데, +이는 하나의 인스턴스만을 가지는 클래스라 할 수 있다. 따라서 위의 선언은 +`HelloWorld`라는 클래스와 역시 `HelloWorld`라고 이름 +붙인 이 클래스의 인스턴스를 함께 정의 하는 것이다. 이 인스턴스는 처음 +사용 될 때에 필요에 따라 만들어 진다. + +똑똑한 독자들은 이미 눈치챘겠지만 위의 예제에서 `main` 함수는 +`static`이 아니다. Scala에는 정적 멤버(함수든 필드든)라는 개념이 +아얘 존재하지 않는다. 클래스의 일부로 정적 멤버를 정의하는 대신에 Scala +프로그래머들은 정적이기 원하는 멤버들을 싱글턴 객체안에 선언한다. + +### 예제를 컴파일 하기 + +예제를 컴파일 하기 위하여 Scala 컴파일러인 `scalac`를 사용한다. +`scalac`는 대부분의 컴파일러들과 비슷하게 동작한다. 소스파일과 필요에 +따라 몇개의 옵션들을 인자로 받아 한개 또는 여러개의 오브젝트 파일을 +생성한다. `scalac`가 생성하는 오브젝트 파일은 표준적인 Java 클래스 +파일이다. + +위의 예제 프로그램을 `HelloWorld.scala`라는 이름으로 저장했다면, +아래의 명령으로 컴파일 할 수 있다 (부등호 `>`는 쉘 프롬프트이므로 +함께 입력하지 말것) : + + > scalac HelloWorld.scala + +이제 현재 디렉토리에 몇개의 클래스 파일이 생성되는 것을 확인 할 수 있다. +그 중에 하나는 `HelloWorld.class`이며 `scala` 명령을 통해 바로 실행 +가능한 클래스를 포함하고 있다. 다음 장을 보자. + +### 예제를 실행하기 + +일단 컴파일 되면 Scala 프로그램은 `scala` 명령을 통해 실행 할 수 있다. +사용법은 Java 프로그램을 실행 할 때 사용하는 `java` 명령과 매우 비슷하며 +동일한 옵션을 사용 가능하다. 위의 예제는 아래의 명령으로 실행 할 수 있으며 +예상한대로의 결과가 나온다. + + > scala -classpath . HelloWorld + + Hello, world! + +## 자바와 함께 사용하기 + +Scala의 장점 중 하나는 Java 코드와 함께 사용하기 쉽다는 것이다. +사용하고 싶은 Java 클래스를 간단히 임포트 하면 되며, `java.lang` +패키지의 모든 클래스는 임포트 하지 않아도 기본적으로 사용 할 수 있다. + +아래는 Scala가 Java와 얼마나 잘 어울리는지를 보여주는 예제이다. +우리는 아래 예제에서 현재의 날짜를 구하여 특정 국가에서 사용하는 형식으로 +변환 할 것이다. 이를테면 프랑스(불어를 사용하는 스위스의 일부 지역도 +동일한 형식을 사용한다)라 하자. + +Java의 클래스 라이브러리는 `Date`와 `DateFormat`과 같은 +강력한 유틸리티 클래스를 가지고 있다. Scala는 Java와 자연스럽게 +서로를 호출 할 수 있으므로, 동일한 역할을 하는 Scala 클래스 라이브러리를 +구현하기 보다는 우리가 원하는 기능을 가진 Java 패키지를 간단히 임포트하여 +이용하자. + + import java.util.{Date, Locale} + import java.text.DateFormat + import java.text.DateFormat._ + + object FrenchDate { + def main(args: Array[String]) { + val now = new Date + val df = getDateInstance(LONG, Locale.FRANCE) + println(df format now) + } + } + +Scala의 임포트 구문은 Java의 그것과 매우 비슷해 보이지만 사실 좀 더 +강력하다. 위 예제의 첫번째 줄과 같이 중괄호를 사용하면 같은 패키지에서 +여러개의 클래스를 선택적으로 불러 올 수 있다. Scala 임포트 구문의 +또 한가지 특징은 패키지나 클래스에 속한 모든 이름들을 불러 올 경우 +별표(`*`) 대신 밑줄(`_`) 을 사용 한다는 것이다. 별표는 Scala에서 +합법적인 식별자(함수명 등에 사용 가능한)로 사용된다. 나중에 자세히 살펴 +볼 것이다. + +따라서 세번째 줄의 임포트 구문은 `DateFormat` 클래스의 모든 멤버를 +불러온다. 이렇게 함으로써 정적 함수 `getDateInstance`와 정적 필드 +`LONG`이 바로 사용 가능하게 된다. + +`main` 함수 안에서 처음 하는 일은 Java 라이브러리에 속한 +`Date` 클래스의 인스턴스를 생성하는 것이다. 이 인스턴스는 기본적으로 +현재의 날짜를 가지고 있다. 다음으로 이전에 불러온 정적 함수 +`getDateInstance`를 통해 날짜 형식을 결정하고, 프랑스에 맞춰진 +`DateFormat` 인스턴스를 사용하여 현재의 날짜를 출력한다. 이 +마지막 줄은 Scala 문법의 재미있는 특성을 보여준다. 오직 하나의 인자를 +갖는 함수는 마치 이항연산자 같은 문법으로 호출 가능하다. 이 이야기는 곧 +아래의 표현식이: + + df format now + +아래 표현식과 동일한 의미를 가진 다는 것이다. 그저 좀 더 간단하게 표현 +되었을 뿐이다. + + df.format(now) + +이러한 특성은 그저 별것 아닌 문법의 일부 인것 처럼 보이지만 여러 곳에서 +중요하게 사용 된다. 그중에 하나가 다음 장에 나와있다. + +이번 장에서는 Java와 Scala가 얼마나 자연스럽게 서로 녹아드는지에 대해 +배웠다. 이번 장에는 나타나지 않았지만, Scala 안에서 Java의 클래스들을 +상속받고 Java의 인터페이스들을 바로 구현하는 것도 가능하다. + +## 모든 것은 객체다 + +Scala는 순수한 객체지향적 언어이다. 이 말은 곧 숫자와 함수를 포함한 +**모든것**이 객체라는 것이다. 이러한 면에서 Scala는 Java와 다르다. +Java에서는 기본적인 타입(`boolean`이나 `int` 따위)과 참조 가능한 +타입이 분리되어 있으며, 함수를 값과 동일하게 다룰 수도 없다. + +### 숫자도 하나의 객체다 + +숫자는 객체이기 때문에 함수들을 포함하고 있다. 사실 아래와 같은 +표현식은: + + 1 + 2 * 3 / x + +오직 함수 호출로만 이루어져 있다. 우리가 이전 장에서 보았듯이, 위의 +표현식은 아래의 표현식과 동일하다. + + (1).+(((2).*(3))./(x)) + +위의 표현식처럼 `+`, `*` 등은 Scala에서 합법적인 식별자이다. + +위의 두번째 표현식에서 괄호는 꼭 필요하다. 왜냐하면 스칼라의 렉서(lexer)는 +토큰들에 대하여 가장 긴 부분을 찾는 방법을 사용하기 때문이다. 아래의 +표현식은: + + 1.+(2) + +세개(`1.`, `+`, `2`)의 토큰들로 분리된다. 이렇게 토큰들이 +분리되는 이유는 미리 정의되어 있는 유효한 토큰 중에 `1.`이 +`1`보다 길기 때문이다. 토큰 `1.`은 리터럴 `1.0`으로 +해석 되어 `Double` 타입이 된다. 실제로 우리는 `Int` 타입을 +의도 했음에도 말이다. 표현식을 아래와 같이 쓰면: + + (1).+(2) + +토큰 `1`이 `Double`로 해석 되는 것을 방지 할 수 있다. + +### 함수마저 객체다 + +Java 프로그래머들에게는 놀라운 일이겠지만 Scala에서는 함수도 +역시 객체이다. 따라서 함수에 함수를 인자로 넘기거나, 함수를 변수에 +저장하거나, 함수가 함수를 리턴하는 것도 가능하다. 이처럼 함수를 값과 +동일하게 다루는 것은 매우 흥미로운 프로그래밍 패러다임인 +**함수형 프로그래밍**의 핵심 요소 중 하나이다. + +함수를 값과 같이 다루는 것이 유용함을 보이기 위해 아주 간단한 예제를 +든다. 어떠한 행동을 매초 수행하는 타이머 함수를 생각해 보자. 수행 할 +행동을 어떻게 넘겨 주어야 할까? 논리적으로 생각한다면 함수를 넘겨 주어야 +한다. 함수를 전달하는 이런 종류의 상황은 많은 프로그래머들에게 익숙 할 +것이다. 바로 유저 인터페이스 코드에서 어떤 이벤트가 발생하였을 때 불릴 +콜백 함수를 등록하는 것 말이다. + +아래 프로그램에서 타이머 함수의 이름은 `oncePerSecond`이다. 이 함수는 +콜백 함수를 인자로 받는다. 인자로 받는 함수의 타입은 `() => Unit` 인데, +이 타입은 인자를 받지 않고 아무 것도 돌려주지 않는 모든 함수를 뜻한다 +(`Unit` 타입은 C/C++에서 `void`와 비슷하다). 이 프로그램의 메인 함수는 +이 타이머 함수를 화면에 문장을 출력하는 간단한 콜백함수를 인자로 호출한다. +결국 이 프로그램이 하는 일은 일초에 한번씩 "time flies like an arrow"를 +화면에 출력하는 것이 된다. + + object Timer { + def oncePerSecond(callback: () => Unit) { + while (true) { callback(); Thread sleep 1000 } + } + def timeFlies() { + println("time flies like an arrow...") + } + def main(args: Array[String]) { + oncePerSecond(timeFlies) + } + } + +우리는 문자열을 화면에 출력하기 위하여 Scala에 정의된 `println`을 사용 +하였다. 이 함수는 Java에서 흔히 사용하는 `System.out`에 정의된 것과 +다르다. + +#### 이름없는 함수 + +이 프로그램은 이해하기 쉽지만 조금 더 다듬을 수도 있다. +함수 `timeFlies`는 오직 함수 `oncePerSecond`에 인자로 +넘겨지기 위해 정의 되었다는 것에 주목하자. 이러한 한번만 사용되는 +함수에 이름을 붙여 준다는 것은 필요 없는 일일 수 있다. 더 행복한 +방법은 `oncePerSecond`에 함수가 전달 되는 그 순간 이 함수를 +생성하는 것이다. Scala에서 제공하는 **무명함수**를 사용하면 +된다. 무명함수란 말 그대로 이름이 없는 함수이다. 함수 `timeFlies` +대신에 무명함수를 사용한 새로운 버전의 타이머 프로그램은 아래와 같다: + + object TimerAnonymous { + def oncePerSecond(callback: () => Unit) { + while (true) { callback(); Thread sleep 1000 } + } + def main(args: Array[String]) { + oncePerSecond(() => + println("time flies like an arrow...")) + } + } + +`main` 함수 안에 오른쪽 화살표 `=>`가 있는 곳이 무명함수이다. +오른쪽 화살표는 함수의 인자와 함수의 내용을 분리 해주는 역할을 한다. 위 +예제에서 인자의 리스트는 비어있다. 화살표의 왼쪽을 보면 빈 괄호를 볼 수 +있다. 함수의 내용은 `timeFlies`와 일치한다. + +## 클래스에 대하여 + +지금까지 보았듯 Scala는 객체지향적 언어이며 클래스의 개념이 존재한다. +(어떤 객체지향 언어는 클래스의 개념이 존재하지 않는다. 당연하게도 +Scala는 이들에 속하지 않는다.) +Scala의 클래스 정의는 Java의 클래스 정의와 유사하다. 한가지 중요한 차이점은 +Scala 클래스의 경우 파라미터들을 가질 수 있다는 것인데 아래 복소수 예제에 +잘 나타나 있다: + + class Complex(real: Double, imaginary: Double) { + def re() = real + def im() = imaginary + } + +이 복소수 클래스는 두개의 인자를 받는다. 하나는 복소수의 실수 부분이고 +다른 하나는 복소수의 허수 부분에 해당하는 값이 된다. 이 인자들은 +`Complex` 클래스의 인스턴스를 생성 할 때 이처럼 반드시 전달 되어야 +한다: `new Complex(1.5, 2.3)`. 클래스는 `re`와 `im`라는 +두 함수를 가지고 있는데 각각의 함수를 통해 복소수를 구성하는 해당 부분의 +값을 얻을 수 있다. + +이 두 함수의 리턴타입은 명시적으로 나타나 있지 않다는 사실에 주목하자. +컴파일러는 이 함수들의 오른편을 보고 둘 다 `Double` 타입을 리턴 +한다고 자동으로 유추해 낸다. + +하지만 컴파일러가 언제나 이렇게 타입을 유추해 낼 수 있는 것은 아니다. +그리고 불행하게도 어떤 경우 이러한 타입 유추가 가능하고 어떤 경우 불가능 +한지에 관한 명확한 규칙도 존재하지 않는다. 일반적으로 이러한 상황은 +별 문제가 되지 않는다. 왜냐하면 명시적으로 주어지지 않은 타입정보를 +컴파일러가 자동으로 유추 해 낼 수 없는 경우 컴파일 시 에러가 발생하기 +때문이다. 초보 Scala 프로그래머들을 위한 한가지 방법은, 주변을 보고 쉽게 +타입을 유추 해 낼 수 있는 경우 일단 타입 선언을 생략하고 컴파일러가 받아 +들이는지 확인하는 것이다. 이렇게 몇번을 반복하고 나면 프로그래머는 언제 +타입을 생략해도 되고 언제 명시적으로 써주어야 하는지 감을 잡게 된다. + +### 인자 없는 함수 + +함수 `re`와 `im`의 사소한 문제는 그들을 호출하기 위해 항상 +뒤에 빈 괄호를 붙여 주어야 한다는 것이다. 아래를 보자: + + object ComplexNumbers { + def main(args: Array[String]) { + val c = new Complex(1.2, 3.4) + println("imaginary part: " + c.im()) + } + } + +실수 부분과 허수 부분에 접근 할 때에 마치 그들이 필드인 것 처럼 함수 +마지막에 빈 괄호를 붙이지 않을 수 있다면 더욱 좋겠다. 놀라지 마시라, +Scala는 이러한 기능을 완벽하게 제공한다. 그저 **인자를 제외**하고 +함수를 정의하면 된다. 이런 종류의 함수는 인자가 0개인 함수와는 다른데, +인자가 0개인 함수는 빈 괄호가 따라 붙는 반면 이 함수는 정의 할 때도 +사용 할 때도 이름 뒤에 괄호를 붙이지 않는다. 우리가 앞서 정의한 +`Complex` 클래스는 아래와 같이 다시 쓸 수 있다: + + class Complex(real: Double, imaginary: Double) { + def re = real + def im = imaginary + } + + +### 상속과 재정의 + +모든 Scala의 클래스들은 항상 상위 클래스로부터 상속된다. 만약 +`Complex` 예제 처럼 상위 클래스가 존재하지 않을 경우는 +묵시적으로 `scala.AnyRef`를 상속한다. + +Scala에서는 물론 상위 클래스에 정의된 함수를 오버라이드 하는 것도 +가능하다. 그러나 의도하지 않는 실수를 방지하기 위하여 다른 함수를 +오버라이드 하는 함수는 `override` 지시자를 꼭 적어주어야 한다. +예를 들면, 우리의 `Complex` 클래스에 대해 `Object`로 부터 +상속된 `toString` 함수를 재정의 하는 법은 아래와 같다: + + class Complex(real: Double, imaginary: Double) { + def re = real + def im = imaginary + override def toString() = + "" + re + (if (im < 0) "" else "+") + im + "i" + } + + +## 케이스 클래스 그리고 패턴 매칭 + +프로그램에 자주 등장하는 데이터 구조 중의 하나는 트리이다. +인터프리터와 컴파일러는 흔히 트리를 사용하여 내부 표현을 저장하고, +XML 문서도 트리이며, 레드블랙 트리와 같은 저장구조 들도 트리에 +기반을 두고 있다. + +작은 계산기 프로그램을 통해 Scala에서 이러한 트리들을 어떻게 +표현하고 다루는지에 대해 알아 보자. 이 프로그램의 목표는 더하기와 +상수인 정수 그리고 변수로 이루어진 간단한 산술 표현식을 다루는 것이다. +예를 들면, `1+2`나 `(x+x)+(7+y)` 같은 식들 말이다. + +처음으로, 우리는 해당 산술 표현식들을 어떻게 표현 할지 결정해야 한다. +가장 자연스러운 방법은 트리를 사용하는 것이다. 노드는 연산(여기서는 +덧셈)이 될 것이고, 리프는 값(여기서는 상수 또는 변수)가 되겠다. + +Java였다면 트리를 나타내기 위해, 트리에 대한 추상 상위 클래스와 +노드와 리프 각각에 대한 실제 하위 클래스들을 정의 했을 것이다. +함수형 언어였다면 같은 목적으로 대수적 데이터 타입을 사용 했을 것이다. +Scala는 **케이스 클래스**라 하는 이 둘 사이의 어디쯤에 놓여 질 수 +있는 장치를 제공한다. 우리 예제의 트리 타입을 정의하기 위해 이 장치가 +어떻게 사용 되는지 아래에서 실제적인 예를 보자: + + abstract class Tree + case class Sum(l: Tree, r: Tree) extends Tree + case class Var(n: String) extends Tree + case class Const(v: Int) extends Tree + +클래스 `Sum`, `Var` 그리고 `Const`가 케이스 클래스로 +선언되었다는 것은 이들이 여러가지 면에서 일반적인 클래스와 다르다는 +의미이다: + +- 인스턴스를 생성 할 때 `new` 키워드를 생략 할 수 있다. + 다른 말로, `new Const(5)`라 쓰는 대신 `Const(5)`라 쓰면 된다. +- 생성자 파라미터들에 대한 getter 함수가 자동으로 정의된다. 다른 말로, + 클래스 `Const`의 인스턴스 `c`에 있는 생성자 파라미터 `v`의 + 값은 `c.v`로 접근 가능하다. +- 함수 `equals`와 `hashCode`도 공짜로 제공된다. 이 함수들은 + 레퍼런스의 동일함 보다 **구조**의 동일함을 확인 하도록 구현되어 있다. + 다른 말로, 생성 된 곳이 다르더라도 각각의 생성자 파라미터 값이 같다면 + 같은 것으로 여긴다. +- 함수 `toString`에 대한 기본적 구현이 제공된다. 이 기본적인 + 구현은 "값이 생성 될 때"의 형태를 출력한다. 예를 들어 `x+1`의 트리 표현 + 을 출력 한다면 `Sum(Var(x),Const(1))`이 된다. +- 케이스 클래스들의 인스턴스는 **패턴 매칭**을 통해 따로 사용 될 + 수 있다. 자세한 내용은 아래에서 다룬다. + +산술 표현식을 나타낼 수 있는 데이터 타입을 정의 했으므로 이제 그것들을 +계산 할 연산자들을 정의 할 차례다. 일단, 어떤 **환경**안에서 표현식을 +계산 해주는 함수부터 시작하자. 환경은 각각의 변수마다 주어진 값들을 저장 +해 두는 곳이다. 컴퓨터에서 메모리의 역할과 비슷 하다고 생각하면 된다. +예를 들어, 변수 `x`에 `5`가 저장된 환경(`{ x -> 5 }`)에서 표현식 +`x+1`을 계산하면 결과로 `6`이 나온다. + +환경은 어떻게 표현하는게 좋을까? 간단히 생각하면, 해쉬 테이블 같은 +두 값을 묶어주는 데이터 구조를 사용 할 수 있겠다. 그러나 우리는 이러한 +데이터를 저장하는 목적으로 함수를 직접 사용 할 수도 있다! 가만 생각해 +보면 환경이라는 것은 변수명에서 값으로 가는 함수에 지나지 않는다. +위에서 사용한 환경 `{ x -> 5 }` 은 Scala로 간단히 아래와 같이 +쓴다: + + { case "x" => 5 } + +이 문법은 함수를 정의한다. 이 함수는 문자열 `"x"`가 인자로 들어 +왔을 때 정수 `5`를 돌려주고, 다른 모든 경우에 예외를 발생시키는 함수이다. + +계산하는 함수를 작성하기 전에 환경 타입에 이름을 붙여 주는 것이 좋겠다. +물론 항상 환경 타입으로 `String => Int`를 사용해도 되지만 보기 좋은 +이름을 붙이는 것은 프로그램을 더 읽기에 명료하고 변경에 유연하게 해 준다. +Scala에서는 아래와 같이 할 수 있다: + + type Environment = String => Int + +이제부터 타입 `Environment`는 `String`에서 `Int`로 가는 +함수 타입의 다른 이름이다. + +지금부터 계산하는 함수를 정의하자. 개념으로 따지면 매우 간단하다: +두 표현식의 합은 각 표현식의 값을 구하여 더한 것이다. 변수의 값은 +환경에서 바로 가져 올 수 있고, 상수의 값은 상수 자체이다. 이것을 +Scala로 나타내는 것은 어렵지 않다: + + def eval(t: Tree, env: Environment): Int = t match { + case Sum(l, r) => eval(l, env) + eval(r, env) + case Var(n) => env(n) + case Const(v) => v + } + +이 계산 함수는 트리 `t`에 대해 **패턴 매칭**을 수행함으로써 +동작한다. 위의 함수 정의는 직관적으로도 이해하기 쉽다: + +1. 처음으로 `t`가 `Sum`인지 확인한다. 만약 맞다면 왼쪽 + 서브트리를 새로운 변수 `l`에 오른쪽 서브트리를 새로운 변수 + `r`에 할당 한다. 그리고 화살표를 따라 화살표의 오른편으로 계산을 + 이어 나간다. 화살표의 오른편에서는 화살표의 왼편에서 할당된 변수 + `l`과 `r`을 사용 한다. +2. 첫번째 확인이 성공하지 못하면 트리는 `Sum`이 아니라는 + 이야기이다. 다음으로는 `t`가 `Var`인지 확인한다. 만약 + 맞다면 `Var` 노드 안에 포함된 이름을 변수 `n`에 할당한다. + 그리고 화살표의 오른쪽으로 진행한다. +3. 두번째 확인 역시 실패하면 `t`는 `Sum`도 `Var`도 + 아니라는 뜻이다. 이제는 `Const`에 대해 확인 해본다. 만약 + 맞다면 `Const` 노드 안의 값을 변수 `v`에 할당하고 화살표의 + 오른쪽으로 진행한다. +4. 마지막으로 모든 확인이 실패하면 패턴 매칭이 실패 했음을 알리는 + 예외가 발생하게 된다. 이러한 상황은 확인 한 것 외에 `Tree`의 + 하위 클래스가 더 존재 할 경우 일어난다. + +패턴 매칭의 기본적인 아이디어는 대상이 되는 값을 여러가지 관심있는 +패턴에 대해 순서대로 맞춰 본 후, 맞는 것이 있으면 맞은 값 중 관심 있는 +부분에 대해 새롭게 이름 붙이고, 그 이름 붙인 부분을 사용하는 어떠한 +작업을 진행하는 것이다. + +객체지향에 숙련된 프로그래머라면 왜 `eval`을 클래스 `Tree`와 +그 하위 클래스에 대한 **멤버 함수**로 정의하지 않았는지 궁금 할 것이다. +사실 그렇게 할 수도 있었다. Scala는 일반적인 클래스 처럼 케이스 클래스에 +대해서도 함수 정의를 허용한다. 패턴 매칭을 사용하느냐 멤버 함수를 +사용하느냐는 사용자의 취향에 달린 문제다. 하지만 확장성에 관해 시사하는 +중요한 점이 있다: + +- 멤버 함수를 사용하면 단지 `Tree`에 대한 하위 클래스를 새롭게 + 정의 함으로 새로운 노드를 추가하기 쉽다. 반면에 트리에 대한 새로운 + 연산을 추가하는 작업이 고되다. 새로운 연산을 추가하기 위해서는 + `Tree`의 모든 하위 클래스를 변경해야 하기 때문이다. +- 패턴 매칭을 사용하면 상황이 반대가 된다. 새로운 노드를 추가하려면 + 트리에 대해 패턴 매칭을 수행하는 모든 함수들을 새로운 노드도 고려하도록 + 변경해야 한다. 반면에 새로운 연산을 추가하는 것은 쉽다. 그냥 새로운 + 독립적인 함수를 만들면 된다. + +패턴 매칭에 대해 좀 더 알아보기 위해, 산술 표현식에 대한 또 다른 연산을 +정의 해보자. 이번 연산은 심볼 추출이다. 트리에서 우리가 원하는 특정 변수만 +1로 표시하는 일이다. 독자는 아래 규칙만 기억하면 된다: + +1. 더하기 표현식에서의 심볼 추출은 좌변과 우변의 심볼을 추출하여 더한 + 것과 같다. +2. 변수 `v`에 대한 심볼 추출은 `v`가 우리가 추출하기 원하는 심볼과 + 관련이 있다면 1이 되고 그 외의 경우 0이 된다. +3. 상수에 대한 심볼 추출 값은 0이다. + +이 규칙들은 거의 그대로 Scala 코드가 된다. + + def derive(t: Tree, v: String): Tree = t match { + case Sum(l, r) => Sum(derive(l, v), derive(r, v)) + case Var(n) if (v == n) => Const(1) + case _ => Const(0) + } + +위의 함수는 패턴 매칭에 관한 두 가지 새로운 기능을 소개한다. +첫 번째로, `case` 표현은 **가드**를 가질 수 있다. 가드란 +`if` 키워드 뒤에 오는 표현식을 뜻하는 말로 패턴 매칭에 추가적인 +조건을 부여한다. 가드가 참이 되지 않으면 패턴 매칭은 성공하지 못한다. +여기서는, 매칭 된 변수의 이름이 우리가 추출하는 심볼 `v`와 같을 +때만 상수 1을 리턴함을 보장하는 용도로 사용된다. 두 번째 새로운 기능은 +**와일드카드**이다. 밑줄 문자 `_`로 쓰며, 모든 값과 매치 되고 +따로 이름을 붙이지 않는다. + +매턴 매칭의 뛰어난 기능들을 모두 살펴보지는 못했지만, 문서를 너무 +지루하게 만들지 않기 위하여 이쯤에서 멈추기로 한다. 이제 위에서 정의한 +두 개의 예제 함수가 실제로 동작하는 모습을 보자. 산술 표현식 +`(x+x)+(7+y)`에 대해 몇가지의 연산을 실행하는 간단한 `main` 함수를 +만들기로 한다. 첫번째로 환경 `{ x -> 5, y -> 7 }`에서 +그 값을 계산 할 것이고, 다음으로 `x`와 `y`에 대한 심볼 추출을 수행 할 +것이다. + + def main(args: Array[String]) { + val exp: Tree = Sum(Sum(Var("x"),Var("x")),Sum(Const(7),Var("y"))) + val env: Environment = { case "x" => 5 case "y" => 7 } + println("Expression: " + exp) + println("Evaluation with x=5, y=7: " + eval(exp, env)) + println("Derivative relative to x:\n " + derive(exp, "x")) + println("Derivative relative to y:\n " + derive(exp, "y")) + } + +이 프로그램을 실행하면, 예상된 결과를 얻을 수 있다: + + Expression: Sum(Sum(Var(x),Var(x)),Sum(Const(7),Var(y))) + Evaluation with x=5, y=7: 24 + Derivative relative to x: + Sum(Sum(Const(1),Const(1)),Sum(Const(0),Const(0))) + Derivative relative to y: + Sum(Sum(Const(0),Const(0)),Sum(Const(0),Const(1))) + +출력을 살펴 보면 심볼 추출의 결과가 사용자에게 좀 복잡하다는 +생각이 든다. 패턴 매칭을 사용하여 이 결과를 단순화 하는 함수를 +정의하는 것은 재미있는 문제이다(생각보다 복잡하기도 하다). +독자들에게 연습문제로 남겨두겠다. + +## 트레잇에 대하여 + +Scala 클래스에서는 상위 클래스에서 코드를 상속 받는 것 뿐만이 아니라, +하나 또는 여러개의 **트레잇(trait)**에서 코드를 불러 올 수 있는 방법도 +있다. + +Java 프로그래머들이 트레잇을 이해하는 가장 쉬운 길은 코드를 가질 수 있는 +인터페이스라고 생각하는 것이다. Scala에서 어떤 클래스가 트레잇을 상속하면, +그 클래스는 트레잇의 인터페이스를 구현해야만 하고 동시에 트레잇이 가진 모든 +코드들을 가져오게 된다. + +트레잇의 유용함을 보이기 위해 객체들에 순서를 붙이는 고전적인 예제 하나를 +들어보기로 하자. 순서가 있는 객체들은 정렬문제 처럼 주로 그들 사이에 비교가 +필요 할 경우 유용하다. Java에서는 비교가능한 객체들이 `Comparable` +인터페이스를 구현하게 된다. Scala에서는 이 `Comparable`을 트레잇으로 +정의하여 더 나은 프로그램 디자인을 제공 할 수 있다. 여기서는 이를 +`Ord`라 부를 것이다. + +객체를 비교 할 때, 여섯개의 서로 다른 관계가 주로 사용 된다: 작다, +작거나 같다, 같다, 같지 않다, 크거나 같다, 크다. 하지만 이 여섯개를 +일일히 구현하는 것은 지루하고 의미 없는 일이 될 것이다. 게다가 이중 두 +가지 관계만 정의 되어도 나머지 네가지 관계를 계산 할 수 있지 않은가. +예를 들어 같다와 작다만 결정 할 수 있어도 나머지 관계의 참 거짓을 쉽게 +판단 할 수 있다. Scala에서는 이러한 논리들을 트레잇의 정의 안에 +우아하게 표현 해 낼 수 있다: + + trait Ord { + def < (that: Any): Boolean + def <=(that: Any): Boolean = (this < that) || (this == that) + def > (that: Any): Boolean = !(this <= that) + def >=(that: Any): Boolean = !(this < that) + } + +위의 정의는 Java의 `Comparable` 인터페이스와 같은 역할을 하는 +`Ord`라고 불리는 새로운 타입을 만든다. 이 새로운 타입에는 +세가지의 관계식이 기본적으로 구현이 되어 있으며 이 구현은 모두 하나의 +추상 함수를 사용하고 있다. 모든 객체에 대해 기본적으로 존재하는 같다와 +같지 않다에 대한 관계식은 빠져 있다. + +위에서 사용된 타입 `Any`는 Scala의 최상위 타입이다. Java의 +`Object` 타입과 같으나, `Int`, `Float`과 같은 기본 타입의 +상위 타입이라는 점에서 좀 더 일반화 된 버전이라 생각 할 수 있다. + +객체를 비교 가능하게 만들기 위해 정의해야 할 것은 같다와 작다 뿐이다. +나머지는 위의 `Ord` 트레잇을 삽입하여 처리한다. 하나의 예로 +그레고리력의 날짜를 나타내는 `Date` 클래스를 만들어 보자. +이 날짜는 정수인 날, 월, 년으로 구성 된다. 일단 아래처럼 만든다: + + class Date(y: Int, m: Int, d: Int) extends Ord { + def year = y + def month = m + def day = d + override def toString(): String = year + "-" + month + "-" + day + +여기서 중요한 부분은 클래스 이름과 파라미터 뒤에 따라오는 +`extends Ord` 선언이다. 이 선언은 `Date` 클래스가 `Ord` +트레잇을 상속함을 뜻한다. + +다음으로 `Object`에서 상속된 `equals` 함수를 재정의 하여 +각각의 일, 월, 년을 비교하여 같음을 올바르게 판단하도록 한다. +`equals`의 기본 정의는 쓸모가 없다. 왜냐하면 Java와 같이 +기본적인 `equals`는 물리적 주소를 비교하기 때문이다. 최종적인 +코드는 다음과 같다: + + override def equals(that: Any): Boolean = + that.isInstanceOf[Date] && { + val o = that.asInstanceOf[Date] + o.day == day && o.month == month && o.year == year + } + +이 함수는 미리 정의된 함수인 `isInstanceOf`와 `asInstanceOf`를 +사용한다. 첫번째 `isInstanceOf`는 Java의 `instanceof` 연산자와 +동일한 일을 한다. 함수가 호출 된 객체가 함수의 인자로 들어온 타입의 +인스턴스이면 참을 리턴한다. 두번째 `asInstanceOf`는 Java의 캐스트 +연산자와 동일하다. 호출 된 객체가 인자로 들어온 타입의 인스턴스이면 그렇게 +여겨지도록 변환하고 아니라면 `ClassCastException`을 발생시킨다. + +아래 마지막으로 정의된 함수는 작음을 판단하는 함수이다. 여기서는 +`error`라는 또 다른 미리 정의된 함수가 쓰였는데, 이 함수는 +주어진 에러 메시지와 함께 예외를 발생 시키는 역할을 한다. + + def <(that: Any): Boolean = { + if (!that.isInstanceOf[Date]) + error("cannot compare " + that + " and a Date") + + val o = that.asInstanceOf[Date] + (year < o.year) || + (year == o.year && (month < o.month || + (month == o.month && day < o.day))) + } + +이걸로 `Date` 클래스의 정의가 완성되었다. 이 클래스의 인스턴스는 +날짜로도 또는 비교가능한 어떤 객체로도 여겨질 수 있다. 이들은 위에서 +언급한 여섯가지 비교연산을 모두 가지고 있는데, `equals`와 `<`는 +`Date` 클래스의 정의 안에 직접 구현되어 있고 나머지는 `Ord` +트레잇에서 상속 받은 것이다. + +트레잇은 여기서 예로 든 경우 외에도 물론 다양하게 사용 될 수 있다. +하지만 다양한 경우들에 대하여 깊게 다루는 일은 이 문서의 범위 밖이다. + +## 제네릭함 + +이 튜토리얼에서 다룰 Scala의 마지막 특징은 제네릭함이다. Java +프로그래머들은 Java의 제네릭 지원이 부족하기 때문에 발생한 여러가지 +문제점들에 대해 잘 알고 있을 것이다. 이 문제점들은 Java 1.5에서 +다뤄졌다. + +제네릭함이란 코드를 타입에 대하여 파라미터화 할 수 있는 능력이다. +이해를 돕기 위해 하나의 예를 들어 보자. 연결 리스트 라이브러리를 작성하는 +프로그래머는 리스트의 원소 타입을 도대체 무엇으로 해야 할지 고민에 +빠지게 된다. 이 연결 리스트는 서로 다른 많은 상황에서 사용 될 수 있기 +때문에 원소의 타입이 반드시 `Int` 또는 반드시 `Double`이 될 +것이라 미리 결정하는 것은 불가능하다. 이렇게 결정해 두는 일은 완전히 +임의적이며 라이브러리의 사용에 있어 필요 이상의 심한 제약으로 작용 +한다. + +Java 프로그래머는 어쩔 수 없이 `Object`를 사용하곤 한다. +`Object`는 모든 객체의 상위 타입이기 때문이다. 하지만 이런 방법은 +이상적이지 않다. `int`, `long`, `float`등과 같은 +기본 타입에 대해 동작하지 않으며, 연결 리스트에서 원소를 가져 올 때마다 +많은 동적 타입 캐스트들을 프로그래머가 직접 삽입해 주어야 하기 때문이다. + +Scala는 이 문제를 해결하기 위한 제네릭 클래스와 제네릭 함수를 지원한다. +예제로 함께 살펴보자. 예제는 레퍼런스라는 간단한 저장구조 클래스이다. +이 클래스는 비어있거나 또는 어떤 타입의 객체를 가리키는 포인터가 된다. + + class Reference[T] { + private var contents: T = _ + def set(value: T) { contents = value } + def get: T = contents + } + +클래스 `Reference`는 타입 `T`에 대해 파라미터화 되어있다. +타입 `T`는 레퍼런스의 원소 타입이다. 이 타입은 클래스 내부 +여러 곳에서 나타나는데, `contents` 변수의 타입으로, `set` +함수의 인자 타입으로, 그리고 `get` 함수의 리턴 타입으로 사용 된다. + +위의 코드 샘플은 Scala에서 필드 변수를 만드는 내용이므로 따로 설명이 +필요 없다. 한가지 흥미로운 점이 있다면 변수의 초기값이 `_`로 주어져 +있다는 것인데, 여기서 `_`는 기본값을 뜻한다. 기본값은 수 타입에 +대해서 0, `Boolean` 타입에 대해서 `false`, `Unit` +타입에 대해 `()`, 그리고 모든 객체 타입에 대해 `null`이다. + +`Reference` 클래스를 사용하려면 타입 파라미터 `T`에 대해 적당한 +타입을 지정해 주어야 한다. 이 타입은 레퍼런스 안에 들어갈 원소의 +타입이 된다. 예를 들어, 정수 값을 저장 할 수 있는 레퍼런스를 생성하고 +사용하기 위해서는 다음과 같이 쓴다: + + object IntegerReference { + def main(args: Array[String]) { + val cell = new Reference[Int] + cell.set(13) + println("Reference contains the half of " + (cell.get * 2)) + } + } + +위 예제에서 보듯 `get` 함수의 리턴값을 정수처럼 사용하기 위해 +따로 캐스팅이 필요하지 않다. 여기서 정의된 레퍼런스는 정수를 포함하도록 +선언이 되어 있으므로 정수 외에 다른 것은 넣을 수 없다. + +## 마치며 + +우리는 지금까지 Scala 언어의 간략한 소개와 몇가지의 예제를 살펴 +보았다. 흥미가 생겼다면 *Scala By Example*도 함께 읽어보자. 더 수준 +높고 다양한 예제를 만날 수 있다. 필요 할 때마다 *Scala Language +Specification*을 참고하는 것도 좋다. diff --git a/_layouts/blog-detail.html b/_layouts/blog-detail.html new file mode 100644 index 0000000000..8e6419da3b --- /dev/null +++ b/_layouts/blog-detail.html @@ -0,0 +1,6 @@ +--- +layout: inner-page-parent +--- + + +{% include inner-page-blog-detail-main-content.html %} \ No newline at end of file diff --git a/_layouts/blog-list.html b/_layouts/blog-list.html new file mode 100644 index 0000000000..d06c97051a --- /dev/null +++ b/_layouts/blog-list.html @@ -0,0 +1,9 @@ +--- +layout: inner-page-parent-dropdown +--- + +{% if page.category %} + {% include blog-list.html category=page.category %} +{% else %} + {% include blog-list.html %} +{% endif %} diff --git a/_layouts/cheatsheet.html b/_layouts/cheatsheet.html index 26e4ac8287..3e6e9cd3ec 100644 --- a/_layouts/cheatsheet.html +++ b/_layouts/cheatsheet.html @@ -1,32 +1,36 @@ --- -layout: default +layout: inner-page-parent-dropdown --- -{% include cheatsheet-header.txt %} - -
      - {% include topbar.txt %} - -
      -

      {{ page.title }}

      -
      - -
      -
      -
      - -
      - {% include cheatsheet-sidebar.txt %} -
      - -
      - {{ content }} -
      - -
      - -
      -
      -
      -
      -{% include footer.txt %} +
      +
      +
      +
      + {{content}} + {% if page.languages %} + + {% elsif page.language %} + {% assign engPath = page.id | remove_first: "/" | remove_first: page.language | append: '.html' %} + {% assign engPg = site.overviews | where: 'partof', page.partof | first %} + {{ engPg.languages }} + + {% endif %} +
      +
      +
      +
      diff --git a/_layouts/contribute.html b/_layouts/contribute.html index 22b573af65..8cd5f886e5 100644 --- a/_layouts/contribute.html +++ b/_layouts/contribute.html @@ -1,33 +1,17 @@ --- -layout: default +layout: inner-page-no-masthead +includeTOC: true --- -{% include contributing-header.txt %} -
      - {% include topbar.txt %} - -
      -

      {{ page.title }}

      -
      -
      - -
      +
      {{ content }}
      - -
      - {% include contributing-toc.txt %} -
      -
      -
      - -{% include footer.txt %} diff --git a/_layouts/default.html b/_layouts/default.html deleted file mode 100644 index c81b3a18cb..0000000000 --- a/_layouts/default.html +++ /dev/null @@ -1,2 +0,0 @@ - -{{ content }} \ No newline at end of file diff --git a/_layouts/downloadpage.html b/_layouts/downloadpage.html new file mode 100644 index 0000000000..0849a3d013 --- /dev/null +++ b/_layouts/downloadpage.html @@ -0,0 +1,100 @@ +--- +layout: inner-page-parent +--- + +
      +
      +
      +
      +
      + {{page.release_version}} +
      + +
      +
      +

      Compared to other programming languages, installing Scala is a bit unusual. It's possible to "install" Scala in numerous ways.

      +
      +
      + +
      1
      +
      +

      First, make sure you have the Java 8 JDK installed.

      +

      java -version(Make sure you have version 1.8.)

      +
      +
      +
      +
      2
      +
      +

      Then, install Scala using:

      +
      +
      +
      +
      + or +
      + +

      Best if you prefer a full-featured IDE (recommended for beginners)

      +
      + + + Download intellij + +
        + {% include tutorial-list.html column=1 %} +
      +
      +
      +
      + +

      Best if you are familiar with the command line

      +
      + + + Download Sbt + +
        + {% include tutorial-list.html column=0 %} +
      +
      +
      +

      Scala is unusual because it is usually installed for each of your Scala projects rather than being installed system-wide. Both of the above options manage a specific Scala version per Scala project you create.

      + +

      Release Notes

      +

      For important changes, please consult the release notes.

      + +

      Software Requirements

      + + {{ page.requirements }} + + {{ content }} + + {% if page.show_resources == "true" %} + + {% include download-resource-list.html %} + + {% endif %} + +

      License

      +

      The Scala distribution is released under the 3-clause BSD license.

      +
      + +
      +
      +
      + + {% for step in site.data.downloads.stepOne %} + + {% endfor %} + + {% for intellijUrl in site.data.downloads.intellijUrls %} + + {% endfor %} + + {% for sbtUrl in site.data.downloads.sbtUrls %} + + {% endfor %} +
      \ No newline at end of file diff --git a/_layouts/events.html b/_layouts/events.html new file mode 100644 index 0000000000..4959010ad3 --- /dev/null +++ b/_layouts/events.html @@ -0,0 +1,13 @@ +--- +layout: inner-page-parent +--- + +{% include events-training-list-top.html collection=paginator.events %} +
      + {{content}} +
      + +
      + + {% include paginator.html urlPath="events" %} + \ No newline at end of file diff --git a/_layouts/frontpage.html b/_layouts/frontpage.html index cb1a4f3774..86a7694ae1 100644 --- a/_layouts/frontpage.html +++ b/_layouts/frontpage.html @@ -1,15 +1,386 @@ --- -layout: default --- -{% include frontpage-header.txt %} +{% include headertop.html %} +{% include headerbottom.html %} -
      - {% include topbar.txt %} + + + - {% include frontpage-content.txt %} -
      -
      +
      + +
      +
      +

      {{site.data.common.texts.scalaBackendsTitle}}

      +
        + {% for backend in page.scalaBackends %} +
      • + + {{backend.description}} + +
      • + {% unless forloop.last %}
      • {% endunless %} + {% endfor %} +
      +

      - {{site.data.common.texts.scalaBackendsMore}} -

      +
      +
      -{% include frontpage-footer.txt %} \ No newline at end of file + +
      +
      +
      +

      IDEs for Scala

      +
      + +
      +
      + + +
      +
      +
      +

      Scala in a Nutshell

      +
      +

      click the boxes below to see Scala in action!

      +
      +
      +
      +
      +
      + {% for scalaItem in site.scala_items %} + {% assign loopIndexMod = forloop.index | minus: 1 | modulo: 3 %} + + {% if loopIndexMod == 0 %} + {% assign codeSnippets = '' | split: ',' %} +
      + {% endif %} + {% assign codeSnippets = codeSnippets | push: scalaItem.content %} +
      +

      {{scalaItem.shortTitle}}

      +

      {{scalaItem.shortDescription}}

      +
      + {% if loopIndexMod == 2 or forloop.last %} +
      +
      + {% for snippet in codeSnippets %} +
      {{snippet}}
      + {% endfor %} +
      + {% endif %} + {% endfor %} +
      + +
      +
      + + + + + +
      +
      + {% include online-courses.html %} + {% include upcoming-training.html %} +
      +
      + + +
      +
      +
      +

      Upcoming Events

      +
      +
      + {% assign upcomingEvents = '' | split: ',' %} + {% capture now %}{{site.time | date: '%s' | plus: 0}}{% endcapture %} + {% for event in site.events %} + {% capture date %}{{event.date|date: '%s'|plus: 86400}}{% endcapture %} + {% if now <= date %} + {% assign upcomingEvents = upcomingEvents | push: event %} + {% endif %} + {% endfor %} + {% for event in upcomingEvents limit: 6 %} + + {% endfor %} +
      + +
      +
      + +
      +
      +
      +

      {{page.ecosystemTitle}}

      +

      {{page.ecosystemDescription}}

      +
      +
      +
      + + +
      +
      +

      The Scala Library Index

      +
      + + +
      +
      +
      +
      +
      +
      +
      +
      +
      + +
      +
      +
      +
      +

      What’s New

      +
      + {% assign firstPost = site.posts | first %} +
      +

      {{firstPost.post-type|upcase}}

      +

      {{firstPost.title}}

      + {{firstPost.date | date: "%A, %B %-d, %Y"}} +

      {{firstPost.content}}

      +
      +
      + +
      + +
      + +
      +
      +
      +

      Talk to us!

      +
      +
      +

      Mailing lists / forums

      + {% for forum in site.data.chats-forums.discourseForums %} + + {{forum.title}} +

      {{forum.title}}

      +

      {{forum.subtitle}}

      +
      + {% endfor %} +
      +
      +

      Real-time (topic-specialized) chat

      + {% assign modLimit = site.data.chats-forums.gitterChannels.size | modulo: 2 %} + {% capture channelLimit %} + {% if modLimit != 0 %} + {{site.data.chats-forums.gitterChannels.size | minus: 1}} + {% else %} + {{site.data.chats-forums.gitterChannels.size}} + {% endif %} + {% endcapture %} + {% for channel in site.data.chats-forums.gitterChannels limit: channelLimit %} + {% if forloop.first %} +
        + {% endif %} +
      • + + + {{channel.name}} + +
      • + + {% assign halfLength = forloop.length | divided_by: 2 | floor %} + {% if forloop.index == halfLength %} +
      +
        + {% endif %} + + {% if forloop.last %} +
      + {% endif %} + {% endfor %} +
      + {% if page.communities %} +
      +

      Communities

      +
        + {% for community in page.communities %} +
      • + {{community.name}} +
      • + {% endfor %} +
      +
      + {% endif %} + +
      +
      + + +{% include twitter-feed.html %} + + +
      + + +
      +
      +
      +

      {{site.data.common.texts.scalaMaintainersTitle}}

      +
      +
        + {% for maintainer in site.data.scala-supporters.maintainers %} +
      • {{maintainer.name}}
      • + {% endfor %} +
      +

      {{site.data.common.texts.scalaSupportersTitle}}

      +
      + {% for supporter in site.data.scala-supporters.supporters %} + {{supporter.name}} + {% endfor %} +
      +
      +
      +
      + +{% include footer.html %} \ No newline at end of file diff --git a/_layouts/glossary.html b/_layouts/glossary.html index 15ca5cd9f1..ea5d5dde0f 100644 --- a/_layouts/glossary.html +++ b/_layouts/glossary.html @@ -1,35 +1,17 @@ --- -layout: default +layout: inner-page-parent-dropdown +includeTOC: true --- -{% include glossary-header.txt %} +
      +
      +
      +
      + {{content}} +
      +
      -
      - {% include topbar.txt %} - -
      -

      {{ page.title }}

      -
      - -
      -
      -
      - -
      - {% include glossary-sidebar.txt %} -
      - -
      -
      Glossary from the definitive book on Scala, Programming in Scala.
      -
      - {{ content }} -
      -
      - -
      - -
      -
      -
      -
      -{% include footer.txt %} + + {% include sidebar-toc-glossary.html %} +
      +
      diff --git a/_layouts/guides-index.html b/_layouts/guides-index.html deleted file mode 100644 index 4e55de5d7a..0000000000 --- a/_layouts/guides-index.html +++ /dev/null @@ -1,43 +0,0 @@ ---- -layout: index ---- - -
      -
      - {% for pg in site.pages %} - {% if pg.layout == "guides-index" and pg.languages %} - {% assign languages = pg.languages %} - {% endif %} - {% endfor %} - - {% if page.language %} - {% capture intermediate %}{{ page.url | remove_first: page.language }}{% endcapture %} - {% capture rootIndexURL %}{{ intermediate | remove_first: '/' }}{% endcapture %} - {% else %} - {% assign rootIndexURL = page.url %} - {% endif %} - -
      - {{ content }} -
      - -
      - - - {% if languages %} -
        -
      • English
      • - {% for l in languages %} - {% assign lang = site.data.languages[l] %} -
      • {{lang.name}}
      • - {% endfor %} -
      - {% endif %} -
      -
      -
      diff --git a/_layouts/guides-thanks.html b/_layouts/guides-thanks.html deleted file mode 100644 index 3bfd721fbd..0000000000 --- a/_layouts/guides-thanks.html +++ /dev/null @@ -1,12 +0,0 @@ ---- -layout: index ---- - -
      -
      -
      - {{ content }} -
      - -
      -
      diff --git a/_layouts/index.html b/_layouts/index.html deleted file mode 100644 index 565fd11a28..0000000000 --- a/_layouts/index.html +++ /dev/null @@ -1,23 +0,0 @@ ---- -layout: default ---- - -{% include index-header.txt %} - -
      - {% include topbar.txt %} - -
      -

      {{ page.title }}

      -
      - -
      -
      -
      - {{ content }} -
      -
      -
      -
      -
      -{% include frontpage-footer.txt %} \ No newline at end of file diff --git a/_layouts/inner-page-community.html b/_layouts/inner-page-community.html new file mode 100644 index 0000000000..f5cdb3afdb --- /dev/null +++ b/_layouts/inner-page-community.html @@ -0,0 +1,9 @@ +--- +layout: inner-page-parent +--- + + +{% include masthead-community.html %} + + +{% include inner-page-main-content.html %} \ No newline at end of file diff --git a/_layouts/inner-page-documentation.html b/_layouts/inner-page-documentation.html new file mode 100644 index 0000000000..5c420b46c3 --- /dev/null +++ b/_layouts/inner-page-documentation.html @@ -0,0 +1,6 @@ +--- +layout: inner-page-parent +--- + + +{% include masthead-documentation.html %} diff --git a/_layouts/inner-page-no-masthead.html b/_layouts/inner-page-no-masthead.html new file mode 100644 index 0000000000..28c8fe1663 --- /dev/null +++ b/_layouts/inner-page-no-masthead.html @@ -0,0 +1,6 @@ +--- +layout: inner-page-parent +--- + + +{% include inner-page-main-content.html %} \ No newline at end of file diff --git a/_layouts/inner-page-parent-dropdown.html b/_layouts/inner-page-parent-dropdown.html new file mode 100644 index 0000000000..d2e5029f68 --- /dev/null +++ b/_layouts/inner-page-parent-dropdown.html @@ -0,0 +1,38 @@ +{% include headertop.html %} +{% include headerbottom.html %} + + + +{% include navbar-inner.html %} + +
      + + +
      +
      +
      +
      + {% if page.overview-name %} +
      {{ page.overview-name }}
      + {% elsif site.data.docnames[page.partof].name %} +
      {{ site.data.docnames[page.partof].name }}
      + {% else %} +
       
      + {% endif %} +

      {{ page.title }}

      +
      +
      +
      + Language + +
      +
      +
      +
      + + {% comment %}Specific content from child layouts{% endcomment %} + {{content}} + +
      + +{% include footer.html %} diff --git a/_layouts/inner-page-parent.html b/_layouts/inner-page-parent.html new file mode 100644 index 0000000000..8940ae4e54 --- /dev/null +++ b/_layouts/inner-page-parent.html @@ -0,0 +1,23 @@ + +{% include headertop.html %} +{% include headerbottom.html %} + + + +{% include navbar-inner.html %} + +
      + + +
      +
      +

      {{page.title}}

      +
      +
      + + {% comment %}Specific content from child layouts{% endcomment %} + {{content}} + +
      + +{% include footer.html %} diff --git a/_layouts/inner-page.html b/_layouts/inner-page.html new file mode 100644 index 0000000000..30d6366101 --- /dev/null +++ b/_layouts/inner-page.html @@ -0,0 +1,13 @@ +--- +layout: inner-page-parent +--- + +
      +
      +
      +
      + {{content}} +
      +
      +
      +
      diff --git a/_layouts/multipage-overview.html b/_layouts/multipage-overview.html new file mode 100644 index 0000000000..cf7c0b5a32 --- /dev/null +++ b/_layouts/multipage-overview.html @@ -0,0 +1,18 @@ +--- +layout: inner-page-parent-dropdown +includeTOC: true +includeCollectionTOC: true +--- + +
      +
      +
      +
      + {{content}} +
      +
      + + + {% include sidebar-toc-multipage-overview.html %} +
      +
      diff --git a/_layouts/news-coursera.html b/_layouts/news-coursera.html deleted file mode 100644 index 44b89ebe26..0000000000 --- a/_layouts/news-coursera.html +++ /dev/null @@ -1,21 +0,0 @@ ---- -layout: default ---- -{% include header-coursera.txt %} -{% include topbar.txt %} - -
      -
      - -
      {% if page.title %}

      {{ page.title }}

      {% else %}

      {{ site.title }}

      {% endif %}
      - -
      - {{ content }} - {% if page.discourse == true %}{% include discourse.html %}{% endif %} -
      - -
      -
      - -{% include footer.txt %} -{% include coursera-stats-js.txt %} diff --git a/_layouts/news.html b/_layouts/news.html deleted file mode 100644 index 95febb9c94..0000000000 --- a/_layouts/news.html +++ /dev/null @@ -1,25 +0,0 @@ ---- -layout: default ---- -{% include header.txt %} -{% include topbar.txt %} - -
      -
      - -
      {% if page.title %}

      {{ page.title }}

      {% else %}

      {{ site.title }}

      {% endif %}
      - -
      - {{ content }} - {% if page.discourse == true %}{% include discourse.html %}{% endif %} -
      - -
      - {% include toc.txt %} -
      - - -
      -
      - -{% include footer.txt %} diff --git a/_layouts/overview-large.html b/_layouts/overview-large.html deleted file mode 100644 index 3bc82af36f..0000000000 --- a/_layouts/overview-large.html +++ /dev/null @@ -1,52 +0,0 @@ ---- -layout: default ---- -{% include header.txt %} -{% include topbar.txt %} - -
      -
      - - {% if page.partof %}
      {{ page.partof | replace: '-',' ' }}
      {% endif %} -
      {% if page.title %}

      {{ page.title }}

      {% else %}

      {{ site.title }}

      {% endif %}
      - - {% for pg in site.pages %} - {% if pg.partof == page.partof and pg.languages %} - {% assign languages = pg.languages %} - {% endif %} - {% endfor %} - - {% if page.language %} - {% capture intermediate %}{{ page.url | remove_first: page.language }}{% endcapture %} - {% capture rootTutorialURL %}{{ intermediate | remove_first: '/' }}{% endcapture %} - {% else %} - {% assign rootTutorialURL = page.url %} - {% endif %} - -
      - {% if languages %} -
        -
      • English
      • - {% for l in languages %} - {% assign lang = site.data.languages[l] %} -
      • {{lang.name}}
      • - {% endfor %} -
      - {% endif %} -
      - -
      - {{ content }} - {% include pager.txt %} - {% if page.discourse == true %}{% include discourse.html %}{% endif %} -
      - -
      - {% include toc-large.txt %} -
      - - -
      -
      - -{% include footer.txt %} diff --git a/_layouts/overview.html b/_layouts/overview.html index 5befdb7d41..2d255c52bf 100644 --- a/_layouts/overview.html +++ b/_layouts/overview.html @@ -1,55 +1,27 @@ --- -layout: default +layout: inner-page-parent +includeTOC: true +includeCollectionTOC: true --- -{% include header.txt %} -{% include topbar.txt %} -
      -
      -
      {% if page.title %}

      {{ page.title }}

      {% else %}

      {{ site.title }}

      {% endif %}
      - - {% for pg in site.posts %} - {% if pg.overview == page.overview and pg.languages %} - {% assign languages = pg.languages %} - {% endif %} - {% endfor %} - - {% for pg in site.pages %} - {% if pg.overview == page.overview and pg.languages %} - {% assign languages = pg.languages %} - {% endif %} - {% endfor %} - - {% if page.language %} - {% capture intermediate %}{{ page.url | remove_first: page.language }}{% endcapture %} - {% capture rootTutorialURL %}{{ intermediate | remove_first: '/' }}{% endcapture %} - {% else %} - {% assign rootTutorialURL = page.url %} - {% endif %} - -
      - {% if languages %} -
        -
      • English
      • - {% for l in languages %} - {% assign lang = site.data.languages[l] %} -
      • {{lang.name}}
      • - {% endfor %} -
      - {% endif %} -
      - -
      - {{ content }} - {% if page.discourse == true %}{% include discourse.html %}{% endif %} -
      - -
      - {% include toc.txt %} -
      - - -
      -
      - -{% include footer.txt %} +{% for pg in site.posts %} + {% if pg.overview == page.overview and pg.languages %} + {% assign languages = pg.languages %} + {% endif %} +{% endfor %} + +{% for pg in site.pages %} + {% if pg.overview == page.overview and pg.languages %} + {% assign languages = pg.languages %} + {% endif %} +{% endfor %} + +{% if page.language %} + {% capture intermediate %}{{ page.url | remove_first: page.language }}{% endcapture %} + {% capture rootTutorialURL %}{{ intermediate | remove_first: '/' }}{% endcapture %} +{% else %} + {% assign rootTutorialURL = page.url %} +{% endif %} + + +{% include inner-page-main-content.html %} diff --git a/_layouts/overviews.html b/_layouts/overviews.html new file mode 100644 index 0000000000..888ffc2061 --- /dev/null +++ b/_layouts/overviews.html @@ -0,0 +1,13 @@ +--- +layout: inner-page-parent +--- + +
      +
      +
      +
      + {{content}} +
      +
      +
      +
      diff --git a/_layouts/page.html b/_layouts/page.html deleted file mode 100644 index 1f502c40f0..0000000000 --- a/_layouts/page.html +++ /dev/null @@ -1,33 +0,0 @@ ---- -layout: default ---- - -{% include contributing-header.txt %} - -
      - {% include topbar.txt %} - -
      -

      {{ page.title }}

      -
      - -
      -
      -
      - -
      - {{ content }} -
      - -
      - {% include gen-toc.txt %} -
      - -
      - -
      -
      -
      -
      - -{% include footer.txt %} diff --git a/_layouts/redirected.html b/_layouts/redirected.html deleted file mode 100644 index 2b5a672596..0000000000 --- a/_layouts/redirected.html +++ /dev/null @@ -1,16 +0,0 @@ ---- -layout: default ---- - - - - - - - - -

      Redirecting...

      - Click here if you are not redirected. - - - diff --git a/_layouts/search.html b/_layouts/search.html deleted file mode 100644 index e34d9b7250..0000000000 --- a/_layouts/search.html +++ /dev/null @@ -1,23 +0,0 @@ ---- -layout: default ---- - -{% include search-header.txt %} - -
      - {% include topbar.txt %} - -
      -

      {{ page.title }}

      -
      - -
      -
      -
      - {{ content }} -
      -
      -
      -
      -
      -{% include frontpage-footer.txt %} \ No newline at end of file diff --git a/_layouts/singlepage-overview.html b/_layouts/singlepage-overview.html new file mode 100644 index 0000000000..87c3279b92 --- /dev/null +++ b/_layouts/singlepage-overview.html @@ -0,0 +1,17 @@ +--- +layout: inner-page-parent-dropdown +includeTOC: true +--- + +
      +
      +
      +
      + {{content}} +
      +
      + + + {% include sidebar-toc-singlepage-overview.html %} +
      +
      diff --git a/_layouts/sip-landing.html b/_layouts/sip-landing.html deleted file mode 100644 index 76aed0ff0b..0000000000 --- a/_layouts/sip-landing.html +++ /dev/null @@ -1,28 +0,0 @@ ---- -layout: default ---- -{% include header.txt %} -{% include sips-topbar.txt %} - -
      -
      -
      - - {% if page.title %}

      {{ page.title }}

      {% else %}

      {{ site.title }}

      {% endif %} - -
      - {{ content }} - {% if page.discourse == true %}{% include discourse.html %}{% endif %} -
      - -
      - {% include allsids.txt %} -
      - - -
      -
      -
      -
      - -{% include footer.txt %} \ No newline at end of file diff --git a/_layouts/sip.html b/_layouts/sip.html index 0b2ca110c8..f91f543949 100644 --- a/_layouts/sip.html +++ b/_layouts/sip.html @@ -1,23 +1,34 @@ --- -layout: default +layout: inner-page-parent-dropdown +includeTOC: true --- -{% include header.txt %} -{% include sips-topbar.txt %} -
      - + diff --git a/_layouts/sips.html b/_layouts/sips.html new file mode 100644 index 0000000000..f54fb89149 --- /dev/null +++ b/_layouts/sips.html @@ -0,0 +1,48 @@ +--- +layout: inner-page-parent-dropdown +--- + +
      +
      +
      +
      + {{content}} +
      +
      + + +
      + +
      + +
      +
      diff --git a/_layouts/slip.html b/_layouts/slip.html deleted file mode 100644 index 5e824d4727..0000000000 --- a/_layouts/slip.html +++ /dev/null @@ -1,23 +0,0 @@ ---- -layout: default ---- -{% include header.txt %} -{% include sips-topbar.txt %} - -
      -
      - -
      - {% if page.title %}

      {{ page.title }}

      {% else %}

      {{ site.title }}

      {% endif %} - {{ content }} - {% if page.discourse == true %}{% include discourse.html %}{% endif %} -
      - -
      - {% include toc.txt %} -
      - -
      -
      - -{% include footer.txt %} diff --git a/_layouts/style-guide.html b/_layouts/style-guide.html new file mode 100644 index 0000000000..8028663a43 --- /dev/null +++ b/_layouts/style-guide.html @@ -0,0 +1,18 @@ +--- +layout: inner-page-parent-dropdown +includeCollectionTOC: true +includeTOC: true +--- + +
      +
      +
      +
      + {{content}} +
      +
      + + + {% include sidebar-toc-style.html %} +
      +
      diff --git a/_layouts/tour.html b/_layouts/tour.html new file mode 100644 index 0000000000..8ba00e63a8 --- /dev/null +++ b/_layouts/tour.html @@ -0,0 +1,18 @@ +--- +layout: inner-page-parent-dropdown +includeTOC: true +includeCollectionTOC: true +--- + +
      +
      +
      +
      + {{content}} +
      +
      + + + {% include sidebar-toc-tour-overview.html %} +
      +
      diff --git a/_layouts/training.html b/_layouts/training.html new file mode 100644 index 0000000000..3274549b11 --- /dev/null +++ b/_layouts/training.html @@ -0,0 +1,9 @@ +--- +layout: inner-page-parent +--- + +{% include events-training-list-top.html collection=paginator.trainings %} +
      + + {% include paginator.html urlPath="training" %} + \ No newline at end of file diff --git a/_layouts/tutorial.html b/_layouts/tutorial.html deleted file mode 100644 index bccff0988c..0000000000 --- a/_layouts/tutorial.html +++ /dev/null @@ -1,53 +0,0 @@ ---- -layout: post -permalink: /tutorials/:categories/:title/ ---- -{% include header.txt %} -
      - {% include topbar.txt %} - -
      -
      - -
      {% if page.title %}

      {{ page.title }}

      {% else %}

      {{ site.title }}

      {% endif %}
      - - {% for pg in site.categories.tour %} - {% if pg.languages %} - {% assign languages = pg.languages %} - {% endif %} - {% endfor %} - - {% if page.language %} - {% capture intermediate %}{{ page.url | remove_first: page.language }}{% endcapture %} - {% capture rootTutorialURL %}{{ intermediate | remove_first: '/' }}{% endcapture %} - {% else %} - {% assign rootTutorialURL = page.url %} - {% endif %} - -
      - {% if languages %} -
        -
      • English
      • - {% for l in languages %} - {% assign lang = site.data.languages[l] %} -
      • {{lang.name}}
      • - {% endfor %} -
      - {% endif %} -
      - -
      - {{ content }} - {% include pager.txt %} - {% if page.discourse == true %}{% include discourse.html %}{% endif %} -
      - -
      - {% include tutorial-toc.txt %} -
      - -
      -
      -
      -
      -{% include footer.txt %} diff --git a/tutorials/FAQ/breakout.md b/_overviews/FAQ/breakout.md similarity index 93% rename from tutorials/FAQ/breakout.md rename to _overviews/FAQ/breakout.md index 1a3f0153b2..0a39214d55 100644 --- a/tutorials/FAQ/breakout.md +++ b/_overviews/FAQ/breakout.md @@ -1,11 +1,14 @@ --- -layout: overview-large +layout: multipage-overview title: What is breakOut, and how does it work? discourse: true +overview-name: FAQ partof: FAQ + num: 5 +permalink: /tutorials/FAQ/:title.html --- You might have encountered some code like the one below, and wonder what is `breakOut`, and why is it being passed as parameter? @@ -16,11 +19,11 @@ You might have encountered some code like the one below, and wonder what is The answer is found on the definition of `map`: - def map[B, That](f : (A) => B)(implicit bf : CanBuildFrom[Repr, B, That]) : That + def map[B, That](f : (A) => B)(implicit bf : CanBuildFrom[Repr, B, That]) : That Note that it has two parameters. The first is your function and the second is an implicit. If you do not provide that implicit, Scala will choose the most -_specific_ one available. +_specific_ one available. ### About breakOut @@ -59,7 +62,7 @@ definition? Let's look at `CanBuildFrom`'s definition: - trait CanBuildFrom[-From, -Elem, +To] + trait CanBuildFrom[-From, -Elem, +To] extends AnyRef So `CanBuildFrom` is contra-variant on its first type parameter. Because @@ -110,16 +113,16 @@ parameterize the class. Let's see both definitions together: The class `TraversableLike` is defined as: - trait TraversableLike[+A, +Repr] + trait TraversableLike[+A, +Repr] extends HasNewBuilder[A, Repr] with AnyRef - def map[B, That](f : (A) => B)(implicit bf : CanBuildFrom[Repr, B, That]) : That + def map[B, That](f : (A) => B)(implicit bf : CanBuildFrom[Repr, B, That]) : That To understand where `A` and `Repr` come from, let's consider the definition of `Map` itself: - trait Map[A, +B] + trait Map[A, +B] extends Iterable[(A, B)] with Map[A, B] with MapLike[A, B, Map[A, B]] Because `TraversableLike` is inherited by all traits which extend `Map`, `A` @@ -127,19 +130,19 @@ and `Repr` could be inherited from any of them. The last one gets the preference, though. So, following the definition of the immutable `Map` and all the traits that connect it to `TraversableLike`, we have: - trait Map[A, +B] + trait Map[A, +B] extends Iterable[(A, B)] with Map[A, B] with MapLike[A, B, Map[A, B]] - trait MapLike[A, +B, +This <: MapLike[A, B, This] with Map[A, B]] + trait MapLike[A, +B, +This <: MapLike[A, B, This] with Map[A, B]] extends MapLike[A, B, This] - - trait MapLike[A, +B, +This <: MapLike[A, B, This] with Map[A, B]] + + trait MapLike[A, +B, +This <: MapLike[A, B, This] with Map[A, B]] extends PartialFunction[A, B] with IterableLike[(A, B), This] with Subtractable[A, This] - - trait IterableLike[+A, +Repr] + + trait IterableLike[+A, +Repr] extends Equals with TraversableLike[A, Repr] - trait TraversableLike[+A, +Repr] + trait TraversableLike[+A, +Repr] extends HasNewBuilder[A, Repr] with AnyRef If you pass the type parameters of `Map[Int, String]` all the way down the @@ -164,7 +167,7 @@ With that information, let's consider the other types. We can see that the type returned by the first `map` is `Map[Int,Int]`, and the second is `Iterable[String]`. Looking at `map`'s definition, it is easy to see -that these are the values of `That`. But where do they come from? +that these are the values of `That`. But where do they come from? If we look inside the companion objects of the classes involved, we see some implicit declarations providing them. On object `Map`: @@ -188,22 +191,22 @@ see how the types are inferred: val map : Map[Int,String] = List("London", "France").map(x => (x.length, x))(breakOut) - sealed abstract class List[+A] + sealed abstract class List[+A] extends LinearSeq[A] with Product with GenericTraversableTemplate[A, List] with LinearSeqLike[A, List[A]] - trait LinearSeqLike[+A, +Repr <: LinearSeqLike[A, Repr]] + trait LinearSeqLike[+A, +Repr <: LinearSeqLike[A, Repr]] extends SeqLike[A, Repr] - trait SeqLike[+A, +Repr] + trait SeqLike[+A, +Repr] extends IterableLike[A, Repr] - - trait IterableLike[+A, +Repr] + + trait IterableLike[+A, +Repr] extends Equals with TraversableLike[A, Repr] - - trait TraversableLike[+A, +Repr] + + trait TraversableLike[+A, +Repr] extends HasNewBuilder[A, Repr] with AnyRef - def map[B, That](f : (A) => B)(implicit bf : CanBuildFrom[Repr, B, That]) : That + def map[B, That](f : (A) => B)(implicit bf : CanBuildFrom[Repr, B, That]) : That The type of `List("London", "France")` is `List[String]`, so the types `A` and `Repr` defined on `TraversableLike` are: @@ -231,4 +234,3 @@ That means `breakOut` must, necessarily, return a type or subtype of This answer was originally submitted in response to [this question on Stack Overflow][1]. [1]: http://stackoverflow.com/q/1715681/53013 - diff --git a/tutorials/FAQ/chaining-implicits.md b/_overviews/FAQ/chaining-implicits.md similarity index 97% rename from tutorials/FAQ/chaining-implicits.md rename to _overviews/FAQ/chaining-implicits.md index a06d40e4f3..261d8b7bb7 100644 --- a/tutorials/FAQ/chaining-implicits.md +++ b/_overviews/FAQ/chaining-implicits.md @@ -1,11 +1,14 @@ --- -layout: overview-large +layout: multipage-overview title: How can I chain/nest implicit conversions? discourse: true +overview-name: FAQ partof: FAQ + num: 6 +permalink: /tutorials/FAQ/:title.html --- The enrich-my-library pattern allows one to seemingly add a method to a class by diff --git a/_overviews/FAQ/collections.md b/_overviews/FAQ/collections.md new file mode 100644 index 0000000000..97ad5520a1 --- /dev/null +++ b/_overviews/FAQ/collections.md @@ -0,0 +1,379 @@ +--- +layout: multipage-overview +title: How are the collections structured? Which one should I choose? + +discourse: true + +overview-name: FAQ +partof: FAQ + +num: 8 +permalink: /tutorials/FAQ/:title.html +--- +## Foreword + +There's a [2.8 collection walk-through][1] by Martin Odersky which should +probably be your first reference. It has been supplemented as well with +[architectural notes][2], which will be of particular interest to those who +want to design their own collections. + +The rest of this answer was written way before any such thing existed (in fact, +before 2.8.0 itself was released). + +You can find a paper about it as [Scala SID #3][3]. Other papers in that area +should be interesting as well to people interested in the differences between +Scala 2.7 and 2.8. + +I'll quote from the paper, selectively, and complement with some thoughts of +mine. There are also some images, generated by Matthias at decodified.com, and +the original SVG files can be found [here][4]. + +## The collection classes/traits themselves + +There are actually three hierarchies of traits for the collections: one for +mutable collections, one for immutable collections, and one which doesn't make +any assumptions about the collections. + +There's also a distinction between parallel, serial and maybe-parallel +collections, which was introduced with Scala 2.9. I'll talk about them in the +next section. The hierarchy described in this section refers _exclusively to +non-parallel collections_. + +The following image shows the non-specific hierarchy introduced with Scala 2.8: +![General collection hierarchy][5] + +All elements shown are traits. In the other two hierarchies there are also +classes directly inheriting the traits as well as classes which can be _viewed +as_ belonging in that hierarchy through implicit conversion to wrapper classes. +The legend for these graphs can be found after them. + +Graph for immutable hierarchy: + + +Graph for mutable hierarchy: + + +Legend: + +![Graph legend][8] + +Here's an abbreviated ASCII depiction of the collection hierarchy, for those who can't see the images. + + Traversable + | + | + Iterable + | + +------------------+--------------------+ + Map Set Seq + | | | + | +----+----+ +-----+------+ + Sorted Map SortedSet BitSet Buffer Vector LinearSeq + + +## Parallel Collections + +When Scala 2.9 introduced parallel collections, one of the design goals was to +make their use as seamless as possible. In the simplest terms, one can replace +a non-parallel (serial) collection with a parallel one, and instantly reap the +benefits. + +However, since all collections until then were serial, many algorithms using +them assumed and depended on the fact that they _were_ serial. Parallel +collections fed to the methods with such assumptions would fail. For this +reason, all the hierarchy described in the previous section _mandates serial +processing_. + +Two new hierarchies were created to support the parallel collections. + +The parallel collections hierarchy has the same names for traits, but preceded +with `Par`: `ParIterable`, `ParSeq`, `ParMap` and `ParSet`. Note that there is +no `ParTraversable`, since any collection supporting parallel access is capable +of supporting the stronger `ParIterable` trait. It doesn't have some of the +more specialized traits present in the serial hierarchy either. This whole +hierarchy is found under the directory `scala.collection.parallel`. + +The classes implementing parallel collections also differ, with `ParHashMap` +and `ParHashSet` for both mutable and immutable parallel collections, plus +`ParRange` and `ParVector` implementing `immutable.ParSeq` and `ParArray` +implementing `mutable.ParSeq`. + +Another hierarchy also exists that mirrors the traits of serial and parallel +collections, but with a prefix `Gen`: `GenTraversable`, `GenIterable`, +`GenSeq`, `GenMap` and `GenSet`. These traits are _parents_ to both parallel +and serial collections. This means that a method taking a `Seq` cannot receive +a parallel collection, but a method taking a `GenSeq` is expected to work with +both serial and parallel collections. + +Given the way these hierarchies were structured, code written for Scala 2.8 was +fully compatible with Scala 2.9, and demanded serial behavior. Without being +rewritten, it cannot take advantage of parallel collections, but the changes +required are very small. + +### Using Parallel Collections + +Any collection can be converted into a parallel one by calling the method `par` +on it. Likewise, any collection can be converted into a serial one by calling +the method `seq` on it. + +If the collection was already of the type requested (parallel or serial), no +conversion will take place. If one calls `seq` on a parallel collection or +`par` on a serial collection, however, a new collection with the requested +characteristic will be generated. + +Do not confuse `seq`, which turns a collection into a non-parallel collection, +with `toSeq`, which returns a `Seq` created from the elements of the +collection. Calling `toSeq` on a parallel collection will return a `ParSeq`, +not a serial collection. + +## The Main Traits + +While there are many implementing classes and subtraits, there are some basic +traits in the hierarchy, each of which providing more methods or more specific +guarantees, but reducing the number of classes that could implement them. + +In the following subsections, I'll give a brief description of the main traits +and the idea behind them. + +### Trait TraversableOnce + +This trait is pretty much like trait `Traversable` described below, but with +the limitation that you can only use it _once_. That is, any methods called on +a `TraversableOnce` _may_ render it unusable. + +This limitation makes it possible for the same methods to be shared between the +collections and `Iterator`. This makes it possible for a method that works with +an `Iterator` but not using `Iterator`-specific methods to actually be able to +work with any collection at all, plus iterators, if rewritten to accept +`TraversableOnce`. + +Because `TraversableOnce` unifies collections and iterators, and iterators are +not considered collections, it does not appear in the previous graphs, which +concern themselves only with collections. + +### Trait Traversable + +At the top of the _collection_ hierarchy is trait `Traversable`. Its only +abstract operation is + + def foreach[U](f: Elem => U) + +The operation is meant to traverse all elements of the collection, and apply +the given operation f to each element. The application is done for its side +effect only; in fact any function result of f is discarded by foreach. + +Traversible objects can be finite or infinite. An example of an infinite +traversable object is the stream of natural numbers `Stream.from(0)`. The +method `hasDefiniteSize` indicates whether a collection is possibly infinite. +If `hasDefiniteSize` returns true, the collection is certainly finite. If it +returns false, the collection has not been not fully elaborated yet, so it +might be infinite or finite. + +This class defines methods which can be efficiently implemented in terms of +`foreach` (over 40 of them). + +### Trait Iterable + +This trait declares an abstract method `iterator` that returns an iterator that +yields all the collection’s elements one by one. The `foreach` method in +`Iterable` is implemented in terms of `iterator`. Subclasses of `Iterable` +often override foreach with a direct implementation for efficiency. + +Class `Iterable` also adds some less-often used methods to `Traversable`, which +can be implemented efficiently only if an `iterator` is available. They are +summarized below. + + xs.iterator An iterator that yields every element in xs, in the same order as foreach traverses elements. + xs takeRight n A collection consisting of the last n elements of xs (or, some arbitrary n elements, if no order is defined). + xs dropRight n The rest of the collection except xs takeRight n. + xs sameElements ys A test whether xs and ys contain the same elements in the same order + +### Seq, Set and Map + +After `Iterable` there come three base traits which inherit from it: `Seq`, +`Set`, and `Map`. All three have an `apply` method and all three implement the +`PartialFunction` trait, but the meaning of `apply` is different in each case. + +I trust the meaning of `Seq`, `Set` and `Map` is intuitive. After them, the +classes break up in specific implementations that offer particular guarantees +with regards to performance, and the methods it makes available as a result of +it. Also available are some traits with further refinements, such as +`LinearSeq`, `IndexedSeq` and `SortedSet`. + +## Complete Overview + +### Base Classes and Traits + +* `TraversableOnce` -- All methods and behavior common to collections and iterators. + + * `Traversable` -- Basic collection class. Can be implemented just with `foreach`. + + * `TraversableProxy` -- Proxy for a `Traversable`. Just point `self` to the real collection. + * `TraversableView` -- A Traversable with some non-strict methods. + * `TraversableForwarder` -- Forwards most methods to `underlying`, except `toString`, `hashCode`, `equals`, `stringPrefix`, `newBuilder`, `view` and all calls creating a new iterable object of the same kind. + * `mutable.Traversable` and `immutable.Traversable` -- same thing as `Traversable`, but restricting the collection type. + * Other special-cases `Iterable` classes, such as `MetaData`, exists. + * `Iterable` -- A collection for which an `Iterator` can be created (through `iterator`). + * `IterableProxy`, `IterableView`, `mutable` and `immutable.Iterable`. + + * `Iterator` -- A trait which is not descendant of `Traversable`. Define `next` and `hasNext`. + * `CountedIterator` -- An `Iterator` defining `count`, which returns the elements seen so far. + * `BufferedIterator` -- Defines `head`, which returns the next element without consuming it. + * Other special-cases `Iterator` classes, such as `Source`, exists. + +### The Sequences + +* `Seq` -- A sequence of elements. One assumes a well-defined size and element repetition. Extends `PartialFunction` as well. + + * `IndexedSeq` -- Sequences that support O(1) element access and O(1) length computation. + * `IndexedSeqView` + * `immutable.PagedSeq` -- An implementation of `IndexedSeq` where the elements are produced on-demand by a function passed through the constructor. + * `immutable.IndexedSeq` + + * `immutable.Range` -- A delimited sequence of integers, closed on the lower end, open on the high end, and with a step. + * `immutable.Range.Inclusive` -- A `Range` closed on the high end as well. + * `immutable.NumericRange` -- A more generic version of `Range` which works with any `Integral`. + * `immutable.NumericRange.Inclusive`, `immutable.NumericRange.Exclusive`. + * `immutable.WrappedString`, `immutable.RichString` -- Wrappers which enables seeing a `String` as a `Seq[Char]`, while still preserving the `String` methods. I'm not sure what the difference between them is. + + * `mutable.IndexedSeq` + * `mutable.GenericArray` -- An `Seq`-based array-like structure. Note that the "class" `Array` is Java's `Array`, which is more of a memory storage method than a class. + * `mutable.ResizableArray` -- Internal class used by classes based on resizable arrays. + * `mutable.PriorityQueue`, `mutable.SynchronizedPriorityQueue` -- Classes implementing prioritized queues -- queues where the elements are dequeued according to an `Ordering` first, and order of queueing last. + * `mutable.PriorityQueueProxy` -- an abstract `Proxy` for a `PriorityQueue`. + + * `LinearSeq` -- A trait for linear sequences, with efficient time for `isEmpty`, `head` and `tail`. + + * `immutable.LinearSeq` + * `immutable.List` -- An immutable, singlely-linked, list implementation. + * `immutable.Stream` -- A lazy-list. Its elements are only computed on-demand, but memoized (kept in memory) afterwards. It can be theoretically infinite. + * `mutable.LinearSeq` + * `mutable.DoublyLinkedList` -- A list with mutable `prev`, `head` (`elem`) and `tail` (`next`). + * `mutable.LinkedList` -- A list with mutable `head` (`elem`) and `tail` (`next`). + * `mutable.MutableList` -- A class used internally to implement classes based on mutable lists. + * `mutable.Queue`, `mutable.QueueProxy` -- A data structure optimized for FIFO (First-In, First-Out) operations. + * `mutable.QueueProxy` -- A `Proxy` for a `mutable.Queue`. + + * `SeqProxy`, `SeqView`, `SeqForwarder` + + * `immutable.Seq` + + * `immutable.Queue` -- A class implementing a FIFO-optimized (First-In, First-Out) data structure. There is no common superclass of both `mutable` and `immutable` queues. + * `immutable.Stack` -- A class implementing a LIFO-optimized (Last-In, First-Out) data structure. There is no common superclass of both `mutable` `immutable` stacks. + * `immutable.Vector` -- ? + * `scala.xml.NodeSeq` -- A specialized XML class which extends `immutable.Seq`. + * `immutable.IndexedSeq` -- As seen above. + * `immutable.LinearSeq` -- As seen above. + + * `mutable.ArrayStack` -- A class implementing a LIFO-optimized data structure using arrays. Supposedly significantly faster than a normal stack. + * `mutable.Stack`, `mutable.SynchronizedStack` -- Classes implementing a LIFO-optimized data structure. + * `mutable.StackProxy` -- A `Proxy` for a `mutable.Stack`.. + * `mutable.Seq` + + * `mutable.Buffer` -- Sequence of elements which can be changed by appending, prepending or inserting new members. + * `mutable.ArrayBuffer` -- An implementation of the `mutable.Buffer` class, with constant amortized time for the append, update and random access operations. It has some specialized subclasses, such as `NodeBuffer`. + * `mutable.BufferProxy`, `mutable.SynchronizedBuffer`. + * `mutable.ListBuffer` -- A buffer backed by a list. It provides constant time append and prepend, with most other operations being linear. + * `mutable.ObservableBuffer` -- A *mixin* trait which, when mixed to a `Buffer`, provides notification events through a `Publisher` interfaces. + * `mutable.IndexedSeq` -- As seen above. + * `mutable.LinearSeq` -- As seen above. + +### The Sets + +* `Set` -- A set is a collection that includes at most one of any object. + + * `BitSet` -- A set of integers stored as a bitset. + * `immutable.BitSet` + * `mutable.BitSet` + + * `SortedSet` -- A set whose elements are ordered. + * `immutable.SortedSet` + * `immutable.TreeSet` -- An implementation of a `SortedSet` based on a tree. + + * `SetProxy` -- A `Proxy` for a `Set`. + + * `immutable.Set` + * `immutable.HashSet` -- An implementation of `Set` based on element hashing. + * `immutable.ListSet` -- An implementation of `Set` based on lists. + * Additional set classes exists to provide optimized implementations for sets from 0 to 4 elements. + * `immutable.SetProxy` -- A `Proxy` for an immutable `Set`. + + * `mutable.Set` + * `mutable.HashSet` -- An implementation of `Set` based on element hashing. + * `mutable.ImmutableSetAdaptor` -- A class implementing a mutable `Set` from an immutable `Set`. + * `LinkedHashSet` -- An implementation of `Set` based on lists. + * `ObservableSet` -- A *mixin* trait which, when mixed with a `Set`, provides notification events through a `Publisher` interface. + * `SetProxy` -- A `Proxy` for a `Set`. + * `SynchronizedSet` -- A *mixin* trait which, when mixed with a `Set`, provides notification events through a `Publisher` interface. + +### The Maps + +* `Map` -- An `Iterable` of `Tuple2`, which also provides methods for retrieving a value (the second element of the tuple) given a key (the first element of the tuple). Extends `PartialFunction` as well. + * `MapProxy` -- A `Proxy` for a `Map`. + * `DefaultMap` -- A trait implementing some of `Map`'s abstract methods. + * `SortedMap` -- A `Map` whose keys are sorted. + * `immutable.SortMap` + * `immutable.TreeMap` -- A class implementing `immutable.SortedMap`. + * `immutable.Map` + * `immutable.MapProxy` + * `immutable.HashMap` -- A class implementing `immutable.Map` through key hashing. + * `immutable.IntMap` -- A class implementing `immutable.Map` specialized for `Int` keys. Uses a tree based on the binary digits of the keys. + * `immutable.ListMap` -- A class implementing `immutable.Map` through lists. + * `immutable.LongMap` -- A class implementing `immutable.Map` specialized for `Long` keys. See `IntMap`. + * There are additional classes optimized for an specific number of elements. + * `mutable.Map` + * `mutable.HashMap` -- A class implementing `mutable.Map` through key hashing. + * `mutable.ImmutableMapAdaptor` -- A class implementing a `mutable.Map` from an existing `immutable.Map`. + * `mutable.LinkedHashMap` -- ? + * `mutable.ListMap` -- A class implementing `mutable.Map` through lists. + * `mutable.MultiMap` -- A class accepting more than one distinct value for each key. + * `mutable.ObservableMap` -- A *mixin* which, when mixed with a `Map`, publishes events to observers through a `Publisher` interface. + * `mutable.OpenHashMap` -- A class based on an open hashing algorithm. + * `mutable.SynchronizedMap` -- A *mixin* which should be mixed with a `Map` to provide a version of it with synchronized methods. + * `mutable.MapProxy`. + +## Bonus Questions + +* Why the Like classes exist (e.g. TraversableLike)? + +This was done to achieve maximum code reuse. The concrete *generic* +implementation for classes with a certain structure (a traversable, a map, etc) +is done in the Like classes. The classes intended for general consumption, +then, override selected methods that can be optmized. + +* What the companion methods are for (e.g. List.companion)? + +The builder for the classes, ie, the object which knows how to create instances +of that class in a way that can be used by methods like `map`, is created by a +method in the companion object. So, in order to build an object of type X, I +need to get that builder from the companion object of X. Unfortunately, there +is no way, in Scala, to get from class X to object X. Because of that, there is +a method defined in each instance of X, `companion`, which returns the +companion object of class X. + +While there might be some use for such method in normal programs, its target is +enabling code reuse in the collection library. + +* How I know what implicit objects are in scope at a given point? + +You aren't supposed to care about that. They are implicit precisely so that you +don't need to figure out how to make it work. + +These implicits exists to enable the methods on the collections to be defined +on parent classes but still return a collection of the same type. For example, +the `map` method is defined on `TraversableLike`, but if you used on a `List` +you'll get a `List` back. + +This answer was originally submitted in response to [this question][9] on Stack +Overflow. + + + [1]: http://docs.scala-lang.org/overviews/collections/introduction.html + [2]: http://docs.scala-lang.org/overviews/core/architecture-of-scala-collections.html + [3]: http://www.scala-lang.org/sid/3 + [4]: https://github.com/sirthias/scala-collections-charts/downloads + [5]: http://i.stack.imgur.com/bSVyA.png + [6]: http://i.stack.imgur.com/2fjoA.png + [7]: http://i.stack.imgur.com/Dsptl.png + [8]: http://i.stack.imgur.com/szWUr.png + [9]: http://stackoverflow.com/q/1722137/53013 diff --git a/tutorials/FAQ/context-bounds.md b/_overviews/FAQ/context-bounds.md similarity index 97% rename from tutorials/FAQ/context-bounds.md rename to _overviews/FAQ/context-bounds.md index ff3dee06f1..fa0beaaaa6 100644 --- a/tutorials/FAQ/context-bounds.md +++ b/_overviews/FAQ/context-bounds.md @@ -1,11 +1,14 @@ --- -layout: overview-large +layout: multipage-overview title: What are Scala context bounds? discourse: true +overview-name: FAQ partof: FAQ + num: 3 +permalink: /tutorials/FAQ/:title.html --- What is a Context Bound? @@ -17,7 +20,7 @@ functionality provided by Haskell type classes, though in a more verbose manner. A context bound requires a _parameterized type_, such as `Ordered[A]`, -but unlike `String`. +but unlike `String`. A context bound describes an implicit _value_. It is used to declare that for some type `A`, there is an @@ -102,4 +105,3 @@ Related questions of interest: This answer was originally submitted in response to [this question on Stack Overflow][1]. [1]: http://stackoverflow.com/q/4465948/53013 - diff --git a/tutorials/FAQ/finding-implicits.md b/_overviews/FAQ/finding-implicits.md similarity index 98% rename from tutorials/FAQ/finding-implicits.md rename to _overviews/FAQ/finding-implicits.md index 3117e5dc0d..5dd36ec510 100644 --- a/tutorials/FAQ/finding-implicits.md +++ b/_overviews/FAQ/finding-implicits.md @@ -1,11 +1,14 @@ --- -layout: overview-large +layout: multipage-overview title: Where does Scala look for implicits? discourse: true +overview-name: FAQ partof: FAQ + num: 7 +permalink: /tutorials/FAQ/:title.html --- Newcomers to Scala often ask: Where does the compiler look for implicits? @@ -14,15 +17,15 @@ For example, where do the values for `integral` below come from? scala> import scala.math._ import scala.math._ - + scala> def foo[T](t: T)(implicit integral: Integral[T]): Unit = { println(integral) } foo: [T](t: T)(implicit integral: scala.math.Integral[T])Unit - + scala> foo(0) scala.math.Numeric$IntIsIntegral$@3dbea611 - + scala> foo(0L) scala.math.Numeric$LongIsIntegral$@48c610af @@ -86,7 +89,7 @@ The method `getIndex` can receive any object, as long as there is an implicit conversion available from its class to `Seq[T]`. Because of that, a `String` can be passed to `getIndex`, and it will work. -Behind the scenes, the compiler changes `seq.indexOf(value)` to +Behind the scenes, the compiler changes `seq.IndexOf(value)` to `conv(seq).indexOf(value)`. ### Context Bounds @@ -168,7 +171,7 @@ Let's give examples for them. import scala.collection.JavaConversions.mapAsScalaMap def env = System.getenv() // Java map val term = env("TERM") // implicit conversion from Java Map to Scala Map - + ### Wildcard Imports def sum[T : Integral](list: List[T]): T = { @@ -219,7 +222,7 @@ the object `Ordering`, companion to the class `Ordering`, and finds an implicit Note that companion objects of super classes are also looked into. For example: class A(val n: Int) - object A { + object A { implicit def str(a: A) = "A: %d" format a.n } class B(val x: Int, y: Int) extends A(y) @@ -228,7 +231,7 @@ Note that companion objects of super classes are also looked into. For example: This is how Scala found the implicit `Numeric[Int]` and `Numeric[Long]` in the opening example, by the way, as they are found inside `Numeric`, not `Integral`. - + ### Implicit scope of an argument's type If you have a method with an argument type `A`, then the implicit scope of type @@ -325,4 +328,3 @@ This question and answer were originally submitted on [Stack Overflow][3]. [5]: http://scala-lang.org/files/archive/spec/2.11/06-expressions.html [6]: http://scala-lang.org/files/archive/spec/2.11/07-implicits.html [7]: https://github.com/scala/scala.github.com/issues - diff --git a/tutorials/FAQ/finding-symbols.md b/_overviews/FAQ/finding-symbols.md similarity index 99% rename from tutorials/FAQ/finding-symbols.md rename to _overviews/FAQ/finding-symbols.md index a499b7b5ee..948dc99db6 100644 --- a/tutorials/FAQ/finding-symbols.md +++ b/_overviews/FAQ/finding-symbols.md @@ -1,12 +1,15 @@ --- -layout: overview-large +layout: multipage-overview title: How do I find what some symbol means or does? discourse: true +overview-name: FAQ partof: FAQ + num: 1 -outof: 9 + +permalink: /tutorials/FAQ/:title.html --- We can divide the operators in Scala, for the purpose of teaching, into four categories: diff --git a/_overviews/FAQ/index.md b/_overviews/FAQ/index.md new file mode 100644 index 0000000000..80b0e657de --- /dev/null +++ b/_overviews/FAQ/index.md @@ -0,0 +1,22 @@ +--- +layout: singlepage-overview +title: Scala FAQs + +discourse: true + +permalink: /tutorials/FAQ/index.html +--- + +A collection of frequently asked questions and their answers! Graciously +provided by Daniel Sobral, adapted from his StackOverflow posts. + +## FAQs + +{% assign overviews = site.overviews | sort: 'num' %} +
        +{% for overview in overviews %} + {% if overview.partof == "FAQ" %} +
      • {{ overview.title }}
      • + {% endif %} +{% endfor %} +
      diff --git a/tutorials/FAQ/initialization-order.md b/_overviews/FAQ/initialization-order.md similarity index 98% rename from tutorials/FAQ/initialization-order.md rename to _overviews/FAQ/initialization-order.md index d9c3973d7c..2c6f0b678f 100644 --- a/tutorials/FAQ/initialization-order.md +++ b/_overviews/FAQ/initialization-order.md @@ -1,11 +1,14 @@ --- -layout: overview-large +layout: multipage-overview title: Why is my abstract or overridden val null? discourse: true +overview-name: FAQ partof: FAQ + num: 9 +permalink: /tutorials/FAQ/:title.html --- ## Example diff --git a/tutorials/FAQ/stream-view-iterator.md b/_overviews/FAQ/stream-view-iterator.md similarity index 95% rename from tutorials/FAQ/stream-view-iterator.md rename to _overviews/FAQ/stream-view-iterator.md index cdaf98b17a..cc6215233a 100644 --- a/tutorials/FAQ/stream-view-iterator.md +++ b/_overviews/FAQ/stream-view-iterator.md @@ -1,11 +1,14 @@ --- -layout: overview-large +layout: multipage-overview title: What is the difference between view, stream and iterator? discourse: true +overview-name: FAQ partof: FAQ + num: 4 +permalink: /tutorials/FAQ/:title.html --- First, they are all _non-strict_. That has a particular mathematical meaning related to functions, but, basically, means they are computed on-demand instead @@ -43,4 +46,3 @@ expected to ever be fetched, compared to the total size of the view. This answer was originally submitted in response to [this question on Stack Overflow][1]. [1]: http://stackoverflow.com/q/5159000/53013 - diff --git a/tutorials/FAQ/yield.md b/_overviews/FAQ/yield.md similarity index 94% rename from tutorials/FAQ/yield.md rename to _overviews/FAQ/yield.md index 50db76eca1..44a839882f 100644 --- a/tutorials/FAQ/yield.md +++ b/_overviews/FAQ/yield.md @@ -1,11 +1,12 @@ --- -layout: overview-large +layout: multipage-overview title: How does yield work? discourse: true partof: FAQ num: 2 +permalink: /tutorials/FAQ/:title.html --- Though there's a `yield` in other languages such as Python and Ruby, Scala's `yield` does something very different from them. In Scala, `yield` is part @@ -20,8 +21,8 @@ Translating for-comprehensions ------------------------------ Scala's "for comprehensions" are syntactic sugar for composition of multiple -operations with `foreach`, `map`, `flatMap`, `filter` or `withFilter`. -Scala actually translates a for-expression into calls to those methods, +operations with `foreach`, `map`, `flatMap`, `filter` or `withFilter`. +Scala actually translates a for-expression into calls to those methods, so any class providing them, or a subset of them, can be used with for comprehensions. First, let's talk about the translations. There are very simple rules: @@ -54,7 +55,7 @@ with a fallback into c.filter(x => cond).map(x => {...}) -if method `withFilter` is not available but `filter` is. +if method `withFilter` is not available but `filter` is. The next chapter has more information on this. #### Example 4 @@ -96,16 +97,16 @@ collection. To understand this better, let's take a look at some Scala 2.7 with scala> var found = false found: Boolean = false - + scala> List.range(1,10).filter(_ % 2 == 1 && !found).foreach(x => if (x == 5) found = true else println(x)) 1 3 7 9 - + scala> found = false found: Boolean = false - + scala> Stream.range(1,10).filter(_ % 2 == 1 && !found).foreach(x => if (x == 5) found = true else println(x)) 1 3 @@ -120,10 +121,10 @@ each element is requested by `foreach`, `filter` tests the condition, which enables `foreach` to influence it through `found`. Just to make it clear, here is the equivalent for-comprehension code: - for (x <- List.range(1, 10); if x % 2 == 1 && !found) + for (x <- List.range(1, 10); if x % 2 == 1 && !found) if (x == 5) found = true else println(x) - for (x <- Stream.range(1, 10); if x % 2 == 1 && !found) + for (x <- Stream.range(1, 10); if x % 2 == 1 && !found) if (x == 5) found = true else println(x) This caused many problems, because people expected the `if` to be considered @@ -135,16 +136,16 @@ methods on Scala 2.8: scala> var found = false found: Boolean = false - + scala> List.range(1,10).filter(_ % 2 == 1 && !found).foreach(x => if (x == 5) found = true else println(x)) 1 3 7 9 - + scala> found = false found: Boolean = false - + scala> List.range(1,10).withFilter(_ % 2 == 1 && !found).foreach(x => if (x == 5) found = true else println(x)) 1 3 diff --git a/_overviews/cheatsheets/index.md b/_overviews/cheatsheets/index.md new file mode 100644 index 0000000000..f069308031 --- /dev/null +++ b/_overviews/cheatsheets/index.md @@ -0,0 +1,358 @@ +--- +layout: cheatsheet +title: Scalacheat + +partof: cheatsheet + +by: Brendan O'Connor +about: Thanks to Brendan O'Connor, this cheatsheet aims to be a quick reference of Scala syntactic constructions. Licensed by Brendan O'Connor under a CC-BY-SA 3.0 license. + +languages: [ba, fr, ja, pl, pt-br, zh-cn] +permalink: /cheatsheets/index.html +--- + + + +{{ page.about }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      variables
      var x = 5variable
      Good
      val x = 5
      Bad
      x=6
      constant
      var x: Double = 5explicit type
      functions
      Good
      def f(x: Int) = { x*x }
      Bad
      def f(x: Int) { x*x }
      define function
      hidden error: without = it’s a Unit-returning procedure; causes havoc
      Good
      def f(x: Any) = println(x)
      Bad
      def f(x) = println(x)
      define function
      syntax error: need types for every arg.
      type R = Doubletype alias
      def f(x: R) vs.
      def f(x: => R)
      call-by-value
      call-by-name (lazy parameters)
      (x:R) => x*xanonymous function
      (1 to 5).map(_*2) vs.
      (1 to 5).reduceLeft( _+_ )
      anonymous function: underscore is positionally matched arg.
      (1 to 5).map( x => x*x )anonymous function: to use an arg twice, have to name it.
      Good
      (1 to 5).map(2*)
      Bad
      (1 to 5).map(*2)
      anonymous function: bound infix method. Use 2*_ for sanity’s sake instead.
      (1 to 5).map { x => val y=x*2; println(y); y }anonymous function: block style returns last expression.
      (1 to 5) filter {_%2 == 0} map {_*2}anonymous functions: pipeline style. (or parens too).
      def compose(g:R=>R, h:R=>R) = (x:R) => g(h(x))
      val f = compose({_*2}, {_-1})
      anonymous functions: to pass in multiple blocks, need outer parens.
      val zscore = (mean:R, sd:R) => (x:R) => (x-mean)/sdcurrying, obvious syntax.
      def zscore(mean:R, sd:R) = (x:R) => (x-mean)/sdcurrying, obvious syntax
      def zscore(mean:R, sd:R)(x:R) = (x-mean)/sdcurrying, sugar syntax. but then:
      val normer = zscore(7, 0.4) _need trailing underscore to get the partial, only for the sugar version.
      def mapmake[T](g:T=>T)(seq: List[T]) = seq.map(g)generic type.
      5.+(3); 5 + 3
      (1 to 5) map (_*2)
      infix sugar.
      def sum(args: Int*) = args.reduceLeft(_+_)varargs.
      packages
      import scala.collection._wildcard import.
      import scala.collection.Vector
      import scala.collection.{Vector, Sequence}
      selective import.
      import scala.collection.{Vector => Vec28}renaming import.
      import java.util.{Date => _, _}import all from java.util except Date.
      package pkg at start of file
      package pkg { ... }
      declare a package.
      data structures
      (1,2,3)tuple literal. (Tuple3)
      var (x,y,z) = (1,2,3)destructuring bind: tuple unpacking via pattern matching.
      Bad
      var x,y,z = (1,2,3)
      hidden error: each assigned to the entire tuple.
      var xs = List(1,2,3)list (immutable).
      xs(2)paren indexing. (slides)
      1 :: List(2,3)cons.
      1 to 5 same as 1 until 6
      1 to 10 by 2
      range sugar.
      () (empty parens)sole member of the Unit type (like C/Java void).
      control constructs
      if (check) happy else sadconditional.
      if (check) happy +
      same as
      + if (check) happy else ()
      conditional sugar.
      while (x < 5) { println(x); x += 1}while loop.
      do { println(x); x += 1} while (x < 5)do while loop.
      import scala.util.control.Breaks._
      +breakable {
      +  for (x <- xs) {
      +    if (Math.random < 0.1)
      +      break
      +  }
      +}
      break. (slides)
      for (x <- xs if x%2 == 0) yield x*10 +
      same as
      + xs.filter(_%2 == 0).map(_*10)
      for comprehension: filter/map
      for ((x,y) <- xs zip ys) yield x*y +
      same as
      + (xs zip ys) map { case (x,y) => x*y }
      for comprehension: destructuring bind
      for (x <- xs; y <- ys) yield x*y +
      same as
      + xs flatMap {x => ys map {y => x*y}}
      for comprehension: cross product
      for (x <- xs; y <- ys) {
      +  println("%d/%d = %.1f".format(x, y, x/y.toFloat))
      +}
      for comprehension: imperative-ish
      sprintf-style
      for (i <- 1 to 5) {
      +  println(i)
      +}
      for comprehension: iterate including the upper bound
      for (i <- 1 until 5) {
      +  println(i)
      +}
      for comprehension: iterate omitting the upper bound
      pattern matching
      Good
      (xs zip ys) map { case (x,y) => x*y }
      Bad
      (xs zip ys) map( (x,y) => x*y )
      use case in function args for pattern matching.
      Bad
      +
      val v42 = 42
      +Some(3) match {
      +  case Some(v42) => println("42")
      +  case _ => println("Not 42")
      +}
      “v42” is interpreted as a name matching any Int value, and “42” is printed.
      Good
      +
      val v42 = 42
      +Some(3) match {
      +  case Some(`v42`) => println("42")
      +  case _ => println("Not 42")
      +}
      ”`v42`” with backticks is interpreted as the existing val v42, and “Not 42” is printed.
      Good
      +
      val UppercaseVal = 42
      +Some(3) match {
      +  case Some(UppercaseVal) => println("42")
      +  case _ => println("Not 42")
      +}
      UppercaseVal is treated as an existing val, rather than a new pattern variable, because it starts with an uppercase letter. Thus, the value contained within UppercaseVal is checked against 3, and “Not 42” is printed.
      object orientation
      class C(x: R)constructor params - x is only available in class body
      class C(val x: R)
      var c = new C(4)
      c.x
      constructor params - automatic public member defined
      class C(var x: R) {
      +  assert(x > 0, "positive please")
      +  var y = x
      +  val readonly = 5
      +  private var secret = 1
      +  def this = this(42)
      +}
      constructor is class body
      declare a public member
      declare a gettable but not settable member
      declare a private member
      alternative constructor
      new{ ... }anonymous class
      abstract class D { ... }define an abstract class. (non-createable)
      class C extends D { ... }define an inherited class.
      class D(var x: R)
      class C(x: R) extends D(x)
      inheritance and constructor params. (wishlist: automatically pass-up params by default)
      object O extends D { ... }define a singleton. (module-like)
      trait T { ... }
      class C extends T { ... }
      class C extends D with T { ... }
      traits.
      interfaces-with-implementation. no constructor params. mixin-able.
      trait T1; trait T2
      class C extends T1 with T2
      class C extends D with T1 with T2
      multiple traits.
      class C extends D { override def f = ...}must declare method overrides.
      new java.io.File("f")create object.
      Bad
      new List[Int]
      Good
      List(1,2,3)
      type error: abstract type
      instead, convention: callable factory shadowing the type
      classOf[String]class literal.
      x.isInstanceOf[String]type check (runtime)
      x.asInstanceOf[String]type cast (runtime)
      x: Stringascription (compile time)
      diff --git a/_overviews/collections/arrays.md b/_overviews/collections/arrays.md new file mode 100644 index 0000000000..dfa85521e9 --- /dev/null +++ b/_overviews/collections/arrays.md @@ -0,0 +1,123 @@ +--- +layout: multipage-overview +title: Arrays + +discourse: true + +partof: collections +overview-name: Collections + +num: 10 + +languages: [ja, zh-cn] +permalink: /overviews/collections/:title.html +--- + +[Array](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/Array.html) is a special kind of collection in Scala. On the one hand, Scala arrays correspond one-to-one to Java arrays. That is, a Scala array `Array[Int]` is represented as a Java `int[]`, an `Array[Double]` is represented as a Java `double[]` and a `Array[String]` is represented as a Java `String[]`. But at the same time, Scala arrays offer much more than their Java analogues. First, Scala arrays can be _generic_. That is, you can have an `Array[T]`, where `T` is a type parameter or abstract type. Second, Scala arrays are compatible with Scala sequences - you can pass an `Array[T]` where a `Seq[T]` is required. Finally, Scala arrays also support all sequence operations. Here's an example of this in action: + + scala> val a1 = Array(1, 2, 3) + a1: Array[Int] = Array(1, 2, 3) + scala> val a2 = a1 map (_ * 3) + a2: Array[Int] = Array(3, 6, 9) + scala> val a3 = a2 filter (_ % 2 != 0) + a3: Array[Int] = Array(3, 9) + scala> a3.reverse + res0: Array[Int] = Array(9, 3) + +Given that Scala arrays are represented just like Java arrays, how can these additional features be supported in Scala? In fact, the answer to this question differs between Scala 2.8 and earlier versions. Previously, the Scala compiler somewhat "magically" wrapped and unwrapped arrays to and from `Seq` objects when required in a process called boxing and unboxing. The details of this were quite complicated, in particular when one created a new array of generic type `Array[T]`. There were some puzzling corner cases and the performance of array operations was not all that predictable. + +The Scala 2.8 design is much simpler. Almost all compiler magic is gone. Instead the Scala 2.8 array implementation makes systematic use of implicit conversions. In Scala 2.8 an array does not pretend to _be_ a sequence. It can't really be that because the data type representation of a native array is not a subtype of `Seq`. Instead there is an implicit "wrapping" conversion between arrays and instances of class `scala.collection.mutable.WrappedArray`, which is a subclass of `Seq`. Here you see it in action: + + scala> val seq: Seq[Int] = a1 + seq: Seq[Int] = WrappedArray(1, 2, 3) + scala> val a4: Array[Int] = seq.toArray + a4: Array[Int] = Array(1, 2, 3) + scala> a1 eq a4 + res1: Boolean = true + +The interaction above demonstrates that arrays are compatible with sequences, because there's an implicit conversion from arrays to `WrappedArray`s. To go the other way, from a `WrappedArray` to an `Array`, you can use the `toArray` method defined in `Traversable`. The last REPL line above shows that wrapping and then unwrapping with `toArray` gives the same array you started with. + +There is yet another implicit conversion that gets applied to arrays. This conversion simply "adds" all sequence methods to arrays but does not turn the array itself into a sequence. "Adding" means that the array is wrapped in another object of type `ArrayOps` which supports all sequence methods. Typically, this `ArrayOps` object is short-lived; it will usually be inaccessible after the call to the sequence method and its storage can be recycled. Modern VMs often avoid creating this object entirely. + +The difference between the two implicit conversions on arrays is shown in the next REPL dialogue: + + scala> val seq: Seq[Int] = a1 + seq: Seq[Int] = WrappedArray(1, 2, 3) + scala> seq.reverse + res2: Seq[Int] = WrappedArray(3, 2, 1) + scala> val ops: collection.mutable.ArrayOps[Int] = a1 + ops: scala.collection.mutable.ArrayOps[Int] = [I(1, 2, 3) + scala> ops.reverse + res3: Array[Int] = Array(3, 2, 1) + +You see that calling reverse on `seq`, which is a `WrappedArray`, will give again a `WrappedArray`. That's logical, because wrapped arrays are `Seqs`, and calling reverse on any `Seq` will give again a `Seq`. On the other hand, calling reverse on the ops value of class `ArrayOps` will give an `Array`, not a `Seq`. + +The `ArrayOps` example above was quite artificial, intended only to show the difference to `WrappedArray`. Normally, you'd never define a value of class `ArrayOps`. You'd just call a `Seq` method on an array: + + scala> a1.reverse + res4: Array[Int] = Array(3, 2, 1) + +The `ArrayOps` object gets inserted automatically by the implicit conversion. So the line above is equivalent to + + scala> intArrayOps(a1).reverse + res5: Array[Int] = Array(3, 2, 1) + +where `intArrayOps` is the implicit conversion that was inserted previously. This raises the question how the compiler picked `intArrayOps` over the other implicit conversion to `WrappedArray` in the line above. After all, both conversions map an array to a type that supports a reverse method, which is what the input specified. The answer to that question is that the two implicit conversions are prioritized. The `ArrayOps` conversion has a higher priority than the `WrappedArray` conversion. The first is defined in the `Predef` object whereas the second is defined in a class `scala.LowPriorityImplicits`, which is inherited from `Predef`. Implicits in subclasses and subobjects take precedence over implicits in base classes. So if both conversions are applicable, the one in `Predef` is chosen. A very similar scheme works for strings. + +So now you know how arrays can be compatible with sequences and how they can support all sequence operations. What about genericity? In Java you cannot write a `T[]` where `T` is a type parameter. How then is Scala's `Array[T]` represented? In fact a generic array like `Array[T]` could be at run-time any of Java's eight primitive array types `byte[]`, `short[]`, `char[]`, `int[]`, `long[]`, `float[]`, `double[]`, `boolean[]`, or it could be an array of objects. The only common run-time type encompassing all of these types is `AnyRef` (or, equivalently `java.lang.Object`), so that's the type to which the Scala compiler maps `Array[T]`. At run-time, when an element of an array of type `Array[T]` is accessed or updated there is a sequence of type tests that determine the actual array type, followed by the correct array operation on the Java array. These type tests slow down array operations somewhat. You can expect accesses to generic arrays to be three to four times slower than accesses to primitive or object arrays. This means that if you need maximal performance, you should prefer concrete over generic arrays. Representing the generic array type is not enough, however, there must also be a way to create generic arrays. This is an even harder problem, which requires a little bit of help from you. To illustrate the problem, consider the following attempt to write a generic method that creates an array. + + // this is wrong! + def evenElems[T](xs: Vector[T]): Array[T] = { + val arr = new Array[T]((xs.length + 1) / 2) + for (i <- 0 until xs.length by 2) + arr(i / 2) = xs(i) + arr + } + +The `evenElems` method returns a new array that consist of all elements of the argument vector `xs` which are at even positions in the vector. The first line of the body of `evenElems` creates the result array, which has the same element type as the argument. So depending on the actual type parameter for `T`, this could be an `Array[Int]`, or an `Array[Boolean]`, or an array of some of the other primitive types in Java, or an array of some reference type. But these types have all different runtime representations, so how is the Scala runtime going to pick the correct one? In fact, it can't do that based on the information it is given, because the actual type that corresponds to the type parameter `T` is erased at runtime. That's why you will get the following error message if you compile the code above: + + error: cannot find class manifest for element type T + val arr = new Array[T]((arr.length + 1) / 2) + ^ + +What's required here is that you help the compiler out by providing some runtime hint what the actual type parameter of `evenElems` is. This runtime hint takes the form of a class manifest of type `scala.reflect.ClassTag`. A class manifest is a type descriptor object which describes what the top-level class of a type is. Alternatively to class manifests there are also full manifests of type `scala.reflect.Manifest`, which describe all aspects of a type. But for array creation, only class manifests are needed. + +The Scala compiler will construct class manifests automatically if you instruct it to do so. "Instructing" means that you demand a class manifest as an implicit parameter, like this: + + def evenElems[T](xs: Vector[T])(implicit m: ClassTag[T]): Array[T] = ... + +Using an alternative and shorter syntax, you can also demand that the type comes with a class manifest by using a context bound. This means following the type with a colon and the class name `ClassTag`, like this: + + import scala.reflect.ClassTag + // this works + def evenElems[T: ClassTag](xs: Vector[T]): Array[T] = { + val arr = new Array[T]((xs.length + 1) / 2) + for (i <- 0 until xs.length by 2) + arr(i / 2) = xs(i) + arr + } + +The two revised versions of `evenElems` mean exactly the same. What happens in either case is that when the `Array[T]` is constructed, the compiler will look for a class manifest for the type parameter T, that is, it will look for an implicit value of type `ClassTag[T]`. If such a value is found, the manifest is used to construct the right kind of array. Otherwise, you'll see an error message like the one above. + +Here is some REPL interaction that uses the `evenElems` method. + + scala> evenElems(Vector(1, 2, 3, 4, 5)) + res6: Array[Int] = Array(1, 3, 5) + scala> evenElems(Vector("this", "is", "a", "test", "run")) + res7: Array[java.lang.String] = Array(this, a, run) + +In both cases, the Scala compiler automatically constructed a class manifest for the element type (first, `Int`, then `String`) and passed it to the implicit parameter of the `evenElems` method. The compiler can do that for all concrete types, but not if the argument is itself another type parameter without its class manifest. For instance, the following fails: + + scala> def wrap[U](xs: Vector[U]) = evenElems(xs) + :6: error: No ClassTag available for U. + def wrap[U](xs: Vector[U]) = evenElems(xs) + ^ + +What happened here is that the `evenElems` demands a class manifest for the type parameter `U`, but none was found. The solution in this case is, of course, to demand another implicit class manifest for `U`. So the following works: + + scala> def wrap[U: ClassTag](xs: Vector[U]) = evenElems(xs) + wrap: [U](xs: Vector[U])(implicit evidence$1: scala.reflect.ClassTag[U])Array[U] + +This example also shows that the context bound in the definition of `U` is just a shorthand for an implicit parameter named here `evidence$1` of type `ClassTag[U]`. + +In summary, generic array creation demands class manifests. So whenever creating an array of a type parameter `T`, you also need to provide an implicit class manifest for `T`. The easiest way to do this is to declare the type parameter with a `ClassTag` context bound, as in `[T: ClassTag]`. diff --git a/_overviews/collections/concrete-immutable-collection-classes.md b/_overviews/collections/concrete-immutable-collection-classes.md new file mode 100644 index 0000000000..78ca9d0857 --- /dev/null +++ b/_overviews/collections/concrete-immutable-collection-classes.md @@ -0,0 +1,196 @@ +--- +layout: multipage-overview +title: Concrete Immutable Collection Classes + +discourse: true + +partof: collections +overview-name: Collections + +num: 8 + +languages: [ja, zh-cn] +permalink: /overviews/collections/:title.html +--- + +Scala provides many concrete immutable collection classes for you to choose from. They differ in the traits they implement (maps, sets, sequences), whether they can be infinite, and the speed of various operations. Here are some of the most common immutable collection types used in Scala. + +## Lists + +A [List](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/List.html) is a finite immutable sequence. They provide constant-time access to their first element as well as the rest of the list, and they have a constant-time cons operation for adding a new element to the front of the list. Many other operations take linear time. + +Lists have always been the workhorse for Scala programming, so not much needs to be said about them here. The major change in 2.8 is that the `List` class together with its subclass `::` and its subobject `Nil` is now defined in package `scala.collection.immutable`, where it logically belongs. There are still aliases for `List`, `Nil`, and `::` in the `scala` package, so from a user perspective, lists can be accessed as before. + +Another change is that lists now integrate more closely into the collections framework, and are less of a special case than before. For instance all of the numerous methods that originally lived in the `List` companion object have been deprecated. They are replaced by the [uniform creation methods]({{ site.baseurl }}/overviews/collections/creating-collections-from-scratch.html) inherited by every collection. + +## Streams + +A [Stream](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Stream.html) is like a list except that its elements are computed lazily. Because of this, a stream can be infinitely long. Only those elements requested are computed. Otherwise, streams have the same performance characteristics as lists. + +Whereas lists are constructed with the `::` operator, streams are constructed with the similar-looking `#::`. Here is a simple example of a stream containing the integers 1, 2, and 3: + + scala> val str = 1 #:: 2 #:: 3 #:: Stream.empty + str: scala.collection.immutable.Stream[Int] = Stream(1, ?) + +The head of this stream is 1, and the tail of it has 2 and 3. The tail is not printed here, though, because it hasn't been computed yet! Streams are specified to compute lazily, and the `toString` method of a stream is careful not to force any extra evaluation. + +Below is a more complex example. It computes a stream that contains a Fibonacci sequence starting with the given two numbers. A Fibonacci sequence is one where each element is the sum of the previous two elements in the series. + + + scala> def fibFrom(a: Int, b: Int): Stream[Int] = a #:: fibFrom(b, a + b) + fibFrom: (a: Int,b: Int)Stream[Int] + +This function is deceptively simple. The first element of the sequence is clearly `a`, and the rest of the sequence is the Fibonacci sequence starting with `b` followed by `a + b`. The tricky part is computing this sequence without causing an infinite recursion. If the function used `::` instead of `#::`, then every call to the function would result in another call, thus causing an infinite recursion. Since it uses `#::`, though, the right-hand side is not evaluated until it is requested. +Here are the first few elements of the Fibonacci sequence starting with two ones: + + scala> val fibs = fibFrom(1, 1).take(7) + fibs: scala.collection.immutable.Stream[Int] = Stream(1, ?) + scala> fibs.toList + res9: List[Int] = List(1, 1, 2, 3, 5, 8, 13) + +## Vectors + +Lists are very efficient when the algorithm processing them is careful to only process their heads. Accessing, adding, and removing the head of a list takes only constant time, whereas accessing or modifying elements later in the list takes time linear in the depth into the list. + +[Vector](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Vector.html) is a collection type (introduced in Scala 2.8) that addresses the inefficiency for random access on lists. Vectors allow accessing any element of the list in "effectively" constant time. It's a larger constant than for access to the head of a list or for reading an element of an array, but it's a constant nonetheless. As a result, algorithms using vectors do not have to be careful about accessing just the head of the sequence. They can access and modify elements at arbitrary locations, and thus they can be much more convenient to write. + +Vectors are built and modified just like any other sequence. + + scala> val vec = scala.collection.immutable.Vector.empty + vec: scala.collection.immutable.Vector[Nothing] = Vector() + scala> val vec2 = vec :+ 1 :+ 2 + vec2: scala.collection.immutable.Vector[Int] = Vector(1, 2) + scala> val vec3 = 100 +: vec2 + vec3: scala.collection.immutable.Vector[Int] = Vector(100, 1, 2) + scala> vec3(0) + res1: Int = 100 + +Vectors are represented as trees with a high branching factor (The branching factor of a tree or a graph is the number of children at each node). Every tree node contains up to 32 elements of the vector or contains up to 32 other tree nodes. Vectors with up to 32 elements can be represented in a single node. Vectors with up to `32 * 32 = 1024` elements can be represented with a single indirection. Two hops from the root of the tree to the final element node are sufficient for vectors with up to 215 elements, three hops for vectors with 220, four hops for vectors with 225 elements and five hops for vectors with up to 230 elements. So for all vectors of reasonable size, an element selection involves up to 5 primitive array selections. This is what we meant when we wrote that element access is "effectively constant time". + +Vectors are immutable, so you cannot change an element of a vector and still retain a new vector. However, with the `updated` method you can create a new vector that differs from a given vector only in a single element: + + scala> val vec = Vector(1, 2, 3) + vec: scala.collection.immutable.Vector[Int] = Vector(1, 2, 3) + scala> vec updated (2, 4) + res0: scala.collection.immutable.Vector[Int] = Vector(1, 2, 4) + scala> vec + res1: scala.collection.immutable.Vector[Int] = Vector(1, 2, 3) + +As the last line above shows, a call to `updated` has no effect on the original vector `vec`. Like selection, functional vector updates are also "effectively constant time". Updating an element in the middle of a vector can be done by copying the node that contains the element, and every node that points to it, starting from the root of the tree. This means that a functional update creates between one and five nodes that each contain up to 32 elements or subtrees. This is certainly more expensive than an in-place update in a mutable array, but still a lot cheaper than copying the whole vector. + +Because vectors strike a good balance between fast random selections and fast random functional updates, they are currently the default implementation of immutable indexed sequences: + + + scala> collection.immutable.IndexedSeq(1, 2, 3) + res2: scala.collection.immutable.IndexedSeq[Int] = Vector(1, 2, 3) + +## Immutable stacks + +If you need a last-in-first-out sequence, you can use a [Stack](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Stack.html). You push an element onto a stack with `push`, pop an element with `pop`, and peek at the top of the stack without removing it with `top`. All of these operations are constant time. + +Here are some simple operations performed on a stack: + + + scala> val stack = scala.collection.immutable.Stack.empty + stack: scala.collection.immutable.Stack[Nothing] = Stack() + scala> val hasOne = stack.push(1) + hasOne: scala.collection.immutable.Stack[Int] = Stack(1) + scala> stack + stack: scala.collection.immutable.Stack[Nothing] = Stack() + scala> hasOne.top + res20: Int = 1 + scala> hasOne.pop + res19: scala.collection.immutable.Stack[Int] = Stack() + +Immutable stacks are used rarely in Scala programs because their functionality is subsumed by lists: A `push` on an immutable stack is the same as a `::` on a list and a `pop` on a stack is the same as a `tail` on a list. + +## Immutable Queues + +A [Queue](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Queue.html) is just like a stack except that it is first-in-first-out rather than last-in-first-out. + +Here's how you can create an empty immutable queue: + + scala> val empty = scala.collection.immutable.Queue[Int]() + empty: scala.collection.immutable.Queue[Int] = Queue() + +You can append an element to an immutable queue with `enqueue`: + + scala> val has1 = empty.enqueue(1) + has1: scala.collection.immutable.Queue[Int] = Queue(1) + +To append multiple elements to a queue, call `enqueue` with a collection as its argument: + + scala> val has123 = has1.enqueue(List(2, 3)) + has123: scala.collection.immutable.Queue[Int] + = Queue(1, 2, 3) + +To remove an element from the head of the queue, you use `dequeue`: + + scala> val (element, has23) = has123.dequeue + element: Int = 1 + has23: scala.collection.immutable.Queue[Int] = Queue(2, 3) + +Note that `dequeue` returns a pair consisting of the element removed and the rest of the queue. + +## Ranges + +A [Range](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Range.html) is an ordered sequence of integers that are equally spaced apart. For example, "1, 2, 3," is a range, as is "5, 8, 11, 14." To create a range in Scala, use the predefined methods `to` and `by`. + + scala> 1 to 3 + res2: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3) + scala> 5 to 14 by 3 + res3: scala.collection.immutable.Range = Range(5, 8, 11, 14) + +If you want to create a range that is exclusive of its upper limit, then use the convenience method `until` instead of `to`: + + scala> 1 until 3 + res2: scala.collection.immutable.Range = Range(1, 2) + +Ranges are represented in constant space, because they can be defined by just three numbers: their start, their end, and the stepping value. Because of this representation, most operations on ranges are extremely fast. + +## Hash Tries + +Hash tries are a standard way to implement immutable sets and maps efficiently. They are supported by class [immutable.HashMap](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/HashMap.html). Their representation is similar to vectors in that they are also trees where every node has 32 elements or 32 subtrees. But the selection of these keys is now done based on hash code. For instance, to find a given key in a map, one first takes the hash code of the key. Then, the lowest 5 bits of the hash code are used to select the first subtree, followed by the next 5 bits and so on. The selection stops once all elements stored in a node have hash codes that differ from each other in the bits that are selected up to this level. + +Hash tries strike a nice balance between reasonably fast lookups and reasonably efficient functional insertions (`+`) and deletions (`-`). That's why they underly Scala's default implementations of immutable maps and sets. In fact, Scala has a further optimization for immutable sets and maps that contain less than five elements. Sets and maps with one to four elements are stored as single objects that just contain the elements (or key/value pairs in the case of a map) as fields. The empty immutable set and the empty immutable map is in each case a single object - there's no need to duplicate storage for those because an empty immutable set or map will always stay empty. + +## Red-Black Trees + +Red-black trees are a form of balanced binary tree where some nodes are designated "red" and others designated "black." Like any balanced binary tree, operations on them reliably complete in time logarithmic to the size of the tree. + +Scala provides implementations of immutable sets and maps that use a red-black tree internally. Access them under the names [TreeSet](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/TreeSet.html) and [TreeMap](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/TreeMap.html). + + + scala> scala.collection.immutable.TreeSet.empty[Int] + res11: scala.collection.immutable.TreeSet[Int] = TreeSet() + scala> res11 + 1 + 3 + 3 + res12: scala.collection.immutable.TreeSet[Int] = TreeSet(1, 3) + +Red-black trees are the standard implementation of `SortedSet` in Scala, because they provide an efficient iterator that returns all elements in sorted order. + +## Immutable BitSets + +A [BitSet](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/BitSet.html) represents a collection of small integers as the bits of a larger integer. For example, the bit set containing 3, 2, and 0 would be represented as the integer 1101 in binary, which is 13 in decimal. + +Internally, bit sets use an array of 64-bit `Long`s. The first `Long` in the array is for integers 0 through 63, the second is for 64 through 127, and so on. Thus, bit sets are very compact so long as the largest integer in the set is less than a few hundred or so. + +Operations on bit sets are very fast. Testing for inclusion takes constant time. Adding an item to the set takes time proportional to the number of `Long`s in the bit set's array, which is typically a small number. Here are some simple examples of the use of a bit set: + + scala> val bits = scala.collection.immutable.BitSet.empty + bits: scala.collection.immutable.BitSet = BitSet() + scala> val moreBits = bits + 3 + 4 + 4 + moreBits: scala.collection.immutable.BitSet = BitSet(3, 4) + scala> moreBits(3) + res26: Boolean = true + scala> moreBits(0) + res27: Boolean = false + +## List Maps + +A [ListMap](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/ListMap.html) represents a map as a linked list of key-value pairs. In general, operations on a list map might have to iterate through the entire list. Thus, operations on a list map take time linear in the size of the map. In fact there is little usage for list maps in Scala because standard immutable maps are almost always faster. The only possible exception to this is if the map is for some reason constructed in such a way that the first elements in the list are selected much more often than the other elements. + + scala> val map = scala.collection.immutable.ListMap(1->"one", 2->"two") + map: scala.collection.immutable.ListMap[Int,java.lang.String] = + Map(1 -> one, 2 -> two) + scala> map(2) + res30: String = "two" diff --git a/_overviews/collections/concrete-mutable-collection-classes.md b/_overviews/collections/concrete-mutable-collection-classes.md new file mode 100644 index 0000000000..b119a307d3 --- /dev/null +++ b/_overviews/collections/concrete-mutable-collection-classes.md @@ -0,0 +1,168 @@ +--- +layout: multipage-overview +title: Concrete Mutable Collection Classes + +discourse: true + +partof: collections +overview-name: Collections + +num: 9 + +languages: [ja, zh-cn] +permalink: /overviews/collections/:title.html +--- + +You've now seen the most commonly used immutable collection classes that Scala provides in its standard library. Take a look now at the mutable collection classes. + +## Array Buffers + +An [ArrayBuffer](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/ArrayBuffer.html) buffer holds an array and a size. Most operations on an array buffer have the same speed as for an array, because the operations simply access and modify the underlying array. Additionally, array buffers can have data efficiently added to the end. Appending an item to an array buffer takes amortized constant time. Thus, array buffers are useful for efficiently building up a large collection whenever the new items are always added to the end. + + scala> val buf = scala.collection.mutable.ArrayBuffer.empty[Int] + buf: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer() + scala> buf += 1 + res32: buf.type = ArrayBuffer(1) + scala> buf += 10 + res33: buf.type = ArrayBuffer(1, 10) + scala> buf.toArray + res34: Array[Int] = Array(1, 10) + +## List Buffers + +A [ListBuffer](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/ListBuffer.html) is like an array buffer except that it uses a linked list internally instead of an array. If you plan to convert the buffer to a list once it is built up, use a list buffer instead of an array buffer. + + scala> val buf = scala.collection.mutable.ListBuffer.empty[Int] + buf: scala.collection.mutable.ListBuffer[Int] = ListBuffer() + scala> buf += 1 + res35: buf.type = ListBuffer(1) + scala> buf += 10 + res36: buf.type = ListBuffer(1, 10) + scala> buf.toList + res37: List[Int] = List(1, 10) + +## StringBuilders + +Just like an array buffer is useful for building arrays, and a list buffer is useful for building lists, a [StringBuilder](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/StringBuilder.html) is useful for building strings. String builders are so commonly used that they are already imported into the default namespace. Create them with a simple `new StringBuilder`, like this: + + scala> val buf = new StringBuilder + buf: StringBuilder = + scala> buf += 'a' + res38: buf.type = a + scala> buf ++= "bcdef" + res39: buf.type = abcdef + scala> buf.toString + res41: String = abcdef + +## Linked Lists + +Linked lists are mutable sequences that consist of nodes which are linked with next pointers. They are supported by class [LinkedList](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/LinkedList.html). In most languages `null` would be picked as the empty linked list. That does not work for Scala collections, because even empty sequences must support all sequence methods. In particular `LinkedList.empty.isEmpty` should return `true` and not throw a `NullPointerException`. Empty linked lists are encoded instead in a special way: Their `next` field points back to the node itself. Like their immutable cousins, linked lists are best traversed sequentially. In addition linked lists make it easy to insert an element or linked list into another linked list. + +## Double Linked Lists + +Double linked lists are like single linked lists, except that they have besides `next` another mutable field `prev` that points to the element preceding the current node. The main benefit of that additional link is that it makes element removal very fast. Double linked lists are supported by class [DoubleLinkedList](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/DoubleLinkedList.html). + +## Mutable Lists + +A [MutableList](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/MutableList.html) consists of a single linked list together with a pointer that refers to the terminal empty node of that list. This makes list append a constant time operation because it avoids having to traverse the list in search for its terminal node. [MutableList](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/MutableList.html) is currently the standard implementation of [mutable.LinearSeq](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/LinearSeq.html) in Scala. + +## Queues + +Scala provides mutable queues in addition to immutable ones. You use a `mQueue` similarly to how you use an immutable one, but instead of `enqueue`, you use the `+=` and `++=` operators to append. Also, on a mutable queue, the `dequeue` method will just remove the head element from the queue and return it. Here's an example: + + scala> val queue = new scala.collection.mutable.Queue[String] + queue: scala.collection.mutable.Queue[String] = Queue() + scala> queue += "a" + res10: queue.type = Queue(a) + scala> queue ++= List("b", "c") + res11: queue.type = Queue(a, b, c) + scala> queue + res12: scala.collection.mutable.Queue[String] = Queue(a, b, c) + scala> queue.dequeue + res13: String = a + scala> queue + res14: scala.collection.mutable.Queue[String] = Queue(b, c) + +## Array Sequences + +Array sequences are mutable sequences of fixed size which store their elements internally in an `Array[Object]`. They are implemented in Scala by class [ArraySeq](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/ArraySeq.html). + +You would typically use an `ArraySeq` if you want an array for its performance characteristics, but you also want to create generic instances of the sequence where you do not know the type of the elements and you do not have a `ClassManifest` to provide it at run-time. These issues are explained in the section on [arrays]({{ site.baseurl }}/overviews/collections/arrays.html). + +## Stacks + +You saw immutable stacks earlier. There is also a mutable version, supported by class [mutable.Stack](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/Stack.html). It works exactly the same as the immutable version except that modifications happen in place. + + scala> val stack = new scala.collection.mutable.Stack[Int] + stack: scala.collection.mutable.Stack[Int] = Stack() + scala> stack.push(1) + res0: stack.type = Stack(1) + scala> stack + res1: scala.collection.mutable.Stack[Int] = Stack(1) + scala> stack.push(2) + res0: stack.type = Stack(1, 2) + scala> stack + res3: scala.collection.mutable.Stack[Int] = Stack(1, 2) + scala> stack.top + res8: Int = 2 + scala> stack + res9: scala.collection.mutable.Stack[Int] = Stack(1, 2) + scala> stack.pop + res10: Int = 2 + scala> stack + res11: scala.collection.mutable.Stack[Int] = Stack(1) + +## Array Stacks + +[ArrayStack](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/ArrayStack.html) is an alternative implementation of a mutable stack which is backed by an Array that gets re-sized as needed. It provides fast indexing and is generally slightly more efficient for most operations than a normal mutable stack. + +## Hash Tables + +A hash table stores its elements in an underlying array, placing each item at a position in the array determined by the hash code of that item. Adding an element to a hash table takes only constant time, so long as there isn't already another element in the array that has the same hash code. Hash tables are thus very fast so long as the objects placed in them have a good distribution of hash codes. As a result, the default mutable map and set types in Scala are based on hash tables. You can access them also directly under the names [mutable.HashSet](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/HashSet.html) and [mutable.HashMap](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/HashMap.html). + +Hash sets and maps are used just like any other set or map. Here are some simple examples: + + scala> val map = scala.collection.mutable.HashMap.empty[Int,String] + map: scala.collection.mutable.HashMap[Int,String] = Map() + scala> map += (1 -> "make a web site") + res42: map.type = Map(1 -> make a web site) + scala> map += (3 -> "profit!") + res43: map.type = Map(1 -> make a web site, 3 -> profit!) + scala> map(1) + res44: String = make a web site + scala> map contains 2 + res46: Boolean = false + +Iteration over a hash table is not guaranteed to occur in any particular order. Iteration simply proceeds through the underlying array in whichever order it happens to be in. To get a guaranteed iteration order, use a _linked_ hash map or set instead of a regular one. A linked hash map or set is just like a regular hash map or set except that it also includes a linked list of the elements in the order they were added. Iteration over such a collection is always in the same order that the elements were initially added. + +## Weak Hash Maps + +A weak hash map is a special kind of hash map where the garbage collector does not follow links from the map to the keys stored in it. This means that a key and its associated value will disappear from the map if there is no other reference to that key. Weak hash maps are useful for tasks such as caching, where you want to re-use an expensive function's result if the function is called again on the same key. If keys and function results are stored in a regular hash map, the map could grow without bounds, and no key would ever become garbage. Using a weak hash map avoids this problem. As soon as a key object becomes unreachable, it's entry is removed from the weak hashmap. Weak hash maps in Scala are implemented by class [WeakHashMap](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/WeakHashMap.html) as a wrapper of an underlying Java implementation `java.util.WeakHashMap`. + +## Concurrent Maps + +A concurrent map can be accessed by several threads at once. In addition to the usual [Map](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Map.html) operations, it provides the following atomic operations: + +### Operations in class ConcurrentMap + +| WHAT IT IS | WHAT IT DOES | +| ------ | ------ | +| `m putIfAbsent(k, v)` |Adds key/value binding `k -> v` unless `k` is already defined in `m` | +| `m remove (k, v)` |Removes entry for `k` if it is currently mapped to `v`. | +| `m replace (k, old, new)` |Replaces value associated with key `k` to `new`, if it was previously bound to `old`. | +| `m replace (k, v)` |Replaces value associated with key `k` to `v`, if it was previously bound to some value.| + +`ConcurrentMap` is a trait in the Scala collections library. Currently, its only implementation is Java's `java.util.concurrent.ConcurrentMap`, which can be converted automatically into a Scala map using the [standard Java/Scala collection conversions]({{ site.baseurl }}/overviews/collections/conversions-between-java-and-scala-collections.html). + +## Mutable Bitsets + +A mutable bit of type [mutable.BitSet](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/BitSet.html) set is just like an immutable one, except that it is modified in place. Mutable bit sets are slightly more efficient at updating than immutable ones, because they don't have to copy around `Long`s that haven't changed. + + scala> val bits = scala.collection.mutable.BitSet.empty + bits: scala.collection.mutable.BitSet = BitSet() + scala> bits += 1 + res49: bits.type = BitSet(1) + scala> bits += 3 + res50: bits.type = BitSet(1, 3) + scala> bits + res51: scala.collection.mutable.BitSet = BitSet(1, 3) diff --git a/_overviews/collections/conversions-between-java-and-scala-collections.md b/_overviews/collections/conversions-between-java-and-scala-collections.md new file mode 100644 index 0000000000..66e016c523 --- /dev/null +++ b/_overviews/collections/conversions-between-java-and-scala-collections.md @@ -0,0 +1,65 @@ +--- +layout: multipage-overview +title: Conversions Between Java and Scala Collections + +discourse: true + +partof: collections +overview-name: Collections + +num: 17 + +languages: [ja, zh-cn] +permalink: /overviews/collections/:title.html +--- + +Like Scala, Java also has a rich collections library. There are many similarities between the two. For instance, both libraries know iterators, iterables, sets, maps, and sequences. But there are also important differences. In particular, the Scala libraries put much more emphasis on immutable collections, and provide many more operations that transform a collection into a new one. + +Sometimes you might need to pass from one collection framework to the other. For instance, you might want to access an existing Java collection as if it were a Scala collection. Or you might want to pass one of Scala's collections to a Java method that expects its Java counterpart. It is quite easy to do this, because Scala offers implicit conversions between all the major collection types in the [JavaConverters](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/JavaConverters$.html) object. In particular, you will find bidirectional conversions between the following types. + + + Iterator <=> java.util.Iterator + Iterator <=> java.util.Enumeration + Iterable <=> java.lang.Iterable + Iterable <=> java.util.Collection + mutable.Buffer <=> java.util.List + mutable.Set <=> java.util.Set + mutable.Map <=> java.util.Map + mutable.ConcurrentMap <=> java.util.concurrent.ConcurrentMap + +To enable these conversions, simply import them from the [JavaConverters](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/JavaConverters$.html) object: + + scala> import collection.JavaConverters._ + import collection.JavaConverters._ + +This enables conversions between Scala collections and their corresponding Java collections by way of extension methods called `asScala` and `asJava`: + + scala> import collection.mutable._ + import collection.mutable._ + + scala> val jul: java.util.List[Int] = ArrayBuffer(1, 2, 3).asJava + jul: java.util.List[Int] = [1, 2, 3] + + scala> val buf: Seq[Int] = jul.asScala + buf: scala.collection.mutable.Seq[Int] = ArrayBuffer(1, 2, 3) + + scala> val m: java.util.Map[String, Int] = HashMap("abc" -> 1, "hello" -> 2).asJava + m: java.util.Map[String,Int] = {abc=1, hello=2} + +Internally, these conversion work by setting up a "wrapper" object that forwards all operations to the underlying collection object. So collections are never copied when converting between Java and Scala. An interesting property is that if you do a round-trip conversion from, say a Java type to its corresponding Scala type, and back to the same Java type, you end up with the identical collection object you have started with. + +Certain other Scala collections can also be converted to Java, but do not have a conversion back to the original Scala type: + + Seq => java.util.List + mutable.Seq => java.util.List + Set => java.util.Set + Map => java.util.Map + +Because Java does not distinguish between mutable and immutable collections in their type, a conversion from, say, `scala.immutable.List` will yield a `java.util.List`, where all mutation operations throw an "UnsupportedOperationException". Here's an example: + + scala> val jul = List(1, 2, 3).asJava + jul: java.util.List[Int] = [1, 2, 3] + + scala> jul.add(7) + java.lang.UnsupportedOperationException + at java.util.AbstractList.add(AbstractList.java:148) diff --git a/_overviews/collections/creating-collections-from-scratch.md b/_overviews/collections/creating-collections-from-scratch.md new file mode 100644 index 0000000000..cdff42e054 --- /dev/null +++ b/_overviews/collections/creating-collections-from-scratch.md @@ -0,0 +1,62 @@ +--- +layout: multipage-overview +title: Creating Collections From Scratch + +discourse: true + +partof: collections +overview-name: Collections + +num: 16 + +languages: [ja, zh-cn] +permalink: /overviews/collections/:title.html +--- + +You have syntax `List(1, 2, 3)` to create a list of three integers and `Map('A' -> 1, 'C' -> 2)` to create a map with two bindings. This is actually a universal feature of Scala collections. You can take any collection name and follow it by a list of elements in parentheses. The result will be a new collection with the given elements. Here are some more examples: + + Traversable() // An empty traversable object + List() // The empty list + List(1.0, 2.0) // A list with elements 1.0, 2.0 + Vector(1.0, 2.0) // A vector with elements 1.0, 2.0 + Iterator(1, 2, 3) // An iterator returning three integers. + Set(dog, cat, bird) // A set of three animals + HashSet(dog, cat, bird) // A hash set of the same animals + Map('a' -> 7, 'b' -> 0) // A map from characters to integers + +"Under the covers" each of the above lines is a call to the `apply` method of some object. For instance, the third line above expands to + + List.apply(1.0, 2.0) + +So this is a call to the `apply` method of the companion object of the `List` class. That method takes an arbitrary number of arguments and constructs a list from them. Every collection class in the Scala library has a companion object with such an `apply` method. It does not matter whether the collection class represents a concrete implementation, like `List`, or `Stream` or `Vector`, do, or whether it is an abstract base class such as `Seq`, `Set` or `Traversable`. In the latter case, calling apply will produce some default implementation of the abstract base class. Examples: + + scala> List(1, 2, 3) + res17: List[Int] = List(1, 2, 3) + scala> Traversable(1, 2, 3) + res18: Traversable[Int] = List(1, 2, 3) + scala> mutable.Traversable(1, 2, 3) + res19: scala.collection.mutable.Traversable[Int] = ArrayBuffer(1, 2, 3) + +Besides `apply`, every collection companion object also defines a member `empty`, which returns an empty collection. So instead of `List()` you could write `List.empty`, instead of `Map()`, `Map.empty`, and so on. + +Descendants of `Seq` classes provide also other factory operations in their companion objects. These are summarized in the following table. In short, there's + +* `concat`, which concatenates an arbitrary number of traversables together, +* `fill` and `tabulate`, which generate single or multi-dimensional sequences of given dimensions initialized by some expression or tabulating function, +* `range`, which generates integer sequences with some constant step length, and +* `iterate`, which generates the sequence resulting from repeated application of a function to a start element. + +### Factory Methods for Sequences + +| WHAT IT IS | WHAT IT DOES | +| ------ | ------ | +| `S.empty` | The empty sequence. | +| `S(x, y, z)` | A sequence consisting of elements `x, y, z`. | +| `S.concat(xs, ys, zs)` | The sequence obtained by concatenating the elements of `xs, ys, zs`. | +| `S.fill(n){e}` | A sequence of length `n` where each element is computed by expression `e`. | +| `S.fill(m, n){e}` | A sequence of sequences of dimension `m×n` where each element is computed by expression `e`. (exists also in higher dimensions). | +| `S.tabulate(n){f}` | A sequence of length `n` where the element at each index i is computed by `f(i)`. | +| `S.tabulate(m, n){f}` | A sequence of sequences of dimension `m×n` where the element at each index `(i, j)` is computed by `f(i, j)`. (exists also in higher dimensions). | +| `S.range(start, end)` | The sequence of integers `start` ... `end-1`. | +| `S.range(start, end, step)`| The sequence of integers starting with `start` and progressing by `step` increments up to, and excluding, the `end` value. | +| `S.iterate(x, n)(f)` | The sequence of length `n` with elements `x`, `f(x)`, `f(f(x))`, ... | diff --git a/_overviews/collections/equality.md b/_overviews/collections/equality.md new file mode 100644 index 0000000000..0e41a5ccd9 --- /dev/null +++ b/_overviews/collections/equality.md @@ -0,0 +1,35 @@ +--- +layout: multipage-overview +title: Equality + +discourse: true + +partof: collections +overview-name: Collections + +num: 13 + +languages: [ja, zh-cn] +permalink: /overviews/collections/:title.html +--- + +The collection libraries have a uniform approach to equality and hashing. The idea is, first, to divide collections into sets, maps, and sequences. Collections in different categories are always unequal. For instance, `Set(1, 2, 3)` is unequal to `List(1, 2, 3)` even though they contain the same elements. On the other hand, within the same category, collections are equal if and only if they have the same elements (for sequences: the same elements in the same order). For example, `List(1, 2, 3) == Vector(1, 2, 3)`, and `HashSet(1, 2) == TreeSet(2, 1)`. + +It does not matter for the equality check whether a collection is mutable or immutable. For a mutable collection one simply considers its current elements at the time the equality test is performed. This means that a mutable collection might be equal to different collections at different times, depending what elements are added or removed. This is a potential trap when using a mutable collection as a key in a hashmap. Example: + + scala> import collection.mutable.{HashMap, ArrayBuffer} + import collection.mutable.{HashMap, ArrayBuffer} + scala> val buf = ArrayBuffer(1, 2, 3) + buf: scala.collection.mutable.ArrayBuffer[Int] = + ArrayBuffer(1, 2, 3) + scala> val map = HashMap(buf -> 3) + map: scala.collection.mutable.HashMap[scala.collection. + mutable.ArrayBuffer[Int],Int] = Map((ArrayBuffer(1, 2, 3),3)) + scala> map(buf) + res13: Int = 3 + scala> buf(0) += 1 + scala> map(buf) + java.util.NoSuchElementException: key not found: + ArrayBuffer(2, 2, 3) + +In this example, the selection in the last line will most likely fail because the hash-code of the array `xs` has changed in the second-to-last line. Therefore, the hash-code-based lookup will look at a different place than the one where `xs` was stored. diff --git a/_overviews/collections/introduction.md b/_overviews/collections/introduction.md new file mode 100644 index 0000000000..bf16363308 --- /dev/null +++ b/_overviews/collections/introduction.md @@ -0,0 +1,100 @@ +--- +layout: multipage-overview +title: Introduction + +discourse: true + +partof: collections +overview-name: Collections + +num: 1 + +languages: [ja, zh-cn] +permalink: /overviews/collections/:title.html +--- + +**Martin Odersky, and Lex Spoon** + +In the eyes of many, the new collections framework is the most significant +change in the Scala 2.8 release. Scala had collections before (and in fact the new +framework is largely compatible with them). But it's only 2.8 that +provides a common, uniform, and all-encompassing framework for +collection types. + +Even though the additions to collections are subtle at first glance, +the changes they can provoke in your programming style can be +profound. In fact, quite often it's as if you work on a higher-level +with the basic building blocks of a program being whole collections +instead of their elements. This new style of programming requires some +adaptation. Fortunately, the adaptation is helped by several nice +properties of the new Scala collections. They are easy to use, +concise, safe, fast, universal. + +**Easy to use:** A small vocabulary of 20-50 methods is +enough to solve most collection problems in a couple of operations. No +need to wrap your head around complicated looping structures or +recursions. Persistent collections and side-effect-free operations mean +that you need not worry about accidentally corrupting existing +collections with new data. Interference between iterators and +collection updates is eliminated. + +**Concise:** You can achieve with a single word what used to +take one or several loops. You can express functional operations with +lightweight syntax and combine operations effortlessly, so that the result +feels like a custom algebra. + +**Safe:** This one has to be experienced to sink in. The +statically typed and functional nature of Scala's collections means +that the overwhelming majority of errors you might make are caught at +compile-time. The reason is that (1) the collection operations +themselves are heavily used and therefore well +tested. (2) the usages of the collection operation make inputs and +output explicit as function parameters and results. (3) These explicit +inputs and outputs are subject to static type checking. The bottom line +is that the large majority of misuses will manifest themselves as type +errors. It's not at all uncommon to have programs of several hundred +lines run at first try. + +**Fast:** Collection operations are tuned and optimized in the +libraries. As a result, using collections is typically quite +efficient. You might be able to do a little bit better with carefully +hand-tuned data structures and operations, but you might also do a lot +worse by making some suboptimal implementation decisions along the +way. What's more, collections have been recently adapted to parallel +execution on multi-cores. Parallel collections support the same +operations as sequential ones, so no new operations need to be learned +and no code needs to be rewritten. You can turn a sequential collection into a +parallel one simply by invoking the `par` method. + +**Universal:** Collections provide the same operations on +any type where it makes sense to do so. So you can achieve a lot with +a fairly small vocabulary of operations. For instance, a string is +conceptually a sequence of characters. Consequently, in Scala +collections, strings support all sequence operations. The same holds +for arrays. + +**Example:** Here's one line of code that demonstrates many of the +advantages of Scala's collections. + + val (minors, adults) = people partition (_.age < 18) + +It's immediately clear what this operation does: It partitions a +collection of `people` into `minors` and `adults` depending on +their age. Because the `partition` method is defined in the root +collection type `TraversableLike`, this code works for any kind of +collection, including arrays. The resulting `minors` and `adults` +collections will be of the same type as the `people` collection. + +This code is much more concise than the one to three loops required for +traditional collection processing (three loops for an array, because +the intermediate results need to be buffered somewhere else). Once +you have learned the basic collection vocabulary you will also find +writing this code is much easier and safer than writing explicit +loops. Furthermore, the `partition` operation is quite fast, and will +get even faster on parallel collections on multi-cores. (Parallel +collections have been released +as part of Scala 2.9.) + +This document provides an in depth discussion of the APIs of the +Scala collections classes from a user perspective. It takes you on +a tour of all the fundamental classes and the methods they define. diff --git a/_overviews/collections/iterators.md b/_overviews/collections/iterators.md new file mode 100644 index 0000000000..d5cce7da68 --- /dev/null +++ b/_overviews/collections/iterators.md @@ -0,0 +1,219 @@ +--- +layout: multipage-overview +title: Iterators + +discourse: true + +partof: collections +overview-name: Collections + +num: 15 + +languages: [ja, zh-cn] +permalink: /overviews/collections/:title.html +--- + +An iterator is not a collection, but rather a way to access the elements of a collection one by one. The two basic operations on an iterator `it` are `next` and `hasNext`. A call to `it.next()` will return the next element of the iterator and advance the state of the iterator. Calling `next` again on the same iterator will then yield the element one beyond the one returned previously. If there are no more elements to return, a call to `next` will throw a `NoSuchElementException`. You can find out whether there are more elements to return using [Iterator](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Iterator.html)'s `hasNext` method. + +The most straightforward way to "step through" all the elements returned by an iterator `it` uses a while-loop: + + while (it.hasNext) + println(it.next()) + +Iterators in Scala also provide analogues of most of the methods that you find in the `Traversable`, `Iterable` and `Seq` classes. For instance, they provide a `foreach` method which executes a given procedure on each element returned by an iterator. Using `foreach`, the loop above could be abbreviated to: + + it foreach println + +As always, for-expressions can be used as an alternate syntax for expressions involving `foreach`, `map`, `withFilter`, and `flatMap`, so yet another way to print all elements returned by an iterator would be: + + for (elem <- it) println(elem) + +There's an important difference between the foreach method on iterators and the same method on traversable collections: When called on an iterator, `foreach` will leave the iterator at its end when it is done. So calling `next` again on the same iterator will fail with a `NoSuchElementException`. By contrast, when called on a collection, `foreach` leaves the number of elements in the collection unchanged (unless the passed function adds to removes elements, but this is discouraged, because it may lead to surprising results). + +The other operations that Iterator has in common with `Traversable` have the same property. For instance, iterators provide a `map` method, which returns a new iterator: + + scala> val it = Iterator("a", "number", "of", "words") + it: Iterator[java.lang.String] = non-empty iterator + scala> it.map(_.length) + res1: Iterator[Int] = non-empty iterator + scala> res1 foreach println + 1 + 6 + 2 + 5 + scala> it.next() + java.util.NoSuchElementException: next on empty iterator + +As you can see, after the call to `it.map`, the `it` iterator has advanced to its end. + +Another example is the `dropWhile` method, which can be used to find the first elements of an iterator that has a certain property. For instance, to find the first word in the iterator above that has at least two characters you could write: + + scala> val it = Iterator("a", "number", "of", "words") + it: Iterator[java.lang.String] = non-empty iterator + scala> it dropWhile (_.length < 2) + res4: Iterator[java.lang.String] = non-empty iterator + scala> it.next() + res5: java.lang.String = number + +Note again that `it` was changed by the call to `dropWhile`: it now points to the second word "number" in the list. +In fact, `it` and the result `res4` returned by `dropWhile` will return exactly the same sequence of elements. + +One way to circumvent this behavior is to `duplicate` the underlying iterator instead of calling methods on it directly. +The _two_ iterators that result will each return exactly the same elements as the underlying iterator `it`: + + scala> val (words, ns) = Iterator("a", "number", "of", "words").duplicate + words: Iterator[String] = non-empty iterator + ns: Iterator[String] = non-empty iterator + + scala> val shorts = words.filter(_.length < 3).toList + shorts: List[String] = List(a, of) + + scala> val count = ns.map(_.length).sum + count: Int = 14 + +The two iterators work independently: advancing one does not affect the other, so that each can be +destructively modified by invoking arbitrary methods. This creates the illusion of iterating over +the elements twice, but the effect is achieved through internal buffering. +As usual, the underlying iterator `it` cannot be used directly and must be discarded. + +In summary, iterators behave like collections _if one never accesses an iterator again after invoking a method on it_. The Scala collection libraries make this explicit with an abstraction [TraversableOnce](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/TraversableOnce.html), which is a common superclass of [Traversable](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Traversable.html) and [Iterator](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Iterator.html). As the name implies, `TraversableOnce` objects can be traversed using `foreach` but the state of that object after the traversal is not specified. If the `TraversableOnce` object is in fact an `Iterator`, it will be at its end after the traversal, but if it is a `Traversable`, it will still exist as before. A common use case of `TraversableOnce` is as an argument type for methods that can take either an iterator or a traversable as argument. An example is the appending method `++` in class `Traversable`. It takes a `TraversableOnce` parameter, so you can append elements coming from either an iterator or a traversable collection. + +All operations on iterators are summarized below. + +### Operations in class Iterator + +| WHAT IT IS | WHAT IT DOES | +| ------ | ------ | +| **Abstract Methods:** | | +| `it.next()` | Returns next element on iterator and advances past it. | +| `it.hasNext` | Returns `true` if `it` can return another element. | +| **Variations:** | | +| `it.buffered` | A buffered iterator returning all elements of `it`. | +| `it grouped size` | An iterator that yields the elements returned by `it` in fixed-sized sequence "chunks". | +| `xs sliding size` | An iterator that yields the elements returned by `it` in sequences representing a sliding fixed-sized window. | +| **Duplication:** | | +| `it.duplicate` | A pair of iterators that each independently return all elements of `it`. | +| **Additions:** | | +| `it ++ jt` | An iterator returning all elements returned by iterator `it`, followed by all elements returned by iterator `jt`. | +| `it padTo (len, x)` | The iterator that first returns all elements of `it` and then follows that by copies of `x` until length `len` elements are returned overall. | +| **Maps:** | | +| `it map f` | The iterator obtained from applying the function `f` to every element returned from `it`. | +| `it flatMap f` | The iterator obtained from applying the iterator-valued function f to every element in `it` and appending the results. | +| `it collect f` | The iterator obtained from applying the partial function `f` to every element in `it` for which it is defined and collecting the results. | +| **Conversions:** | | +| `it.toArray` | Collects the elements returned by `it` in an array. | +| `it.toList` | Collects the elements returned by `it` in a list. | +| `it.toIterable` | Collects the elements returned by `it` in an iterable. | +| `it.toSeq` | Collects the elements returned by `it` in a sequence. | +| `it.toIndexedSeq` | Collects the elements returned by `it` in an indexed sequence. | +| `it.toStream` | Collects the elements returned by `it` in a stream. | +| `it.toSet` | Collects the elements returned by `it` in a set. | +| `it.toMap` | Collects the key/value pairs returned by `it` in a map. | +| **Copying:** | | +| `it copyToBuffer buf` | Copies all elements returned by `it` to buffer `buf`. | +| `it copyToArray(arr, s, n)`| Copies at most `n` elements returned by `it` to array `arr` starting at index `s`. The last two arguments are optional. | +| **Size Info:** | | +| `it.isEmpty` | Test whether the iterator is empty (opposite of `hasNext`). | +| `it.nonEmpty` | Test whether the collection contains elements (alias of `hasNext`). | +| `it.size` | The number of elements returned by `it`. Note: `it` will be at its end after this operation! | +| `it.length` | Same as `it.size`. | +| `it.hasDefiniteSize` | Returns `true` if `it` is known to return finitely many elements (by default the same as `isEmpty`). | +| **Element Retrieval Index Search:**| | +| `it find p` | An option containing the first element returned by `it` that satisfies `p`, or `None` is no element qualifies. Note: The iterator advances to after the element, or, if none is found, to the end. | +| `it indexOf x` | The index of the first element returned by `it` that equals `x`. Note: The iterator advances past the position of this element. | +| `it indexWhere p` | The index of the first element returned by `it` that satisfies `p`. Note: The iterator advances past the position of this element. | +| **Subiterators:** | | +| `it take n` | An iterator returning of the first `n` elements of `it`. Note: it will advance to the position after the `n`'th element, or to its end, if it contains less than `n` elements. | +| `it drop n` | The iterator that starts with the `(n+1)`'th element of `it`. Note: `it` will advance to the same position. | +| `it slice (m,n)` | The iterator that returns a slice of the elements returned from it, starting with the `m`'th element and ending before the `n`'th element. | +| `it takeWhile p` | An iterator returning elements from `it` as long as condition `p` is true. | +| `it dropWhile p` | An iterator skipping elements from `it` as long as condition `p` is `true`, and returning the remainder. | +| `it filter p` | An iterator returning all elements from `it` that satisfy the condition `p`. | +| `it withFilter p` | Same as `it` filter `p`. Needed so that iterators can be used in for-expressions. | +| `it filterNot p` | An iterator returning all elements from `it` that do not satisfy the condition `p`. | +| **Subdivisions:** | | +| `it partition p` | Splits `it` into a pair of two iterators: one returning all elements from `it` that satisfy the predicate `p`, the other returning all elements from `it` that do not. | +| `it span p` | Splits `it` into a pair of two iterators: one returning all elements of the prefix of `it` that satisfy the predicate `p`, the other returning all remaining elements of `it`. | +| **Element Conditions:** | | +| `it forall p` | A boolean indicating whether the predicate p holds for all elements returned by `it`. | +| `it exists p` | A boolean indicating whether the predicate p holds for some element in `it`. | +| `it count p` | The number of elements in `it` that satisfy the predicate `p`. | +| **Folds:** | | +| `it.foldLeft(z)(op)` | Apply binary operation `op` between successive elements returned by `it`, going left to right and starting with `z`. | +| `it.foldRight(z)(op)` | Apply binary operation `op` between successive elements returned by `it`, going right to left and starting with `z`. | +| `it reduceLeft op` | Apply binary operation `op` between successive elements returned by non-empty iterator `it`, going left to right. | +| `it reduceRight op` | Apply binary operation `op` between successive elements returned by non-empty iterator `it`, going right to left. | +| **Specific Folds:** | | +| `it.sum` | The sum of the numeric element values returned by iterator `it`. | +| `it.product` | The product of the numeric element values returned by iterator `it`. | +| `it.min` | The minimum of the ordered element values returned by iterator `it`. | +| `it.max` | The maximum of the ordered element values returned by iterator `it`. | +| **Zippers:** | | +| `it zip jt` | An iterator of pairs of corresponding elements returned from iterators `it` and `jt`. | +| `it zipAll (jt, x, y)` | An iterator of pairs of corresponding elements returned from iterators `it` and `jt`, where the shorter iterator is extended to match the longer one by appending elements `x` or `y`. | +| `it.zipWithIndex` | An iterator of pairs of elements returned from `it` with their indices. | +| **Update:** | | +| `it patch (i, jt, r)` | The iterator resulting from `it` by replacing `r` elements starting with `i` by the patch iterator `jt`. | +| **Comparison:** | | +| `it sameElements jt` | A test whether iterators it and `jt` return the same elements in the same order. Note: Using the iterators after this operation is undefined and subject to change. | +| **Strings:** | | +| `it addString (b, start, sep, end)`| Adds a string to `StringBuilder` `b` which shows all elements returned by `it` between separators `sep` enclosed in strings `start` and `end`. `start`, `sep`, `end` are all optional. | +| `it mkString (start, sep, end)` | Converts the collection to a string which shows all elements returned by `it` between separators `sep` enclosed in strings `start` and `end`. `start`, `sep`, `end` are all optional. | + +### Buffered iterators + +Sometimes you want an iterator that can "look ahead", so that you can inspect the next element to be returned without advancing past that element. Consider for instance, the task to skip leading empty strings from an iterator that returns a sequence of strings. You might be tempted to write the following + + + def skipEmptyWordsNOT(it: Iterator[String]) = + while (it.next().isEmpty) {} + +But looking at this code more closely, it's clear that this is wrong: The code will indeed skip leading empty strings, but it will also advance `it` past the first non-empty string! + +The solution to this problem is to use a buffered iterator. Class [BufferedIterator](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/BufferedIterator.html) is a subclass of [Iterator](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Iterator.html), which provides one extra method, `head`. Calling `head` on a buffered iterator will return its first element but will not advance the iterator. Using a buffered iterator, skipping empty words can be written as follows. + + def skipEmptyWords(it: BufferedIterator[String]) = + while (it.head.isEmpty) { it.next() } + +Every iterator can be converted to a buffered iterator by calling its `buffered` method. Here's an example: + + scala> val it = Iterator(1, 2, 3, 4) + it: Iterator[Int] = non-empty iterator + scala> val bit = it.buffered + bit: scala.collection.BufferedIterator[Int] = non-empty iterator + scala> bit.head + res10: Int = 1 + scala> bit.next() + res11: Int = 1 + scala> bit.next() + res12: Int = 2 + scala> bit.optionHead + res13: Option[Int] = Some(3) + +Note that calling `head` on the buffered iterator `bit` does not advance it. Therefore, the subsequent call `bit.next()` returns the same value as `bit.head`. + +As usual, the underlying iterator must not be used directly and must be discarded. + +The buffered iterator only buffers the next element when `head` is invoked. Other derived iterators, +such as those produced by `duplicate` and `partition`, may buffer arbitrary subsequences of the +underlying iterator. But iterators can be efficiently joined by adding them together with `++`: + + scala> def collapse(it: Iterator[Int]) = if (!it.hasNext) Iterator.empty else { + | var head = it.next + | val rest = if (head == 0) it.dropWhile(_ == 0) else it + | Iterator.single(head) ++ rest + | } + collapse: (it: Iterator[Int])Iterator[Int] + + scala> def collapse(it: Iterator[Int]) = { + | val (zeros, rest) = it.span(_ == 0) + | zeros.take(1) ++ rest + | } + collapse: (it: Iterator[Int])Iterator[Int] + + scala> collapse(Iterator(0, 0, 0, 1, 2, 3, 4)).toList + res14: List[Int] = List(0, 1, 2, 3, 4) + +In the second version of `collapse`, the unconsumed zeros are buffered internally. +In the first version, any leading zeros are dropped and the desired result constructed +as a concatenated iterator, which simply calls its two constituent iterators in turn. diff --git a/_overviews/collections/maps.md b/_overviews/collections/maps.md new file mode 100644 index 0000000000..e55f3bf144 --- /dev/null +++ b/_overviews/collections/maps.md @@ -0,0 +1,168 @@ +--- +layout: multipage-overview +title: Maps + +discourse: true + +partof: collections +overview-name: Collections + +num: 7 + +languages: [ja, zh-cn] +permalink: /overviews/collections/:title.html +--- + +A [Map](http://www.scala-lang.org/api/current/scala/collection/Map.html) is an [Iterable](http://www.scala-lang.org/api/current/scala/collection/Iterable.html) consisting of pairs of keys and values (also named _mappings_ or _associations_). Scala's [Predef](http://www.scala-lang.org/api/current/scala/Predef$.html) object offers an implicit conversion that lets you write `key -> value` as an alternate syntax for the pair `(key, value)`. For instance `Map("x" -> 24, "y" -> 25, "z" -> 26)` means exactly the same as `Map(("x", 24), ("y", 25), ("z", 26))`, but reads better. + +The fundamental operations on maps are similar to those on sets. They are summarized in the following table and fall into the following categories: + +* **Lookup** operations `apply`, `get`, `getOrElse`, `contains`, and `isDefinedAt`. These turn maps into partial functions from keys to values. The fundamental lookup method for a map is: `def get(key): Option[Value]`. The operation "`m get key`" tests whether the map contains an association for the given `key`. If so, it returns the associated value in a `Some`. If no key is defined in the map, `get` returns `None`. Maps also define an `apply` method that returns the value associated with a given key directly, without wrapping it in an `Option`. If the key is not defined in the map, an exception is raised. +* **Additions and updates** `+`, `++`, `updated`, which let you add new bindings to a map or change existing bindings. +* **Removals** `-`, `--`, which remove bindings from a map. +* **Subcollection producers** `keys`, `keySet`, `keysIterator`, `values`, `valuesIterator`, which return a map's keys and values separately in various forms. +* **Transformations** `filterKeys` and `mapValues`, which produce a new map by filtering and transforming bindings of an existing map. + +### Operations in Class Map ### + +| WHAT IT IS | WHAT IT DOES | +| ------ | ------ | +| **Lookups:** | | +| `ms get k` |The value associated with key `k` in map `ms` as an option, `None` if not found.| +| `ms(k)` |(or, written out, `ms apply k`) The value associated with key `k` in map `ms`, or exception if not found.| +| `ms getOrElse (k, d)` |The value associated with key `k` in map `ms`, or the default value `d` if not found.| +| `ms contains k` |Tests whether `ms` contains a mapping for key `k`.| +| `ms isDefinedAt k` |Same as `contains`. | +| **Additions and Updates:**| | +| `ms + (k -> v)` |The map containing all mappings of `ms` as well as the mapping `k -> v` from key `k` to value `v`.| +| `ms + (k -> v, l -> w)` |The map containing all mappings of `ms` as well as the given key/value pairs.| +| `ms ++ kvs` |The map containing all mappings of `ms` as well as all key/value pairs of `kvs`.| +| `ms updated (k, v)` |Same as `ms + (k -> v)`.| +| **Removals:** | | +| `ms - k` |The map containing all mappings of `ms` except for any mapping of key `k`.| +| `ms - (k, l, m)` |The map containing all mappings of `ms` except for any mapping with the given keys.| +| `ms -- ks` |The map containing all mappings of `ms` except for any mapping with a key in `ks`.| +| **Subcollections:** | | +| `ms.keys` |An iterable containing each key in `ms`. | +| `ms.keySet` |A set containing each key in `ms`. | +| `ms.keyIterator` |An iterator yielding each key in `ms`. | +| `ms.values` |An iterable containing each value associated with a key in `ms`.| +| `ms.valuesIterator` |An iterator yielding each value associated with a key in `ms`.| +| **Transformation:** | | +| `ms filterKeys p` |A map view containing only those mappings in `ms` where the key satisfies predicate `p`.| +| `ms mapValues f` |A map view resulting from applying function `f` to each value associated with a key in `ms`.| + +Mutable maps support in addition the operations summarized in the following table. + + +### Operations in Class mutable.Map ### + +| WHAT IT IS | WHAT IT DOES | +| ------ | ------ | +| **Additions and Updates:**| | +| `ms(k) = v` |(Or, written out, `ms.update(x, v)`). Adds mapping from key `k` to value `v` to map ms as a side effect, overwriting any previous mapping of `k`.| +| `ms += (k -> v)` |Adds mapping from key `k` to value `v` to map `ms` as a side effect and returns `ms` itself.| +| `ms += (k -> v, l -> w)` |Adds the given mappings to `ms` as a side effect and returns `ms` itself.| +| `ms ++= kvs` |Adds all mappings in `kvs` to `ms` as a side effect and returns `ms` itself.| +| `ms put (k, v)` |Adds mapping from key `k` to value `v` to `ms` and returns any value previously associated with `k` as an option.| +| `ms getOrElseUpdate (k, d)`|If key `k` is defined in map `ms`, return its associated value. Otherwise, update `ms` with the mapping `k -> d` and return `d`.| +| **Removals:**| | +| `ms -= k` |Removes mapping with key `k` from ms as a side effect and returns `ms` itself.| +| `ms -= (k, l, m)` |Removes mappings with the given keys from `ms` as a side effect and returns `ms` itself.| +| `ms --= ks` |Removes all keys in `ks` from `ms` as a side effect and returns `ms` itself.| +| `ms remove k` |Removes any mapping with key `k` from `ms` and returns any value previously associated with `k` as an option.| +| `ms retain p` |Keeps only those mappings in `ms` that have a key satisfying predicate `p`.| +| `ms.clear()` |Removes all mappings from `ms`. | +| **Transformation:** | | +| `ms transform f` |Transforms all associated values in map `ms` with function `f`.| +| **Cloning:** | | +| `ms.clone` |Returns a new mutable map with the same mappings as `ms`.| + +The addition and removal operations for maps mirror those for sets. Like sets, mutable maps also support the non-destructive addition operations `+`, `-`, and `updated`, but they are used less frequently because they involve a copying of the mutable map. Instead, a mutable map `m` is usually updated "in place", using the two variants `m(key) = value` or `m += (key -> value)`. There is also the variant `m put (key, value)`, which returns an `Option` value that contains the value previously associated with `key`, or `None` if the `key` did not exist in the map before. + +The `getOrElseUpdate` is useful for accessing maps that act as caches. Say you have an expensive computation triggered by invoking a function `f`: + + scala> def f(x: String) = { + println("taking my time."); sleep(100) + x.reverse } + f: (x: String)String + +Assume further that `f` has no side-effects, so invoking it again with the same argument will always yield the same result. In that case you could save time by storing previously computed bindings of argument and results of `f` in a map and only computing the result of `f` if a result of an argument was not found there. One could say the map is a _cache_ for the computations of the function `f`. + + scala> val cache = collection.mutable.Map[String, String]() + cache: scala.collection.mutable.Map[String,String] = Map() + +You can now create a more efficient caching version of the `f` function: + + scala> def cachedF(s: String) = cache.getOrElseUpdate(s, f(s)) + cachedF: (s: String)String + scala> cachedF("abc") + taking my time. + res3: String = cba + scala> cachedF("abc") + res4: String = cba + +Note that the second argument to `getOrElseUpdate` is "by-name", so the computation of `f("abc")` above is only performed if `getOrElseUpdate` requires the value of its second argument, which is precisely if its first argument is not found in the `cache` map. You could also have implemented `cachedF` directly, using just basic map operations, but it would take more code to do so: + + def cachedF(arg: String) = cache get arg match { + case Some(result) => result + case None => + val result = f(x) + cache(arg) = result + result + } + +### Synchronized Sets and Maps ### + +To get a thread-safe mutable map, you can mix the `SynchronizedMap` trait into whatever particular map implementation you desire. For example, you can mix `SynchronizedMap` into `HashMap`, as shown in the code below. This example begins with an import of two traits, `Map` and `SynchronizedMap`, and one class, `HashMap`, from package `scala.collection.mutable`. The rest of the example is the definition of singleton object `MapMaker`, which declares one method, `makeMap`. The `makeMap` method declares its result type to be a mutable map of string keys to string values. + + import scala.collection.mutable.{Map, + SynchronizedMap, HashMap} + object MapMaker { + def makeMap: Map[String, String] = { + new HashMap[String, String] with + SynchronizedMap[String, String] { + override def default(key: String) = + "Why do you want to know?" + } + } + } + +
      Mixing in the `SynchronizedMap` trait.
      + +The first statement inside the body of `makeMap` constructs a new mutable `HashMap` that mixes in the `SynchronizedMap` trait: + + new HashMap[String, String] with + SynchronizedMap[String, String] + +Given this code, the Scala compiler will generate a synthetic subclass of `HashMap` that mixes in `SynchronizedMap`, and create (and return) an instance of it. This synthetic class will also override a method named `default`, because of this code: + + override def default(key: String) = + "Why do you want to know?" + +If you ask a map to give you the value for a particular key, but it doesn't have a mapping for that key, you'll by default get a `NoSuchElementException`. If you define a new map class and override the `default` method, however, your new map will return the value returned by `default` when queried with a non-existent key. Thus, the synthetic `HashMap` subclass generated by the compiler from the code in the synchronized map code will return the somewhat curt response string, `"Why do you want to know?"`, when queried with a non-existent key. + +Because the mutable map returned by the `makeMap` method mixes in the `SynchronizedMap` trait, it can be used by multiple threads at once. Each access to the map will be synchronized. Here's an example of the map being used, by one thread, in the interpreter: + + scala> val capital = MapMaker.makeMap + capital: scala.collection.mutable.Map[String,String] = Map() + scala> capital ++ List("US" -> "Washington", + "France" -> "Paris", "Japan" -> "Tokyo") + res0: scala.collection.mutable.Map[String,String] = + Map(France -> Paris, US -> Washington, Japan -> Tokyo) + scala> capital("Japan") + res1: String = Tokyo + scala> capital("New Zealand") + res2: String = Why do you want to know? + scala> capital += ("New Zealand" -> "Wellington") + scala> capital("New Zealand") + res3: String = Wellington + +You can create synchronized sets similarly to the way you create synchronized maps. For example, you could create a synchronized `HashSet` by mixing in the `SynchronizedSet` trait, like this: + + import scala.collection.mutable + val synchroSet = + new mutable.HashSet[Int] with + mutable.SynchronizedSet[Int] + +Finally, if you are thinking of using synchronized collections, you may also wish to consider the concurrent collections of `java.util.concurrent` instead. diff --git a/_overviews/collections/migrating-from-scala-27.md b/_overviews/collections/migrating-from-scala-27.md new file mode 100644 index 0000000000..c2c2bd9935 --- /dev/null +++ b/_overviews/collections/migrating-from-scala-27.md @@ -0,0 +1,47 @@ +--- +layout: multipage-overview +title: Migrating from Scala 2.7 + +discourse: true + +partof: collections +overview-name: Collections + +num: 18 + +languages: [ja, zh-cn] +permalink: /overviews/collections/:title.html +--- + +Porting your existing Scala applications to use the new collections should be almost automatic. There are only a couple of possible issues to take care of. + +Generally, the old functionality of Scala 2.7 collections has been left in place. Some features have been deprecated, which means they will removed in some future release. You will get a _deprecation warning_ when you compile code that makes use of these features in Scala 2.8. In a few places deprecation was unfeasible, because the operation in question was retained in 2.8, but changed in meaning or performance characteristics. These cases will be flagged with _migration warnings_ when compiled under 2.8. To get full deprecation and migration warnings with suggestions how to change your code, pass the `-deprecation` and `-Xmigration` flags to `scalac` (note that `-Xmigration` is an extended option, so it starts with an `X`). You can also pass the same options to the `scala` REPL to get the warnings in an interactive session. Example: + + >scala -deprecation -Xmigration + Welcome to Scala version 2.8.0.final + Type in expressions to have them evaluated. + Type :help for more information. + scala> val xs = List((1, 2), (3, 4)) + xs: List[(Int, Int)] = List((1,2), (3,4)) + scala> List.unzip(xs) + :7: warning: method unzip in object List is deprecated: use xs.unzip instead of List.unzip(xs) + List.unzip(xs) + ^ + res0: (List[Int], List[Int]) = (List(1, 3),List(2, 4)) + scala> xs.unzip + res1: (List[Int], List[Int]) = (List(1, 3),List(2, 4)) + scala> val m = xs.toMap + m: scala.collection.immutable.Map[Int,Int] = Map((1,2), (3,4)) + scala> m.keys + :8: warning: method keys in trait MapLike has changed semantics: + As of 2.8, keys returns Iterable[A] rather than Iterator[A]. + m.keys + ^ + res2: Iterable[Int] = Set(1, 3) + +There are two parts of the old libraries which have been replaced wholesale, and for which deprecation warnings were not feasible. + +1. The previous `scala.collection.jcl` package is gone. This package tried to mimick some of the Java collection library design in Scala, but in doing so broke many symmetries. Most people who wanted Java collections bypassed `jcl` and used `java.util` directly. Scala 2.8 offers automatic conversion mechanisms between both collection libraries in the [JavaConversions]({{ site.baseurl }}/overviews/collections/conversions-between-java-and-scala-collections.html) object which replaces the `jcl` package. +2. Projections have been generalized and cleaned up and are now available as views. It seems that projections were used rarely, so not much code should be affected by this change. + +So, if your code uses either `jcl` or projections there might be some minor rewriting to do. diff --git a/_overviews/collections/overview.md b/_overviews/collections/overview.md new file mode 100644 index 0000000000..e0a7a33c29 --- /dev/null +++ b/_overviews/collections/overview.md @@ -0,0 +1,146 @@ +--- +layout: multipage-overview +title: Mutable and Immutable Collections + +discourse: true + +partof: collections +overview-name: Collections + +num: 2 + +languages: [ja, zh-cn] +permalink: /overviews/collections/:title.html +--- + +Scala collections systematically distinguish between mutable and +immutable collections. A _mutable_ collection can be updated or +extended in place. This means you can change, add, or remove elements +of a collection as a side effect. _Immutable_ collections, by +contrast, never change. You have still operations that simulate +additions, removals, or updates, but those operations will in each +case return a new collection and leave the old collection unchanged. + +All collection classes are found in the package `scala.collection` or +one of its sub-packages `mutable`, `immutable`, and `generic`. Most +collection classes needed by client code exist in three variants, +which are located in packages `scala.collection`, +`scala.collection.immutable`, and `scala.collection.mutable`, +respectively. Each variant has different characteristics with respect +to mutability. + +A collection in package `scala.collection.immutable` is guaranteed to +be immutable for everyone. Such a collection will never change after +it is created. Therefore, you can rely on the fact that accessing the +same collection value repeatedly at different points in time will +always yield a collection with the same elements. + +A collection in package `scala.collection.mutable` is known to have +some operations that change the collection in place. So dealing with +mutable collection means you need to understand which code changes +which collection when. + +A collection in package `scala.collection` can be either mutable or +immutable. For instance, [collection.IndexedSeq\[T\]](http://www.scala-lang.org/api/current/scala/collection/IndexedSeq.html) +is a superclass of both [collection.immutable.IndexedSeq\[T\]](http://www.scala-lang.org/api/current/scala/collection/immutable/IndexedSeq.html) +and +[collection.mutable.IndexedSeq\[T\]](http://www.scala-lang.org/api/current/scala/collection/mutable/IndexedSeq.html) +Generally, the root collections in +package `scala.collection` define the same interface as the immutable +collections, and the mutable collections in package +`scala.collection.mutable` typically add some side-effecting +modification operations to this immutable interface. + +The difference between root collections and immutable collections is +that clients of an immutable collection have a guarantee that nobody +can mutate the collection, whereas clients of a root collection only +promise not to change the collection themselves. Even though the +static type of such a collection provides no operations for modifying +the collection, it might still be possible that the run-time type is a +mutable collection which can be changed by other clients. + +By default, Scala always picks immutable collections. For instance, if +you just write `Set` without any prefix or without having imported +`Set` from somewhere, you get an immutable set, and if you write +`Iterable` you get an immutable iterable collection, because these +are the default bindings imported from the `scala` package. To get +the mutable default versions, you need to write explicitly +`collection.mutable.Set`, or `collection.mutable.Iterable`. + +A useful convention if you want to use both mutable and immutable +versions of collections is to import just the package +`collection.mutable`. + + import scala.collection.mutable + +Then a word like `Set` without a prefix still refers to an immutable collection, +whereas `mutable.Set` refers to the mutable counterpart. + +The last package in the collection hierarchy is `collection.generic`. This +package contains building blocks for implementing +collections. Typically, collection classes defer the implementations +of some of their operations to classes in `generic`. Users of the +collection framework on the other hand should need to refer to +classes in `generic` only in exceptional circumstances. + +For convenience and backwards compatibility some important types have +aliases in the `scala` package, so you can use them by their simple +names without needing an import. An example is the `List` type, which +can be accessed alternatively as + + scala.collection.immutable.List // that's where it is defined + scala.List // via the alias in the scala package + List // because scala._ + // is always automatically imported + +Other types aliased are +[Traversable](http://www.scala-lang.org/api/current/scala/collection/Traversable.html), [Iterable](http://www.scala-lang.org/api/current/scala/collection/Iterable.html), [Seq](http://www.scala-lang.org/api/current/scala/collection/Seq.html), [IndexedSeq](http://www.scala-lang.org/api/current/scala/collection/IndexedSeq.html), [Iterator](http://www.scala-lang.org/api/current/scala/collection/Iterator.html), [Stream](http://www.scala-lang.org/api/current/scala/collection/immutable/Stream.html), [Vector](http://www.scala-lang.org/api/current/scala/collection/immutable/Vector.html), [StringBuilder](http://www.scala-lang.org/api/current/scala/collection/mutable/StringBuilder.html), and [Range](http://www.scala-lang.org/api/current/scala/collection/immutable/Range.html). + +The following figure shows all collections in package +`scala.collection`. These are all high-level abstract classes or traits, which +generally have mutable as well as immutable implementations. + +[]({{ site.baseurl }}/resources/images/collections.png) + +The following figure shows all collections in package `scala.collection.immutable`. + +[]({{ site.baseurl }}/resources/images/collections.immutable.png) + +And the following figure shows all collections in package `scala.collection.mutable`. + +[]({{ site.baseurl }}/resources/images/collections.mutable.png) + +(All three figures were generated by Matthias at decodified.com). + +## An Overview of the Collections API ## + +The most important collection classes are shown in the figures above. There is quite a bit of commonality shared by all these classes. For instance, every kind of collection can be created by the same uniform syntax, writing the collection class name followed by its elements: + + Traversable(1, 2, 3) + Iterable("x", "y", "z") + Map("x" -> 24, "y" -> 25, "z" -> 26) + Set(Color.red, Color.green, Color.blue) + SortedSet("hello", "world") + Buffer(x, y, z) + IndexedSeq(1.0, 2.0) + LinearSeq(a, b, c) + +The same principle also applies for specific collection implementations, such as: + + List(1, 2, 3) + HashMap("x" -> 24, "y" -> 25, "z" -> 26) + +All these collections get displayed with `toString` in the same way they are written above. + +All collections support the API provided by `Traversable`, but specialize types wherever this makes sense. For instance the `map` method in class `Traversable` returns another `Traversable` as its result. But this result type is overridden in subclasses. For instance, calling `map` on a `List` yields again a `List`, calling it on a `Set` yields again a `Set` and so on. + + scala> List(1, 2, 3) map (_ + 1) + res0: List[Int] = List(2, 3, 4) + scala> Set(1, 2, 3) map (_ * 2) + res0: Set[Int] = Set(2, 4, 6) + +This behavior which is implemented everywhere in the collections libraries is called the _uniform return type principle_. + +Most of the classes in the collections hierarchy exist in three variants: root, mutable, and immutable. The only exception is the Buffer trait which only exists as a mutable collection. + +In the following, we will review these classes one by one. diff --git a/_overviews/collections/performance-characteristics.md b/_overviews/collections/performance-characteristics.md new file mode 100644 index 0000000000..c43aeabb68 --- /dev/null +++ b/_overviews/collections/performance-characteristics.md @@ -0,0 +1,88 @@ +--- +layout: multipage-overview +title: Performance Characteristics + +discourse: true + +partof: collections +overview-name: Collections + +num: 12 + +languages: [ja, zh-cn] +permalink: /overviews/collections/:title.html +--- + +The previous explanations have made it clear that different collection types have different performance characteristics. That's often the primary reason for picking one collection type over another. You can see the performance characteristics of some common operations on collections summarized in the following two tables. + +Performance characteristics of sequence types: + +| | head | tail | apply | update| prepend | append | insert | +| -------- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | +| **immutable** | | | | | | | | +| `List` | C | C | L | L | C | L | - | +| `Stream` | C | C | L | L | C | L | - | +| `Vector` | eC | eC | eC | eC | eC | eC | - | +| `Stack` | C | C | L | L | C | L | L | +| `Queue` | aC | aC | L | L | C | C | - | +| `Range` | C | C | C | - | - | - | - | +| `String` | C | L | C | L | L | L | - | +| **mutable** | | | | | | | | +| `ArrayBuffer` | C | L | C | C | L | aC | L | +| `ListBuffer` | C | L | L | L | C | C | L | +|`StringBuilder`| C | L | C | C | L | aC | L | +| `MutableList` | C | L | L | L | C | C | L | +| `Queue` | C | L | L | L | C | C | L | +| `ArraySeq` | C | L | C | C | - | - | - | +| `Stack` | C | L | L | L | C | L | L | +| `ArrayStack` | C | L | C | C | aC | L | L | +| `Array` | C | L | C | C | - | - | - | + +Performance characteristics of set and map types: + +| | lookup | add | remove | min | +| -------- | ---- | ---- | ---- | ---- | +| **immutable** | | | | | +| `HashSet`/`HashMap`| eC | eC | eC | L | +| `TreeSet`/`TreeMap`| Log | Log | Log | Log | +| `BitSet` | C | L | L | eC1| +| `ListMap` | L | L | L | L | +| **mutable** | | | | | +| `HashSet`/`HashMap`| eC | eC | eC | L | +| `WeakHashMap` | eC | eC | eC | L | +| `BitSet` | C | aC | C | eC1| +| `TreeSet` | Log | Log | Log | Log | + +Footnote: 1 Assuming bits are densely packed. + +The entries in these two tables are explained as follows: + +| | | +| --- | ---- | +| **C** | The operation takes (fast) constant time. | +| **eC** | The operation takes effectively constant time, but this might depend on some assumptions such as maximum length of a vector or distribution of hash keys.| +| **aC** | The operation takes amortized constant time. Some invocations of the operation might take longer, but if many operations are performed on average only constant time per operation is taken. | +| **Log** | The operation takes time proportional to the logarithm of the collection size. | +| **L** | The operation is linear, that is it takes time proportional to the collection size. | +| **-** | The operation is not supported. | + +The first table treats sequence types--both immutable and mutable--with the following operations: + +| | | +| --- | ---- | +| **head** | Selecting the first element of the sequence. | +| **tail** | Producing a new sequence that consists of all elements except the first one. | +| **apply** | Indexing. | +| **update** | Functional update (with `updated`) for immutable sequences, side-effecting update (with `update` for mutable sequences). | +| **prepend**| Adding an element to the front of the sequence. For immutable sequences, this produces a new sequence. For mutable sequences it modified the existing sequence. | +| **append** | Adding an element and the end of the sequence. For immutable sequences, this produces a new sequence. For mutable sequences it modified the existing sequence. | +| **insert** | Inserting an element at an arbitrary position in the sequence. This is only supported directly for mutable sequences. | + +The second table treats mutable and immutable sets and maps with the following operations: + +| | | +| --- | ---- | +| **lookup** | Testing whether an element is contained in set, or selecting a value associated with a key. | +| **add** | Adding a new element to a set or key/value pair to a map. | +| **remove** | Removing an element from a set or a key from a map. | +| **min** | The smallest element of the set, or the smallest key of a map. | diff --git a/_overviews/collections/seqs.md b/_overviews/collections/seqs.md new file mode 100644 index 0000000000..76e33c3065 --- /dev/null +++ b/_overviews/collections/seqs.md @@ -0,0 +1,105 @@ +--- +layout: multipage-overview +title: The sequence traits Seq, IndexedSeq, and LinearSeq + +discourse: true + +partof: collections +overview-name: Collections + +num: 5 + +languages: [ja, zh-cn] +permalink: /overviews/collections/:title.html +--- + +The [Seq](http://www.scala-lang.org/api/current/scala/collection/Seq.html) trait represents sequences. A sequence is a kind of iterable that has a `length` and whose elements have fixed index positions, starting from `0`. + +The operations on sequences, summarized in the table below, fall into the following categories: + +* **Indexing and length** operations `apply`, `isDefinedAt`, `length`, `indices`, and `lengthCompare`. For a `Seq`, the `apply` operation means indexing; hence a sequence of type `Seq[T]` is a partial function that takes an `Int` argument (an index) and which yields a sequence element of type `T`. In other words `Seq[T]` extends `PartialFunction[Int, T]`. The elements of a sequence are indexed from zero up to the `length` of the sequence minus one. The `length` method on sequences is an alias of the `size` method of general collections. The `lengthCompare` method allows you to compare the lengths of a sequences with an Int even if the sequences has infinite length. +* **Index search operations** `indexOf`, `lastIndexOf`, `indexOfSlice`, `lastIndexOfSlice`, `indexWhere`, `lastIndexWhere`, `segmentLength`, `prefixLength`, which return the index of an element equal to a given value or matching some predicate. +* **Addition operations** `+:`, `:+`, `padTo`, which return new sequences obtained by adding elements at the front or the end of a sequence. +* **Update operations** `updated`, `patch`, which return a new sequence obtained by replacing some elements of the original sequence. +* **Sorting operations** `sorted`, `sortWith`, `sortBy`, which sort sequence elements according to various criteria. +* **Reversal operations** `reverse`, `reverseIterator`, `reverseMap`, which yield or process sequence elements in reverse order. +* **Comparisons** `startsWith`, `endsWith`, `contains`, `containsSlice`, `corresponds`, which relate two sequences or search an element in a sequence. +* **Multiset** operations `intersect`, `diff`, `union`, `distinct`, which perform set-like operations on the elements of two sequences or remove duplicates. + +If a sequence is mutable, it offers in addition a side-effecting `update` method, which lets sequence elements be updated. As always in Scala, syntax like `seq(idx) = elem` is just a shorthand for `seq.update(idx, elem)`, so `update` gives convenient assignment syntax for free. Note the difference between `update` and `updated`. `update` changes a sequence element in place, and is only available for mutable sequences. `updated` is available for all sequences and always returns a new sequence instead of modifying the original. + +### Operations in Class Seq ### + +| WHAT IT IS | WHAT IT DOES | +| ------ | ------ | +| **Indexing and Length:** | | +| `xs(i)` |(or, written out, `xs apply i`). The element of `xs` at index `i`.| +| `xs isDefinedAt i` |Tests whether `i` is contained in `xs.indices`.| +| `xs.length` |The length of the sequence (same as `size`).| +| `xs lengthCompare n` |Returns `-1` if `xs` is shorter than `n`, `+1` if it is longer, and `0` if it is of length `n`. Works even if the sequence is infinite, for example `Stream.from(1) lengthCompare 42` equals `+1`.| +| `xs.indices` |The index range of `xs`, extending from `0` to `xs.length - 1`.| +| **Index Search:** | | +| `xs indexOf x` |The index of the first element in `xs` equal to `x` (several variants exist).| +| `xs lastIndexOf x` |The index of the last element in `xs` equal to `x` (several variants exist).| +| `xs indexOfSlice ys` |The first index of `xs` such that successive elements starting from that index form the sequence `ys`.| +| `xs lastIndexOfSlice ys` |The last index of `xs` such that successive elements starting from that index form the sequence `ys`.| +| `xs indexWhere p` |The index of the first element in xs that satisfies `p` (several variants exist).| +| `xs segmentLength (p, i)`|The length of the longest uninterrupted segment of elements in `xs`, starting with `xs(i)`, that all satisfy the predicate `p`.| +| `xs prefixLength p` |The length of the longest prefix of elements in `xs` that all satisfy the predicate `p`.| +| **Additions:** | | +| `x +: xs` |A new sequence that consists of `x` prepended to `xs`.| +| `xs :+ x` |A new sequence that consists of `x` appended to `xs`.| +| `xs padTo (len, x)` |The sequence resulting from appending the value `x` to `xs` until length `len` is reached.| +| **Updates:** | | +| `xs patch (i, ys, r)` |The sequence resulting from replacing `r` elements of `xs` starting with `i` by the patch `ys`.| +| `xs updated (i, x)` |A copy of `xs` with the element at index `i` replaced by `x`.| +| `xs(i) = x` |(or, written out, `xs.update(i, x)`, only available for `mutable.Seq`s). Changes the element of `xs` at index `i` to `x`.| +| **Sorting:** | | +| `xs.sorted` |A new sequence obtained by sorting the elements of `xs` using the standard ordering of the element type of `xs`.| +| `xs sortWith lt` |A new sequence obtained by sorting the elements of `xs` using `lt` as comparison operation.| +| `xs sortBy f` |A new sequence obtained by sorting the elements of `xs`. Comparison between two elements proceeds by mapping the function `f` over both and comparing the results.| +| **Reversals:** | | +| `xs.reverse` |A sequence with the elements of `xs` in reverse order.| +| `xs.reverseIterator` |An iterator yielding all the elements of `xs` in reverse order.| +| `xs reverseMap f` |A sequence obtained by mapping `f` over the elements of `xs` in reverse order.| +| **Comparisons:** | | +| `xs startsWith ys` |Tests whether `xs` starts with sequence `ys` (several variants exist).| +| `xs endsWith ys` |Tests whether `xs` ends with sequence `ys` (several variants exist).| +| `xs contains x` |Tests whether `xs` has an element equal to `x`.| +| `xs containsSlice ys` |Tests whether `xs` has a contiguous subsequence equal to `ys`.| +| `(xs corresponds ys)(p)` |Tests whether corresponding elements of `xs` and `ys` satisfy the binary predicate `p`.| +| **Multiset Operations:** | | +| `xs intersect ys` |The multi-set intersection of sequences `xs` and `ys` that preserves the order of elements in `xs`.| +| `xs diff ys` |The multi-set difference of sequences `xs` and `ys` that preserves the order of elements in `xs`.| +| `xs union ys` |Multiset union; same as `xs ++ ys`.| +| `xs.distinct` |A subsequence of `xs` that contains no duplicated element.| + +Trait [Seq](http://www.scala-lang.org/api/current/scala/collection/Seq.html) has two subtraits [LinearSeq](http://www.scala-lang.org/api/current/scala/collection/IndexedSeq.html), and [IndexedSeq](http://www.scala-lang.org/api/current/scala/collection/IndexedSeq.html). These do not add any new operations, but each offers different performance characteristics: A linear sequence has efficient `head` and `tail` operations, whereas an indexed sequence has efficient `apply`, `length`, and (if mutable) `update` operations. Frequently used linear sequences are `scala.collection.immutable.List` and `scala.collection.immutable.Stream`. Frequently used indexed sequences are `scala.Array` and `scala.collection.mutable.ArrayBuffer`. The `Vector` class provides an interesting compromise between indexed and linear access. It has both effectively constant time indexing overhead and constant time linear access overhead. Because of this, vectors are a good foundation for mixed access patterns where both indexed and linear accesses are used. You'll learn more on vectors [later](http://docs.scala-lang.org/overviews/collections/concrete-immutable-collection-classes.html#vectors). + +### Buffers ### + +An important sub-category of mutable sequences is `Buffer`s. They allow not only updates of existing elements but also element insertions, element removals, and efficient additions of new elements at the end of the buffer. The principal new methods supported by a buffer are `+=` and `++=` for element addition at the end, `+=:` and `++=:` for addition at the front, `insert` and `insertAll` for element insertions, as well as `remove` and `-=` for element removal. These operations are summarized in the following table. + +Two often used implementations of buffers are `ListBuffer` and `ArrayBuffer`. As the name implies, a `ListBuffer` is backed by a `List`, and supports efficient conversion of its elements to a `List`, whereas an `ArrayBuffer` is backed by an array, and can be quickly converted into one. + +#### Operations in Class Buffer #### + +| WHAT IT IS | WHAT IT DOES| +| ------ | ------ | +| **Additions:** | | +| `buf += x` |Appends element `x` to buffer, and returns `buf` itself as result.| +| `buf += (x, y, z)` |Appends given elements to buffer.| +| `buf ++= xs` |Appends all elements in `xs` to buffer.| +| `x +=: buf` |Prepends element `x` to buffer.| +| `xs ++=: buf` |Prepends all elements in `xs` to buffer.| +| `buf insert (i, x)` |Inserts element `x` at index `i` in buffer.| +| `buf insertAll (i, xs)` |Inserts all elements in `xs` at index `i` in buffer.| +| **Removals:** | | +| `buf -= x` |Removes element `x` from buffer.| +| `buf remove i` |Removes element at index `i` from buffer.| +| `buf remove (i, n)` |Removes `n` elements starting at index `i` from buffer.| +| `buf trimStart n` |Removes first `n` elements from buffer.| +| `buf trimEnd n` |Removes last `n` elements from buffer.| +| `buf.clear()` |Removes all elements from buffer.| +| **Cloning:** | | +| `buf.clone` |A new buffer with the same elements as `buf`.| diff --git a/_overviews/collections/sets.md b/_overviews/collections/sets.md new file mode 100644 index 0000000000..8e773555a1 --- /dev/null +++ b/_overviews/collections/sets.md @@ -0,0 +1,152 @@ +--- +layout: multipage-overview +title: Sets + +discourse: true + +partof: collections +overview-name: Collections + +num: 6 + +languages: [ja, zh-cn] +permalink: /overviews/collections/:title.html +--- + +`Set`s are `Iterable`s that contain no duplicate elements. The operations on sets are summarized in the following table for general sets and in the table after that for mutable sets. They fall into the following categories: + +* **Tests** `contains`, `apply`, `subsetOf`. The `contains` method asks whether a set contains a given element. The `apply` method for a set is the same as `contains`, so `set(elem)` is the same as `set contains elem`. That means sets can also be used as test functions that return true for the elements they contain. + +For example: + + + scala> val fruit = Set("apple", "orange", "peach", "banana") + fruit: scala.collection.immutable.Set[java.lang.String] = Set(apple, orange, peach, banana) + scala> fruit("peach") + res0: Boolean = true + scala> fruit("potato") + res1: Boolean = false + + +* **Additions** `+` and `++`, which add one or more elements to a set, yielding a new set. +* **Removals** `-`, `--`, which remove one or more elements from a set, yielding a new set. +* **Set operations** for union, intersection, and set difference. Each of these operations exists in two forms: alphabetic and symbolic. The alphabetic versions are `intersect`, `union`, and `diff`, whereas the symbolic versions are `&`, `|`, and `&~`. In fact, the `++` that Set inherits from `Traversable` can be seen as yet another alias of `union` or `|`, except that `++` takes a `Traversable` argument whereas `union` and `|` take sets. + +### Operations in Class Set ### + +| WHAT IT IS | WHAT IT DOES | +| ------ | ------ | +| **Tests:** | | +| `xs contains x` |Tests whether `x` is an element of `xs`. | +| `xs(x)` |Same as `xs contains x`. | +| `xs subsetOf ys` |Tests whether `xs` is a subset of `ys`. | +| **Additions:** | | +| `xs + x` |The set containing all elements of `xs` as well as `x`.| +| `xs + (x, y, z)` |The set containing all elements of `xs` as well as the given additional elements.| +| `xs ++ ys` |The set containing all elements of `xs` as well as all elements of `ys`.| +| **Removals:** | | +| `xs - x` |The set containing all elements of `xs` except `x`.| +| `xs - (x, y, z)` |The set containing all elements of `xs` except the given elements.| +| `xs -- ys` |The set containing all elements of `xs` except the elements of `ys`.| +| `xs.empty` |An empty set of the same class as `xs`. | +| **Binary Operations:** | | +| `xs & ys` |The set intersection of `xs` and `ys`. | +| `xs intersect ys` |Same as `xs & ys`. | +| xs | ys |The set union of `xs` and `ys`. | +| `xs union ys` |Same as xs | ys. | +| `xs &~ ys` |The set difference of `xs` and `ys`. | +| `xs diff ys` |Same as `xs &~ ys`. | + +Mutable sets offer in addition methods to add, remove, or update elements, which are summarized in below. + +### Operations in Class mutable.Set ### + +| WHAT IT IS | WHAT IT DOES | +| ------ | ------ | +| **Additions:** | | +| `xs += x` |Adds element `x` to set `xs` as a side effect and returns `xs` itself.| +| `xs += (x, y, z)` |Adds the given elements to set `xs` as a side effect and returns `xs` itself.| +| `xs ++= ys` |Adds all elements in `ys` to set `xs` as a side effect and returns `xs` itself.| +| `xs add x` |Adds element `x` to `xs` and returns `true` if `x` was not previously contained in the set, `false` if it was.| +| **Removals:** | | +| `xs -= x` |Removes element `x` from set `xs` as a side effect and returns `xs` itself.| +| `xs -= (x, y, z)` |Removes the given elements from set `xs` as a side effect and returns `xs` itself.| +| `xs --= ys` |Removes all elements in `ys` from set `xs` as a side effect and returns `xs` itself.| +| `xs remove x` |Removes element `x` from `xs` and returns `true` if `x` was previously contained in the set, `false` if it was not.| +| `xs retain p` |Keeps only those elements in `xs` that satisfy predicate `p`.| +| `xs.clear()` |Removes all elements from `xs`.| +| **Update:** | | +| `xs(x) = b` |(or, written out, `xs.update(x, b)`). If boolean argument `b` is `true`, adds `x` to `xs`, otherwise removes `x` from `xs`.| +| **Cloning:** | | +| `xs.clone` |A new mutable set with the same elements as `xs`.| + +Just like an immutable set, a mutable set offers the `+` and `++` operations for element additions and the `-` and `--` operations for element removals. But these are less often used for mutable sets since they involve copying the set. As a more efficient alternative, mutable sets offer the update methods `+=` and `-=`. The operation `s += elem` adds `elem` to the set `s` as a side effect, and returns the mutated set as a result. Likewise, `s -= elem` removes `elem` from the set, and returns the mutated set as a result. Besides `+=` and `-=` there are also the bulk operations `++=` and `--=` which add or remove all elements of a traversable or an iterator. + +The choice of the method names `+=` and `-=` means that very similar code can work with either mutable or immutable sets. Consider first the following REPL dialogue which uses an immutable set `s`: + + scala> var s = Set(1, 2, 3) + s: scala.collection.immutable.Set[Int] = Set(1, 2, 3) + scala> s += 4 + scala> s -= 2 + scala> s + res2: scala.collection.immutable.Set[Int] = Set(1, 3, 4) + +We used `+=` and `-=` on a `var` of type `immutable.Set`. A statement such as `s += 4` is an abbreviation for `s = s + 4`. So this invokes the addition method `+` on the set `s` and then assigns the result back to the `s` variable. Consider now an analogous interaction with a mutable set. + + + scala> val s = collection.mutable.Set(1, 2, 3) + s: scala.collection.mutable.Set[Int] = Set(1, 2, 3) + scala> s += 4 + res3: s.type = Set(1, 4, 2, 3) + scala> s -= 2 + res4: s.type = Set(1, 4, 3) + +The end effect is very similar to the previous interaction; we start with a `Set(1, 2, 3)` and end up with a `Set(1, 3, 4)`. However, even though the statements look the same as before, they do something different. `s += 4` now invokes the `+=` method on the mutable set value `s`, changing the set in place. Likewise, `s -= 2` now invokes the `-=` method on the same set. + +Comparing the two interactions shows an important principle. You often can replace a mutable collection stored in a `val` by an immutable collection stored in a `var`, and _vice versa_. This works at least as long as there are no alias references to the collection through which one can observe whether it was updated in place or whether a new collection was created. + +Mutable sets also provide add and remove as variants of `+=` and `-=`. The difference is that `add` and `remove` return a Boolean result indicating whether the operation had an effect on the set. + +The current default implementation of a mutable set uses a hashtable to store the set's elements. The default implementation of an immutable set uses a representation that adapts to the number of elements of the set. An empty set is represented by just a singleton object. Sets of sizes up to four are represented by a single object that stores all elements as fields. Beyond that size, immutable sets are implemented as [hash tries](concrete-immutable-collection-classes.html#hash_tries). + +A consequence of these representation choices is that, for sets of small sizes (say up to 4), immutable sets are usually more compact and also more efficient than mutable sets. So, if you expect the size of a set to be small, try making it immutable. + +Two subtraits of sets are `SortedSet` and `BitSet`. + +### Sorted Sets ### + +A [SortedSet](http://www.scala-lang.org/api/current/scala/collection/SortedSet.html) is a set that produces its elements (using `iterator` or `foreach`) in a given ordering (which can be freely chosen at the time the set is created). The default representation of a [SortedSet](http://www.scala-lang.org/api/current/scala/collection/SortedSet.html) is an ordered binary tree which maintains the invariant that all elements in the left subtree of a node are smaller than all elements in the right subtree. That way, a simple in order traversal can return all tree elements in increasing order. Scala's class [immutable.TreeSet](http://www.scala-lang.org/api/current/scala/collection/immutable/TreeSet.html) uses a _red-black_ tree implementation to maintain this ordering invariant and at the same time keep the tree _balanced_-- meaning that all paths from the root of the tree to a leaf have lengths that differ only by at most one element. + +To create an empty [TreeSet](http://www.scala-lang.org/api/current/scala/collection/immutable/TreeSet.html), you could first specify the desired ordering: + + scala> val myOrdering = Ordering.fromLessThan[String](_ > _) + myOrdering: scala.math.Ordering[String] = ... + +Then, to create an empty tree set with that ordering, use: + + scala> TreeSet.empty(myOrdering) + res1: scala.collection.immutable.TreeSet[String] = TreeSet() + +Or you can leave out the ordering argument but give an element type or the empty set. In that case, the default ordering on the element type will be used. + + scala> TreeSet.empty[String] + res2: scala.collection.immutable.TreeSet[String] = TreeSet() + +If you create new sets from a tree-set (for instance by concatenation or filtering) they will keep the same ordering as the original set. For instance, + + scala> res2 + ("one", "two", "three", "four") + res3: scala.collection.immutable.TreeSet[String] = TreeSet(four, one, three, two) + +Sorted sets also support ranges of elements. For instance, the `range` method returns all elements from a starting element up to, but excluding, an end element. Or, the `from` method returns all elements greater or equal than a starting element in the set's ordering. The result of calls to both methods is again a sorted set. Examples: + + scala> res3 range ("one", "two") + res4: scala.collection.immutable.TreeSet[String] = TreeSet(one, three) + scala> res3 from "three" + res5: scala.collection.immutable.TreeSet[String] = TreeSet(three, two) + + +### Bitsets ### + +Bitsets are sets of non-negative integer elements that are implemented in one or more words of packed bits. The internal representation of a [BitSet](http://www.scala-lang.org/api/current/scala/collection/BitSet.html) uses an array of `Long`s. The first `Long` covers elements from 0 to 63, the second from 64 to 127, and so on (Immutable bitsets of elements in the range of 0 to 127 optimize the array away and store the bits directly in a one or two `Long` fields.) For every `Long`, each of its 64 bits is set to 1 if the corresponding element is contained in the set, and is unset otherwise. It follows that the size of a bitset depends on the largest integer that's stored in it. If `N` is that largest integer, then the size of the set is `N/64` `Long` words, or `N/8` bytes, plus a small number of extra bytes for status information. + +Bitsets are hence more compact than other sets if they contain many small elements. Another advantage of bitsets is that operations such as membership test with `contains`, or element addition and removal with `+=` and `-=` are all extremely efficient. diff --git a/_overviews/collections/strings.md b/_overviews/collections/strings.md new file mode 100644 index 0000000000..b1729328de --- /dev/null +++ b/_overviews/collections/strings.md @@ -0,0 +1,31 @@ +--- +layout: multipage-overview +title: Strings + +discourse: true + +partof: collections +overview-name: Collections + +num: 11 + +languages: [ja, zh-cn] +permalink: /overviews/collections/:title.html +--- + +Like arrays, strings are not directly sequences, but they can be converted to them, and they also support all sequence operations on strings. Here are some examples of operations you can invoke on strings. + + scala> val str = "hello" + str: java.lang.String = hello + scala> str.reverse + res6: String = olleh + scala> str.map(_.toUpper) + res7: String = HELLO + scala> str drop 3 + res8: String = lo + scala> str slice (1, 4) + res9: String = ell + scala> val s: Seq[Char] = str + s: Seq[Char] = WrappedString(h, e, l, l, o) + +These operations are supported by two implicit conversions. The first, low-priority conversion maps a `String` to a `WrappedString`, which is a subclass of `immutable.IndexedSeq`, This conversion got applied in the last line above where a string got converted into a Seq. the other, high-priority conversion maps a string to a `StringOps` object, which adds all methods on immutable sequences to strings. This conversion was implicitly inserted in the method calls of `reverse`, `map`, `drop`, and `slice` in the example above. diff --git a/_overviews/collections/trait-iterable.md b/_overviews/collections/trait-iterable.md new file mode 100644 index 0000000000..ed2938fe6d --- /dev/null +++ b/_overviews/collections/trait-iterable.md @@ -0,0 +1,69 @@ +--- +layout: multipage-overview +title: Trait Iterable + +discourse: true + +partof: collections +overview-name: Collections + +num: 4 + +languages: [ja, zh-cn] +permalink: /overviews/collections/:title.html +--- + +The next trait from the top in the collections hierarchy is `Iterable`. All methods in this trait are defined in terms of an abstract method, `iterator`, which yields the collection's elements one by one. The `foreach` method from trait `Traversable` is implemented in `Iterable` in terms of `iterator`. Here is the actual implementation: + + def foreach[U](f: Elem => U): Unit = { + val it = iterator + while (it.hasNext) f(it.next()) + } + +Quite a few subclasses of `Iterable` override this standard implementation of foreach in `Iterable`, because they can provide a more efficient implementation. Remember that `foreach` is the basis of the implementation of all operations in `Traversable`, so its performance matters. + +Two more methods exist in `Iterable` that return iterators: `grouped` and `sliding`. These iterators, however, do not return single elements but whole subsequences of elements of the original collection. The maximal size of these subsequences is given as an argument to these methods. The `grouped` method returns its elements in "chunked" increments, where `sliding` yields a sliding "window" over the elements. The difference between the two should become clear by looking at the following REPL interaction: + + scala> val xs = List(1, 2, 3, 4, 5) + xs: List[Int] = List(1, 2, 3, 4, 5) + scala> val git = xs grouped 3 + git: Iterator[List[Int]] = non-empty iterator + scala> git.next() + res3: List[Int] = List(1, 2, 3) + scala> git.next() + res4: List[Int] = List(4, 5) + scala> val sit = xs sliding 3 + sit: Iterator[List[Int]] = non-empty iterator + scala> sit.next() + res5: List[Int] = List(1, 2, 3) + scala> sit.next() + res6: List[Int] = List(2, 3, 4) + scala> sit.next() + res7: List[Int] = List(3, 4, 5) + +Trait `Iterable` also adds some other methods to `Traversable` that can be implemented efficiently only if an iterator is available. They are summarized in the following table. + +### Operations in Trait Iterable ### + +| WHAT IT IS | WHAT IT DOES | +| ------ | ------ | +| **Abstract Method:** | | +| `xs.iterator` |An `iterator` that yields every element in `xs`, in the same order as `foreach` traverses elements.| +| **Other Iterators:** | | +| `xs grouped size` |An iterator that yields fixed-sized "chunks" of this collection.| +| `xs sliding size` |An iterator that yields a sliding fixed-sized window of elements in this collection.| +| **Subcollections:** | | +| `xs takeRight n` |A collection consisting of the last `n` elements of `xs` (or, some arbitrary `n` elements, if no order is defined).| +| `xs dropRight n` |The rest of the collection except `xs takeRight n`.| +| **Zippers:** | | +| `xs zip ys` |An iterable of pairs of corresponding elements from `xs` and `ys`.| +| `xs zipAll (ys, x, y)` |An iterable of pairs of corresponding elements from `xs` and `ys`, where the shorter sequence is extended to match the longer one by appending elements `x` or `y`.| +| `xs.zipWithIndex` |An iterable of pairs of elements from `xs` with their indices.| +| **Comparison:** | | +| `xs sameElements ys` |A test whether `xs` and `ys` contain the same elements in the same order| + +In the inheritance hierarchy below Iterable you find three traits: [Seq](https://www.scala-lang.org/api/current/scala/collection/Seq.html), [Set](https://www.scala-lang.org/api/current/scala/collection/Set.html), and [Map](https://www.scala-lang.org/api/current/scala/collection/Map.html). `Seq` and `Map` implement the [PartialFunction](https://www.scala-lang.org/api/current/scala/PartialFunction.html) trait with its `apply` and `isDefinedAt` methods, each implemented differently. `Set` gets its `apply` method from [GenSetLike](https://www.scala-lang.org/api/current/scala/collection/GenSetLike.html). + +For sequences, `apply` is positional indexing, where elements are always numbered from `0`. That is, `Seq(1, 2, 3)(1)` gives `2`. For sets, `apply` is a membership test. For instance, `Set('a', 'b', 'c')('b')` gives `true` whereas `Set()('a')` gives `false`. Finally for maps, `apply` is a selection. For instance, `Map('a' -> 1, 'b' -> 10, 'c' -> 100)('b')` gives `10`. + +In the following, we will explain each of the three kinds of collections in more detail. diff --git a/_overviews/collections/trait-traversable.md b/_overviews/collections/trait-traversable.md new file mode 100644 index 0000000000..7d1a033e48 --- /dev/null +++ b/_overviews/collections/trait-traversable.md @@ -0,0 +1,113 @@ +--- +layout: multipage-overview +title: Trait Traversable + +discourse: true + +partof: collections +overview-name: Collections + +num: 3 + +languages: [ja, zh-cn] +permalink: /overviews/collections/:title.html +--- + +At the top of the collection hierarchy is trait `Traversable`. Its only abstract operation is `foreach`: + + def foreach[U](f: Elem => U) + +Collection classes that implement `Traversable` just need to define this method; all other methods can be inherited from `Traversable`. + +The `foreach` method is meant to traverse all elements of the collection, and apply the given operation, f, to each element. The type of the operation is `Elem => U`, where `Elem` is the type of the collection's elements and `U` is an arbitrary result type. The invocation of `f` is done for its side effect only; in fact any function result of f is discarded by `foreach`. + +`Traversable` also defines many concrete methods, which are all listed in the following table. These methods fall into the following categories: + +* **Addition**, `++`, which appends two traversables together, or appends all elements of an iterator to a traversable. +* **Map** operations `map`, `flatMap`, and `collect`, which produce a new collection by applying some function to collection elements. +* **Conversions** `toArray`, `toList`, `toIterable`, `toSeq`, `toIndexedSeq`, `toStream`, `toSet`, `toMap`, which turn a `Traversable` collection into something more specific. All these conversions return their receiver argument unchanged if the run-time type of the collection already matches the demanded collection type. For instance, applying `toList` to a list will yield the list itself. +* **Copying operations** `copyToBuffer` and `copyToArray`. As their names imply, these copy collection elements to a buffer or array, respectively. +* **Size info** operations `isEmpty`, `nonEmpty`, `size`, and `hasDefiniteSize`: Traversable collections can be finite or infinite. An example of an infinite traversable collection is the stream of natural numbers `Stream.from(0)`. The method `hasDefiniteSize` indicates whether a collection is possibly infinite. If `hasDefiniteSize` returns true, the collection is certainly finite. If it returns false, the collection has not been not fully elaborated yet, so it might be infinite or finite. +* **Element retrieval** operations `head`, `last`, `headOption`, `lastOption`, and `find`. These select the first or last element of a collection, or else the first element matching a condition. Note, however, that not all collections have a well-defined meaning of what "first" and "last" means. For instance, a hash set might store elements according to their hash keys, which might change from run to run. In that case, the "first" element of a hash set could also be different for every run of a program. A collection is _ordered_ if it always yields its elements in the same order. Most collections are ordered, but some (_e.g._ hash sets) are not-- dropping the ordering gives a little bit of extra efficiency. Ordering is often essential to give reproducible tests and to help in debugging. That's why Scala collections give ordered alternatives for all collection types. For instance, the ordered alternative for `HashSet` is `LinkedHashSet`. +* **Sub-collection retrieval operations** `tail`, `init`, `slice`, `take`, `drop`, `takeWhile`, `dropWhile`, `filter`, `filterNot`, `withFilter`. These all return some sub-collection identified by an index range or some predicate. +* **Subdivision operations** `splitAt`, `span`, `partition`, `groupBy`, which split the elements of this collection into several sub-collections. +* **Element tests** `exists`, `forall`, `count` which test collection elements with a given predicate. +* **Folds** `foldLeft`, `foldRight`, `/:`, `:\`, `reduceLeft`, `reduceRight` which apply a binary operation to successive elements. +* **Specific folds** `sum`, `product`, `min`, `max`, which work on collections of specific types (numeric or comparable). +* **String** operations `mkString`, `addString`, `stringPrefix`, which give alternative ways of converting a collection to a string. +* **View** operations, consisting of two overloaded variants of the `view` method. A view is a collection that's evaluated lazily. You'll learn more about views in [later](#Views). + +### Operations in Class Traversable ### + +| WHAT IT IS | WHAT IT DOES | +| ------ | ------ | +| **Abstract Method:** | | +| `xs foreach f` |Executes function `f` for every element of `xs`.| +| **Addition:** | | +| `xs ++ ys` |A collection consisting of the elements of both `xs` and `ys`. `ys` is a [TraversableOnce](http://www.scala-lang.org/api/current/scala/collection/TraversableOnce.html) collection, i.e., either a [Traversable](http://www.scala-lang.org/api/current/scala/collection/Traversable.html) or an [Iterator](http://www.scala-lang.org/api/current/scala/collection/Iterator.html).| +| **Maps:** | | +| `xs map f` |The collection obtained from applying the function f to every element in `xs`.| +| `xs flatMap f` |The collection obtained from applying the collection-valued function `f` to every element in `xs` and concatenating the results.| +| `xs collect f` |The collection obtained from applying the partial function `f` to every element in `xs` for which it is defined and collecting the results.| +| **Conversions:** | | +| `xs.toArray` |Converts the collection to an array. | +| `xs.toList` |Converts the collection to a list. | +| `xs.toIterable` |Converts the collection to an iterable. | +| `xs.toSeq` |Converts the collection to a sequence. | +| `xs.toIndexedSeq` |Converts the collection to an indexed sequence. | +| `xs.toStream` |Converts the collection to a lazily computed stream.| +| `xs.toSet` |Converts the collection to a set. | +| `xs.toMap` |Converts the collection of key/value pairs to a map. If the collection does not have pairs as elements, calling this operation results in a static type error.| +| **Copying:** | | +| `xs copyToBuffer buf` |Copies all elements of the collection to buffer `buf`.| +| `xs copyToArray(arr, s, n)`|Copies at most `n` elements of the collection to array `arr` starting at index `s`. The last two arguments are optional.| +| **Size info:** | | +| `xs.isEmpty` |Tests whether the collection is empty. | +| `xs.nonEmpty` |Tests whether the collection contains elements. | +| `xs.size` |The number of elements in the collection. | +| `xs.hasDefiniteSize` |True if `xs` is known to have finite size. | +| **Element Retrieval:** | | +| `xs.head` |The first element of the collection (or, some element, if no order is defined).| +| `xs.headOption` |The first element of `xs` in an option value, or None if `xs` is empty.| +| `xs.last` |The last element of the collection (or, some element, if no order is defined).| +| `xs.lastOption` |The last element of `xs` in an option value, or None if `xs` is empty.| +| `xs find p` |An option containing the first element in `xs` that satisfies `p`, or `None` if no element qualifies.| +| **Subcollections:** | | +| `xs.tail` |The rest of the collection except `xs.head`. | +| `xs.init` |The rest of the collection except `xs.last`. | +| `xs slice (from, to)` |A collection consisting of elements in some index range of `xs` (from `from` up to, and excluding `to`).| +| `xs take n` |A collection consisting of the first `n` elements of `xs` (or, some arbitrary `n` elements, if no order is defined).| +| `xs drop n` |The rest of the collection except `xs take n`.| +| `xs takeWhile p` |The longest prefix of elements in the collection that all satisfy `p`.| +| `xs dropWhile p` |The collection without the longest prefix of elements that all satisfy `p`.| +| `xs filter p` |The collection consisting of those elements of xs that satisfy the predicate `p`.| +| `xs withFilter p` |A non-strict filter of this collection. Subsequent calls to `map`, `flatMap`, `foreach`, and `withFilter` will only apply to those elements of `xs` for which the condition `p` is true.| +| `xs filterNot p` |The collection consisting of those elements of `xs` that do not satisfy the predicate `p`.| +| **Subdivisions:** | | +| `xs splitAt n` |Split `xs` at a position, giving the pair of collections `(xs take n, xs drop n)`.| +| `xs span p` |Split `xs` according to a predicate, giving the pair of collections `(xs takeWhile p, xs.dropWhile p)`.| +| `xs partition p` |Split `xs` into a pair of collections; one with elements that satisfy the predicate `p`, the other with elements that do not, giving the pair of collections `(xs filter p, xs.filterNot p)`| +| `xs groupBy f` |Partition `xs` into a map of collections according to a discriminator function `f`.| +| **Element Conditions:** | | +| `xs forall p` |A boolean indicating whether the predicate `p` holds for all elements of `xs`.| +| `xs exists p` |A boolean indicating whether the predicate `p` holds for some element in `xs`.| +| `xs count p` |The number of elements in `xs` that satisfy the predicate `p`.| +| **Folds:** | | +| `(z /: xs)(op)` |Apply binary operation `op` between successive elements of `xs`, going left to right and starting with `z`.| +| `(xs :\ z)(op)` |Apply binary operation `op` between successive elements of `xs`, going right to left and starting with `z`.| +| `xs.foldLeft(z)(op)` |Same as `(z /: xs)(op)`.| +| `xs.foldRight(z)(op)` |Same as `(xs :\ z)(op)`.| +| `xs reduceLeft op` |Apply binary operation `op` between successive elements of non-empty collection `xs`, going left to right.| +| `xs reduceRight op` |Apply binary operation `op` between successive elements of non-empty collection `xs`, going right to left.| +| **Specific Folds:** | | +| `xs.sum` |The sum of the numeric element values of collection `xs`.| +| `xs.product` |The product of the numeric element values of collection `xs`.| +| `xs.min` |The minimum of the ordered element values of collection `xs`.| +| `xs.max` |The maximum of the ordered element values of collection `xs`.| +| **Strings:** | | +| `xs addString (b, start, sep, end)`|Adds a string to `StringBuilder` `b` that shows all elements of `xs` between separators `sep` enclosed in strings `start` and `end`. `start`, `sep`, `end` are all optional.| +| `xs mkString (start, sep, end)`|Converts the collection to a string that shows all elements of `xs` between separators `sep` enclosed in strings `start` and `end`. `start`, `sep`, `end` are all optional.| +| `xs.stringPrefix` |The collection name at the beginning of the string returned from `xs.toString`.| +| **Views:** | | +| `xs.view` |Produces a view over `xs`.| +| `xs view (from, to)` |Produces a view that represents the elements in some index range of `xs`.| diff --git a/_overviews/collections/views.md b/_overviews/collections/views.md new file mode 100644 index 0000000000..d8b9ccec0c --- /dev/null +++ b/_overviews/collections/views.md @@ -0,0 +1,130 @@ +--- +layout: multipage-overview +title: Views + +discourse: true + +partof: collections +overview-name: Collections + +num: 14 + +languages: [ja, zh-cn] +permalink: /overviews/collections/:title.html +--- + +Collections have quite a few methods that construct new collections. Examples are `map`, `filter` or `++`. We call such methods transformers because they take at least one collection as their receiver object and produce another collection as their result. + +There are two principal ways to implement transformers. One is _strict_, that is a new collection with all its elements is constructed as a result of the transformer. The other is non-strict or _lazy_, that is one constructs only a proxy for the result collection, and its elements get constructed only as one demands them. + +As an example of a non-strict transformer consider the following implementation of a lazy map operation: + + def lazyMap[T, U](coll: Iterable[T], f: T => U) = new Iterable[U] { + def iterator = coll.iterator map f + } + +Note that `lazyMap` constructs a new `Iterable` without stepping through all elements of the given collection `coll`. The given function `f` is instead applied to the elements of the new collection's `iterator` as they are demanded. + +Scala collections are by default strict in all their transformers, except for `Stream`, which implements all its transformer methods lazily. However, there is a systematic way to turn every collection into a lazy one and _vice versa_, which is based on collection views. A _view_ is a special kind of collection that represents some base collection, but implements all transformers lazily. + +To go from a collection to its view, you can use the view method on the collection. If `xs` is some collection, then `xs.view` is the same collection, but with all transformers implemented lazily. To get back from a view to a strict collection, you can use the `force` method. + +Let's see an example. Say you have a vector of Ints over which you want to map two functions in succession: + + scala> val v = Vector(1 to 10: _*) + v: scala.collection.immutable.Vector[Int] = + Vector(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + scala> v map (_ + 1) map (_ * 2) + res5: scala.collection.immutable.Vector[Int] = + Vector(4, 6, 8, 10, 12, 14, 16, 18, 20, 22) + +In the last statement, the expression `v map (_ + 1)` constructs a new vector which is then transformed into a third vector by the second call to `map (_ * 2)`. In many situations, constructing the intermediate result from the first call to map is a bit wasteful. In the example above, it would be faster to do a single map with the composition of the two functions `(_ + 1)` and `(_ * 2)`. If you have the two functions available in the same place you can do this by hand. But quite often, successive transformations of a data structure are done in different program modules. Fusing those transformations would then undermine modularity. A more general way to avoid the intermediate results is by turning the vector first into a view, then applying all transformations to the view, and finally forcing the view to a vector: + + scala> (v.view map (_ + 1) map (_ * 2)).force + res12: Seq[Int] = Vector(4, 6, 8, 10, 12, 14, 16, 18, 20, 22) + +Let's do this sequence of operations again, one by one: + + scala> val vv = v.view + vv: scala.collection.SeqView[Int,Vector[Int]] = + SeqView(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + +The application `v.view` gives you a `SeqView`, i.e. a lazily evaluated `Seq`. The type `SeqView` has two type parameters. The first, `Int`, shows the type of the view's elements. The second, `Vector[Int]` shows you the type constructor you get back when forcing the `view`. + +Applying the first `map` to the view gives: + + scala> vv map (_ + 1) + res13: scala.collection.SeqView[Int,Seq[_]] = SeqViewM(...) + +The result of the `map` is a value that prints `SeqViewM(...)`. This is in essence a wrapper that records the fact that a `map` with function `(_ + 1)` needs to be applied on the vector `v`. It does not apply that map until the view is `force`d, however. The "M" after `SeqView` is an indication that the view encapsulates a map operation. Other letters indicate other delayed operations. For instance "S" indicates a delayed `slice` operations, and "R" indicates a `reverse`. Let's now apply the second `map` to the last result. + + scala> res13 map (_ * 2) + res14: scala.collection.SeqView[Int,Seq[_]] = SeqViewMM(...) + +You now get a `SeqView` that contains two map operations, so it prints with a double "M": `SeqViewMM(...)`. Finally, forcing the last result gives: + + scala> res14.force + res15: Seq[Int] = Vector(4, 6, 8, 10, 12, 14, 16, 18, 20, 22) + +Both stored functions get applied as part of the execution of the `force` operation and a new vector is constructed. That way, no intermediate data structure is needed. + +One detail to note is that the static type of the final result is a Seq, not a Vector. Tracing the types back we see that as soon as the first delayed map was applied, the result had static type `SeqViewM[Int, Seq[_]]`. That is, the "knowledge" that the view was applied to the specific sequence type `Vector` got lost. The implementation of a view for some class requires quite a lot of code, so the Scala collection libraries provide views mostly only for general collection types, but not for specific implementations (An exception to this are arrays: Applying delayed operations on arrays will again give results with static type `Array`). + +There are two reasons why you might want to consider using views. The first is performance. You have seen that by switching a collection to a view the construction of intermediate results can be avoided. These savings can be quite important. As another example, consider the problem of finding the first palindrome in a list of words. A palindrome is a word which reads backwards the same as forwards. Here are the necessary definitions: + + def isPalindrome(x: String) = x == x.reverse + def findPalidrome(s: Seq[String]) = s find isPalindrome + +Now, assume you have a very long sequence words and you want to find a palindrome in the first million words of that sequence. Can you re-use the definition of `findPalidrome`? Of course, you could write: + + findPalindrome(words take 1000000) + +This nicely separates the two aspects of taking the first million words of a sequence and finding a palindrome in it. But the downside is that it always constructs an intermediary sequence consisting of one million words, even if the first word of that sequence is already a palindrome. So potentially, 999'999 words are copied into the intermediary result without being inspected at all afterwards. Many programmers would give up here and write their own specialized version of finding palindromes in some given prefix of an argument sequence. But with views, you don't have to. Simply write: + + findPalindrome(words.view take 1000000) + +This has the same nice separation of concerns, but instead of a sequence of a million elements it will only construct a single lightweight view object. This way, you do not need to choose between performance and modularity. + +The second use case applies to views over mutable sequences. Many transformer functions on such views provide a window into the original sequence that can then be used to update selectively some elements of that sequence. To see this in an example, let's suppose you have an array `arr`: + + scala> val arr = (0 to 9).toArray + arr: Array[Int] = Array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9) + +You can create a subwindow into that array by creating a slice of a view of `arr`: + + scala> val subarr = arr.view.slice(3, 6) + subarr: scala.collection.mutable.IndexedSeqView[ + Int,Array[Int]] = IndexedSeqViewS(...) + +This gives a view `subarr` which refers to the elements at positions 3 through 5 of the array `arr`. The view does not copy these elements, it just provides a reference to them. Now, assume you have a method that modifies some elements of a sequence. For instance, the following `negate` method would negate all elements of the sequence of integers it's given: + + scala> def negate(xs: collection.mutable.Seq[Int]) = + for (i <- 0 until xs.length) xs(i) = -xs(i) + negate: (xs: scala.collection.mutable.Seq[Int])Unit + +Assume now you want to negate elements at positions 3 through five of the array `arr`. Can you use `negate` for this? Using a view, this is simple: + + scala> negate(subarr) + scala> arr + res4: Array[Int] = Array(0, 1, 2, -3, -4, -5, 6, 7, 8, 9) + +What happened here is that negate changed all elements of `subarr`, which was originally a slice of the array `arr`. Again, you see that views help in keeping things modular. The code above nicely separated the question of what index range to apply a method to from the question what method to apply. + +After having seen all these nifty uses of views you might wonder why have strict collections at all? One reason is that performance comparisons do not always favor lazy over strict collections. For smaller collection sizes the added overhead of forming and applying closures in views is often greater than the gain from avoiding the intermediary data structures. A probably more important reason is that evaluation in views can be very confusing if the delayed operations have side effects. + +Here's an example which bit a few users of versions of Scala before 2.8. In these versions the Range type was lazy, so it behaved in effect like a view. People were trying to create a number of actors like this: + + + val actors = for (i <- 1 to 10) yield actor { ... } + +They were surprised that none of the actors was executing afterwards, even though the actor method should create and start an actor from the code that's enclosed in the braces following it. To explain why nothing happened, remember that the for expression above is equivalent to an application of map: + + val actors = (1 to 10) map (i => actor { ... }) + +Since previously the range produced by `(1 to 10)` behaved like a view, the result of the map was again a view. That is, no element was computed, and, consequently, no actor was created! Actors would have been created by forcing the range of the whole expression, but it's far from obvious that this is what was required to make the actors do their work. + +To avoid surprises like this, the Scala 2.8 collections library has more regular rules. All collections except streams and views are strict. The only way to go from a strict to a lazy collection is via the `view` method. The only way to go back is via `force`. So the `actors` definition above would behave as expected in Scala 2.8 in that it would create and start 10 actors. To get back the surprising previous behavior, you'd have to add an explicit `view` method call: + + val actors = for (i <- (1 to 10).view) yield actor { ... } + +In summary, views are a powerful tool to reconcile concerns of efficiency with concerns of modularity. But in order not to be entangled in aspects of delayed evaluation, you should restrict views to two scenarios. Either you apply views in purely functional code where collection transformations do not have side effects. Or you apply them over mutable collections where all modifications are done explicitly. What's best avoided is a mixture of views and operations that create new collections while also having side effects. diff --git a/_overviews/core/actors-migration-guide.md b/_overviews/core/actors-migration-guide.md new file mode 100644 index 0000000000..11aab78b7d --- /dev/null +++ b/_overviews/core/actors-migration-guide.md @@ -0,0 +1,585 @@ +--- +layout: singlepage-overview +title: The Scala Actors Migration Guide + +partof: actor-migration + +languages: [zh-cn] + +permalink: /overviews/core/:title.html +--- + +**Vojin Jovanovic and Philipp Haller** + +## Introduction + +Starting with Scala 2.11.0, the Scala +[Actors](http://docs.scala-lang.org/overviews/core/actors.html) +library is deprecated. Already in Scala 2.10.0 the default actor library is +[Akka](http://akka.io). + +To ease the migration from Scala Actors to Akka we are providing the +Actor Migration Kit (AMK). The AMK consists of an extension to Scala +Actors which is enabled by including the `scala-actors-migration.jar` +on a project's classpath. In addition, Akka 2.1 includes features, +such as the `ActorDSL` singleton, which enable a simpler conversion of +code using Scala Actors to Akka. The purpose of this document is to +guide users through the migration process and explain how to use the +AMK. + +This guide has the following structure. In Section "Limitations of the +Migration Kit" we outline the main limitations of the migration +kit. In Section "Migration Overview" we describe the migration process +and talk about changes in the [Scala +distribution](http://www.scala-lang.org/downloads) that make the +migration possible. Finally, in Section "Step by Step Guide for +Migrating to Akka" we show individual steps, with working examples, +that are recommended when migrating from Scala Actors to Akka's +actors. + +A disclaimer: concurrent code is notorious for bugs that are hard to +debug and fix. Due to differences between the two actor +implementations it is possible that errors appear. It is recommended +to thoroughly test the code after each step of the migration process. + +## Limitations of the Migration Kit + +Due to differences in Akka and Scala actor models the complete functionality can not be migrated smoothly. The following list explains parts of the behavior that are hard to migrate: + +1. Relying on termination reason and bidirectional behavior with `link` method - Scala and Akka actors have different fault-handling and actor monitoring models. +In Scala linked actors terminate if one of the linked parties terminates abnormally. If termination is tracked explicitly (by `self.trapExit`) the actor receives +the termination reason from the failed actor. This functionality can not be migrated to Akka with the AMK. The AMK allows migration only for the +[Akka monitoring](http://doc.akka.io/docs/akka/2.1.0/general/supervision.html#What_Lifecycle_Monitoring_Means) +mechanism. Monitoring is different than linking because it is unidirectional and the termination reason is now known. If monitoring support is not enough, the migration +of `link` must be postponed until the last possible moment (Step 5 of migration). +Then, when moving to Akka, users must create an [supervision hierarchy](http://doc.akka.io/docs/akka/2.1.0/general/supervision.html) that will handle faults. + +2. Usage of the `restart` method - Akka does not provide explicit restart of actors so we can not provide the smooth migration for this use-case. +The user must change the system so there are no usages of the `restart` method. + +3. Usage of method `getState` - Akka actors do not have explicit state so this functionality can not be migrated. The user code must not +have `getState` invocations. + +4. Not starting actors right after instantiation - Akka actors are automatically started when instantiated. Users will have to +reshape their system so it starts all the actors right after their instantiation. + +5. Method `mailboxSize` does not exist in Akka and therefore can not be migrated. This method is seldom used and can easily be removed. + + +## Migration Overview + +### Migration Kit +In Scala 2.10.0 actors reside inside the [Scala distribution](http://www.scala-lang.org/downloads) as a separate jar ( *scala-actors.jar* ), and +the their interface is deprecated. The distribution also includes Akka actors in the *akka-actor.jar*. +The AMK resides both in the Scala actors and in the *akka-actor.jar*. Future major releases of Scala will not contain Scala actors and the AMK. + +To start the migration user needs to add the *scala-actors.jar* and the *scala-actors-migration.jar* to the build of their projects. +Addition of *scala-actors.jar* and *scala-actors-migration.jar* enables the usage of the AMK described below. + These artifacts reside in the [Scala Tools](https://oss.sonatype.org/content/groups/scala-tools/org/scala-lang/) + repository and in the [Scala distribution](http://www.scala-lang.org/downloads). + +### Step by Step Migration +Actor Migration Kit should be used in 5 steps. Each step is designed to introduce minimal changes +to the code base and allows users to run all system tests after it. In the first four steps of the migration +the code will use the Scala actors implementation. However, the methods and class signatures will be transformed to closely resemble Akka. +The migration kit on the Scala side introduces a new actor type (`ActWithStash`) and enforces access to actors through the `ActorRef` interface. + +It also enforces creation of actors through special methods on the `ActorDSL` object. In these steps it will be possible to migrate one +actor at a time. This reduces the possibility of complex errors that are caused by several bugs introduced at the same time. + +After the migration on the Scala side is complete the user should change import statements and change +the library used to Akka. On the Akka side, the `ActorDSL` and the `ActWithStash` allow + modeling the `react` construct of Scala Actors and their life cycle. This step migrates all actors to the Akka back-end and could introduce bugs in the system. Once code is migrated to Akka, users will be able to use all the features of Akka. + +## Step by Step Guide for Migrating to Akka + +In this chapter we will go through 5 steps of the actor migration. After each step the code can be tested for possible errors. In the first 4 + steps one can migrate one actor at a time and test the functionality. However, the last step migrates all actors to Akka and it can be tested +only as a whole. After this step the system should have the same functionality as before, however it will use the Akka actor library. + +### Step 1 - Everything as an Actor +The Scala actors library provides public access to multiple types of actors. They are organized in the class hierarchy and each subclass +provides slightly richer functionality. To make further steps of the migration easier we will first change each actor in the system to be of type `Actor`. +This migration step is straightforward since the `Actor` class is located at the bottom of the hierarchy and provides the broadest functionality. + +The Actors from the Scala library should be migrated according to the following rules: + +1. `class MyServ extends Reactor[T]` -> `class MyServ extends Actor` + + Note that `Reactor` provides an additional type parameter which represents the type of the messages received. If user code uses +that information then one needs to: _i)_ apply pattern matching with explicit type, or _ii)_ do the downcast of a message from +`Any` to the type `T`. + +2. `class MyServ extends ReplyReactor` -> `class MyServ extends Actor` + +3. `class MyServ extends DaemonActor` -> `class MyServ extends Actor` + + To pair the functionality of the `DaemonActor` add the following line to the class definition. + + override def scheduler: IScheduler = DaemonScheduler + +### Step 2 - Instantiations + +In Akka, actors can be accessed only through the narrow interface called `ActorRef`. Instances of `ActorRef` can be acquired either +by invoking an `actor` method on the `ActorDSL` object or through the `actorOf` method on an instance of an `ActorRefFactory`. +In the Scala side of AMK we provide a subset of the Akka `ActorRef` and the `ActorDSL` which is the actual singleton object in the Akka library. + +This step of the migration makes all accesses to actors through `ActorRef`s. First, we show how to migrate common patterns for instantiating +Scala `Actor`s. Then we show how to overcome issues with the different interfaces of `ActorRef` and `Actor`, respectively. + +#### Actor Instantiation + +The translation rules for actor instantiation (the following rules require importing `scala.actors.migration._`): + +1. Constructor Call Instantiation + + val myActor = new MyActor(arg1, arg2) + myActor.start() + + should be replaced with + + ActorDSL.actor(new MyActor(arg1, arg2)) + +2. DSL for Creating Actors + + val myActor = actor { + // actor definition + } + + should be replaced with + + val myActor = ActorDSL.actor(new Actor { + def act() { + // actor definition + } + }) + +3. Object Extended from the `Actor` Trait + + object MyActor extends Actor { + // MyActor definition + } + MyActor.start() + + should be replaced with + + class MyActor extends Actor { + // MyActor definition + } + + object MyActor { + val ref = ActorDSL.actor(new MyActor) + } + + All accesses to the object `MyActor` should be replaced with accesses to `MyActor.ref`. + +Note that Akka actors are always started on instantiation. In case actors in the migrated + system are created and started at different locations, and changing this can affect the behavior of the system, +users need to change the code so actors are started right after instantiation. + +Remote actors also need to be fetched as `ActorRef`s. To get an `ActorRef` of an remote actor use the method `selectActorRef`. + +#### Different Method Signatures + +At this point we have changed all the actor instantiations to return `ActorRef`s, however, we are not done yet. +There are differences in the interface of `ActorRef`s and `Actor`s so we need to change the methods invoked on each migrated instance. +Unfortunately, some of the methods that Scala `Actor`s provide can not be migrated. For the following methods users need to find a workaround: + +1. `getState()` - actors in Akka are managed by their supervising actors and are restarted by default. +In that scenario state of an actor is not relevant. + +2. `restart()` - explicitly restarts a Scala actor. There is no corresponding functionality in Akka. + +All other `Actor` methods need to be translated to two methods that exist on the ActorRef. The translation is achieved by the rules described below. +Note that all the rules require the following imports: + + import scala.concurrent.duration._ + import scala.actors.migration.pattern.ask + import scala.actors.migration._ + import scala.concurrent._ + +Additionally rules 1-3 require an implicit `Timeout` with infinite duration defined in the scope. However, since Akka does not allow for infinite timeouts, we will use +100 years. For example: + + implicit val timeout = Timeout(36500 days) + +Rules: + +1. `!!(msg: Any): Future[Any]` gets replaced with `?`. This rule will change a return type to the `scala.concurrent.Future` which might not type check. +Since `scala.concurrent.Future` has broader functionality than the previously returned one, this type error can be easily fixed with local changes: + + actor !! message -> respActor ? message + +2. `!![A] (msg: Any, handler: PartialFunction[Any, A]): Future[A]` gets replaced with `?`. The handler can be extracted as a separate +function and then applied to the generated future result. The result of a handle should yield another future like +in the following example: + + val handler: PartialFunction[Any, T] = ... // handler + actor !! (message, handler) -> (respActor ? message) map handler + +3. `!? (msg: Any): Any` gets replaced with `?` and explicit blocking on the returned future: + + actor !? message -> + Await.result(respActor ? message, Duration.Inf) + +4. `!? (msec: Long, msg: Any): Option[Any]` gets replaced with `?` and explicit blocking on the future: + + actor !? (dur, message) -> + val res = respActor.?(message)(Timeout(dur milliseconds)) + val optFut = res map (Some(_)) recover { case _ => None } + Await.result(optFut, Duration.Inf) + +Public methods that are not mentioned here are declared public for purposes of the actors DSL. They can be used only +inside the actor definition so their migration is not relevant in this step. + +### Step 3 - `Actor`s become `ActWithStash`s + +At this point all actors inherit the `Actor` trait, we instantiate actors through special factory methods, +and all actors are accessed through the `ActorRef` interface. +Now we need to change all actors to the `ActWithStash` class from the AMK. This class behaves exactly the same like Scala `Actor` +but, additionally, provides methods that correspond to methods in Akka's `Actor` trait. This allows easy, step by step, migration to the Akka behavior. + +To achieve this all classes that extend `Actor` should extend the `ActWithStash`. Apply the +following rule: + + class MyActor extends Actor -> class MyActor extends ActWithStash + +After this change code might not compile. The `receive` method exists in `ActWithStash` and can not be used in the body of the `act` as is. To redirect the compiler to the previous method +add the type parameter to all `receive` calls in your system. For example: + + receive { case x: Int => "Number" } -> + receive[String] { case x: Int => "Number" } + +Additionally, to make the code compile, users must add the `override` keyword before the `act` method, and to create +the empty `receive` method in the code. Method `act` needs to be overridden since its implementation in `ActWithStash` +mimics the message processing loop of Akka. The changes are shown in the following example: + + class MyActor extends ActWithStash { + + // dummy receive method (not used for now) + def receive = {case _ => } + + override def act() { + // old code with methods receive changed to react. + } + } + + +`ActWithStash` instances have variable `trapExit` set to `true` by default. If that is not desired set it to `false` in the initializer of the class. + +The remote actors will not work with `ActWithStash` out of the box. The method `register('name, this)` needs to be replaced with: + + registerActorRef('name, self) + +In later steps of the migration, calls to `registerActorRef` and `alive` should be treated like any other calls. + +After this point user can run the test suite and the whole system should behave as before. The `ActWithStash` and `Actor` use the same infrastructure so the system +should behave exactly the same. + +### Step 4 - Removing the `act` Method + +In this section we describe how to remove the `act` method from `ActWithStash`s and how to +change the methods used in the `ActWithStash` to resemble Akka. Since this step can be complex, it is recommended +to do changes one actor at a time. In Scala, an actor's behavior is defined by implementing the `act` method. Logically, an actor is a concurrent process +which executes the body of its `act` method, and then terminates. In Akka, the behavior is defined by using a global message +handler which processes the messages in the actor's mailbox one by one. The message handler is a partial function, returned by the `receive` method, +which gets applied to each message. + +Since the behavior of Akka methods in the `ActWithStash` depends on the removal of the `act` method we have to do that first. Then we will give the translation +rules for translating individual methods of the `scala.actors.Actor` trait. + +#### Removal of `act` + +In the following list we present the translation rules for common message processing patterns. This list is not +exhaustive and it covers only some common patterns. However, users can migrate more complex `act` methods to Akka by looking + at existing translation rules and extending them for more complex situations. + +A note about nested `react`/`reactWithin` calls: the message handling +partial function needs to be expanded with additional constructs that +bring it closer to the Akka model. Although these changes can be +complicated, migration is possible for an arbitrary level of +nesting. See below for examples. + +A note about using `receive`/`receiveWithin` with complex control +flow: migration can be complicated since it requires refactoring the +`act` method. A `receive` call can be modeled using `react` and +`andThen` on the message processing partial function. Again, simple +examples are shown below. + +1. If there is any code in the `act` method that is being executed before the first `loop` with `react` that code +should be moved to the `preStart` method. + + def act() { + // initialization code here + loop { + react { ... } + } + } + + should be replaced with + + override def preStart() { + // initialization code here + } + + def act() { + loop { + react{ ... } + } + } + + This rule should be used in other patterns as well if there is code before the first react. + +2. When `act` is in the form of a simple `loop` with a nested `react` use the following pattern. + + def act() = { + loop { + react { + // body + } + } + } + + should be replaced with + + def receive = { + // body + } + +3. When `act` contains a `loopWhile` construct use the following translation. + + def act() = { + loopWhile(c) { + react { + case x: Int => + // do task + if (x == 42) { + c = false + } + } + } + } + + should be replaced with + + def receive = { + case x: Int => + // do task + if (x == 42) { + context.stop(self) + } + } + +4. When `act` contains nested `react`s use the following rule: + + def act() = { + var c = true + loopWhile(c) { + react { + case x: Int => + // do task + if (x == 42) { + c = false + } else { + react { + case y: String => + // do nested task + } + } + } + } + } + + should be replaced with + + def receive = { + case x: Int => + // do task + if (x == 42) { + context.stop(self) + } else { + context.become(({ + case y: String => + // do nested task + }: Receive).andThen(x => { + unstashAll() + context.unbecome() + }).orElse { case x => stash(x) }) + } + } + +5. For `reactWithin` method use the following translation rule: + + loop { + reactWithin(t) { + case TIMEOUT => // timeout processing code + case msg => // message processing code + } + } + + should be replaced with + + import scala.concurrent.duration._ + + context.setReceiveTimeout(t millisecond) + def receive = { + case ReceiveTimeout => // timeout processing code + case msg => // message processing code + } + +6. Exception handling is done in a different way in Akka. To mimic Scala actors behavior apply the following rule + + def act() = { + loop { + react { + case msg => + // work that can fail + } + } + } + + override def exceptionHandler = { + case x: Exception => println("got exception") + } + + should be replaced with + + def receive = PFCatch({ + case msg => + // work that can fail + }, { case x: Exception => println("got exception") }) + + where `PFCatch` is defined as + + class PFCatch(f: PartialFunction[Any, Unit], + handler: PartialFunction[Exception, Unit]) + extends PartialFunction[Any, Unit] { + + def apply(x: Any) = { + try { + f(x) + } catch { + case e: Exception if handler.isDefinedAt(e) => + handler(e) + } + } + + def isDefinedAt(x: Any) = f.isDefinedAt(x) + } + + object PFCatch { + def apply(f: PartialFunction[Any, Unit], + handler: PartialFunction[Exception, Unit]) = + new PFCatch(f, handler) + } + + `PFCatch` is not included in the AMK as it can stay as the permanent feature in the migrated code + and the AMK will be removed with the next major release. Once the whole migration is complete fault-handling + can also be converted to the Akka [supervision](http://doc.akka.io/docs/akka/2.1.0/general/supervision.html#What_Supervision_Means). + + + +#### Changing `Actor` Methods + +After we have removed the `act` method we should rename the methods that do not exist in Akka but have similar functionality. In the following list we present +the list of differences and their translation: + +1. `exit()`/`exit(reason)` - should be replaced with `context.stop(self)` + +2. `receiver` - should be replaced with `self` + +3. `reply(msg)` - should be replaced with `sender ! msg` + +4. `link(actor)` - In Akka, linking of actors is done partially by [supervision](http://doc.akka.io/docs/akka/2.1.0/general/supervision.html#What_Supervision_Means) +and partially by [actor monitoring](http://doc.akka.io/docs/akka/2.1.0/general/supervision.html#What_Lifecycle_Monitoring_Means). In the AMK we support +only the monitoring method so the complete Scala functionality can not be migrated. + + The difference between linking and watching is that watching actors always receive the termination notification. +However, instead of matching on the Scala `Exit` message that contains the reason of termination the Akka watching +returns the `Terminated(a: ActorRef)` message that contains only the `ActorRef`. The functionality of getting the reason + for termination is not supported by the migration. It can be done in Akka, after the Step 4, by organizing the actors in a [supervision hierarchy](http://doc.akka.io/docs/akka/2.1.0/general/supervision.html). + + If the actor that is watching does not match the `Terminated` message, and this message arrives, it will be terminated with the `DeathPactException`. +Note that this will happen even when the watched actor terminated normally. In Scala linked actors terminate, with the same termination reason, only if +one of the actors terminates abnormally. + + If the system can not be migrated solely with `watch` the user should leave invocations to `link` and `exit(reason)` as is. However since `act()` overrides the `Exit` message the following transformation +needs to be applied: + + case Exit(actor, reason) => + println("sorry about your " + reason) + ... + + should be replaced with + + case t @ Terminated(actorRef) => + println("sorry about your " + t.reason) + ... + + NOTE: There is another subtle difference between Scala and Akka actors. In Scala, `link`/`watch` to the already dead actor will not have affect. +In Akka, watching the already dead actor will result in sending the `Terminated` message. This can give unexpected behavior in the Step 5 of the migration guide. + +### Step 5 - Moving to the Akka Back-end + +At this point user code is ready to operate on Akka actors. Now we can switch the actors library from Scala to +Akka actors. To do this configure the build to exclude the `scala-actors.jar` and the `scala-actors-migration.jar`, + and to include *akka-actor.jar* and *typesafe-config.jar*. The AMK is built to work only with Akka actors version 2.1 which are included in the [Scala distribution](http://www.scala-lang.org/downloads) + and can be configured by these [instructions](http://doc.akka.io/docs/akka/2.1.0/intro/getting-started.html#Using_a_build_tool). + +After this change the compilation will fail due to different package names and slight differences in the API. We will have to change each imported actor +from scala to Akka. Following is the non-exhaustive list of package names that need to be changed: + + scala.actors._ -> akka.actor._ + scala.actors.migration.ActWithStash -> akka.actor.ActorDSL._ + scala.actors.migration.pattern.ask -> akka.pattern.ask + scala.actors.migration.Timeout -> akka.util.Timeout + +Also, method declarations `def receive =` in `ActWithStash` should be prepended with `override`. + +In Scala actors the `stash` method needs a message as a parameter. For example: + + def receive = { + ... + case x => stash(x) + } + +In Akka only the currently processed message can be stashed. Therefore replace the above example with: + + def receive = { + ... + case x => stash() + } + +#### Adding Actor Systems + +The Akka actors are organized in [Actor systems](http://doc.akka.io/docs/akka/2.1.0/general/actor-systems.html). + Each actor that is instantiated must belong to one `ActorSystem`. To achieve this add an `ActorSystem` instance to each actor instantiation call as a first argument. The following example shows the transformation. + +To achieve this transformation you need to have an actor system instantiated. The actor system is usually instantiated in Scala objects or configuration classes that are global to your system. For example: + + val system = ActorSystem("migration-system") + +Then apply the following transformation: + + ActorDSL.actor(...) -> ActorDSL.actor(system)(...) + +If many calls to `actor` use the same `ActorSystem` it can be passed as an implicit parameter. For example: + + ActorDSL.actor(...) -> + import project.implicitActorSystem + ActorDSL.actor(...) + +Finally, Scala programs are terminating when all the non-daemon threads and actors finish. With Akka the program ends when all the non-daemon threads finish and all actor systems are shut down. + Actor systems need to be explicitly terminated before the program can exit. This is achieved by invoking the `shutdown` method on an Actor system. + +#### Remote Actors + +Once the code base is moved to Akka remoting will not work any more. The methods `registerActorFor` and `alive` need to be removed. In Akka, remoting is done solely by configuration and +for further details refer to the [Akka remoting documentation](http://doc.akka.io/docs/akka/2.1.0/scala/remoting.html). + +#### Examples and Issues +All of the code snippets presented in this document can be found in the [Actors Migration test suite](http://github.com/scala/actors-migration/tree/master/src/test/) as test files with the prefix `actmig`. + +This document and the Actor Migration Kit were designed and implemented by: [Vojin Jovanovic](http://people.epfl.ch/vojin.jovanovic) and [Philipp Haller](http://lampwww.epfl.ch/~phaller/) + +If you find any issues or rough edges please report them at the [Scala Bugtracker](https://github.com/scala/actors-migration/issues). diff --git a/_overviews/core/actors.md b/_overviews/core/actors.md new file mode 100644 index 0000000000..35cad2a205 --- /dev/null +++ b/_overviews/core/actors.md @@ -0,0 +1,506 @@ +--- +layout: singlepage-overview +title: The Scala Actors API + +partof: actors + +languages: [zh-cn, es] + +permalink: /overviews/core/:title.html +--- + +**Philipp Haller and Stephen Tu** + +## Introduction + +This guide describes the API of the `scala.actors` package of Scala 2.8/2.9. The organization follows groups of types that logically belong together. The trait hierarchy is taken into account to structure the individual sections. The focus is on the run-time behavior of the various methods that these traits define, thereby complementing the existing Scaladoc-based API documentation. + +NOTE: In Scala 2.10 the Actors library is deprecated and will be removed in future Scala releases. Users should use [Akka](http://akka.io) actors from the package `akka.actor`. For migration from Scala actors to Akka refer to the [Actors Migration Guide](actors-migration-guide.html). + +## The actor traits Reactor, ReplyReactor, and Actor + +### The Reactor trait + +`Reactor` is the super trait of all actor traits. Extending this trait allows defining actors with basic capabilities to send and receive messages. + +The behavior of a `Reactor` is defined by implementing its `act` method. The `act` method is executed once the `Reactor` is started by invoking `start`, which also returns the `Reactor`. The `start` method is *idempotent* which means that invoking it on an actor that has already been started has no effect. + +The `Reactor` trait has a type parameter `Msg` which indicates the type of messages that the actor can receive. + +Invoking the `Reactor`'s `!` method sends a message to the receiver. Sending a message using `!` is asynchronous which means that the sending actor does not wait until the message is received; its execution continues immediately. For example, `a ! msg` sends `msg` to `a`. All actors have a *mailbox* which buffers incoming messages until they are processed. + +The `Reactor` trait also defines a `forward` method. This method is inherited from `OutputChannel`. It has the same effect as the `!` method. Subtraits of `Reactor`, in particular the `ReplyReactor` trait, override this method to enable implicit reply destinations (see below). + +A `Reactor` receives messages using the `react` method. `react` expects an argument of type `PartialFunction[Msg, Unit]` which defines how messages of type `Msg` are handled once they arrive in the actor's mailbox. In the following example, the current actor waits to receive the string "Hello", and then prints a greeting: + + react { + case "Hello" => println("Hi there") + } + +Invoking `react` never returns. Therefore, any code that should run after a message has been received must be contained inside the partial function that is passed to `react`. For example, two messages can be received in sequence by nesting two invocations of `react`: + + react { + case Get(from) => + react { + case Put(x) => from ! x + } + } + +The `Reactor` trait also provides control structures which simplify programming with `react`. + +#### Termination and execution states + +The execution of a `Reactor` terminates when the body of its `act` method has run to completion. A `Reactor` can also terminate itself explicitly using the `exit` method. The return type of `exit` is `Nothing`, because `exit` always throws an exception. This exception is only used internally, and should never be caught. + +A terminated `Reactor` can be restarted by invoking its `restart` method. Invoking `restart` on a `Reactor` that has not terminated, yet, throws an `IllegalStateException`. Restarting a terminated actor causes its `act` method to be rerun. + +`Reactor` defines a method `getState` which returns the actor's current execution state as a member of the `Actor.State` enumeration. An actor that has not been started, yet, is in state `Actor.State.New`. An actor that can run without waiting for a message is in state `Actor.State.Runnable`. An actor that is suspended, waiting for a message is in state `Actor.State.Suspended`. A terminated actor is in state `Actor.State.Terminated`. + +#### Exception handling + +The `exceptionHandler` member allows defining an exception handler that is enabled throughout the entire lifetime of a `Reactor`: + + def exceptionHandler: PartialFunction[Exception, Unit] + +`exceptionHandler` returns a partial function which is used to handle exceptions that are not otherwise handled: whenever an exception propagates out of the body of a `Reactor`'s `act` method, the partial function is applied to that exception, allowing the actor to run clean-up code before it terminates. Note that the visibility of `exceptionHandler` is `protected`. + +Handling exceptions using `exceptionHandler` works well together with the control structures for programming with `react`. Whenever an exception has been handled using the partial function returned by `exceptionHandler`, execution continues with the current continuation closure. Example: + + loop { + react { + case Msg(data) => + if (cond) // process data + else throw new Exception("cannot process data") + } + } + +Assuming that the `Reactor` overrides `exceptionHandler`, after an exception thrown inside the body of `react` is handled, execution continues with the next loop iteration. + +### The ReplyReactor trait + +The `ReplyReactor` trait extends `Reactor[Any]` and adds or overrides the following methods: + +- The `!` method is overridden to obtain a reference to the current + actor (the sender); together with the actual message, the sender + reference is transferred to the mailbox of the receiving actor. The + receiver has access to the sender of a message through its `sender` + method (see below). + +- The `forward` method is overridden to obtain a reference to the + `sender` of the message that is currently being processed. Together + with the actual message, this reference is transferred as the sender + of the current message. As a consequence, `forward` allows + forwarding messages on behalf of actors different from the current + actor. + +- The added `sender` method returns the sender of the message that is + currently being processed. Given the fact that a message might have + been forwarded, `sender` may not return the actor that actually sent + the message. + +- The added `reply` method sends a message back to the sender of the + last message. `reply` is also used to reply to a synchronous message + send or a message send with future (see below). + +- The added `!?` methods provide *synchronous message sends*. Invoking + `!?` causes the sending actor to wait until a response is received + which is then returned. There are two overloaded variants. The + two-parameter variant takes in addition a timeout argument (in + milliseconds), and its return type is `Option[Any]` instead of + `Any`. If the sender does not receive a response within the + specified timeout period, `!?` returns `None`, otherwise it returns + the response wrapped in `Some`. + +- The added `!!` methods are similar to synchronous message sends in + that they allow transferring a response from the receiver. However, + instead of blocking the sending actor until a response is received, + they return `Future` instances. A `Future` can be used to retrieve + the response of the receiver once it is available; it can also be + used to find out whether the response is already available without + blocking the sender. There are two overloaded variants. The + two-parameter variant takes in addition an argument of type + `PartialFunction[Any, A]`. This partial function is used for + post-processing the receiver's response. Essentially, `!!` returns a + future which applies the partial function to the response once it is + received. The result of the future is the result of this + post-processing. + +- The added `reactWithin` method allows receiving messages within a + given period of time. Compared to `react` it takes an additional + parameter `msec` which indicates the time period in milliseconds + until the special `TIMEOUT` pattern matches (`TIMEOUT` is a case + object in package `scala.actors`). Example: + + reactWithin(2000) { + case Answer(text) => // process text + case TIMEOUT => println("no answer within 2 seconds") + } + + The `reactWithin` method also allows non-blocking access to the + mailbox. When specifying a time period of 0 milliseconds, the + mailbox is first scanned to find a matching message. If there is no + matching message after the first scan, the `TIMEOUT` pattern + matches. For example, this enables receiving certain messages with a + higher priority than others: + + reactWithin(0) { + case HighPriorityMsg => // ... + case TIMEOUT => + react { + case LowPriorityMsg => // ... + } + } + + In the above example, the actor first processes the next + `HighPriorityMsg`, even if there is a `LowPriorityMsg` that arrived + earlier in its mailbox. The actor only processes a `LowPriorityMsg` + *first* if there is no `HighPriorityMsg` in its mailbox. + +In addition, `ReplyReactor` adds the `Actor.State.TimedSuspended` execution state. A suspended actor, waiting to receive a message using `reactWithin` is in state `Actor.State.TimedSuspended`. + +### The Actor trait + +The `Actor` trait extends `ReplyReactor` and adds or overrides the following members: + +- The added `receive` method behaves like `react` except that it may + return a result. This is reflected in its type, which is polymorphic + in its result: `def receive[R](f: PartialFunction[Any, R]): R`. + However, using `receive` makes the actor more heavyweight, since + `receive` blocks the underlying thread while the actor is suspended + waiting for a message. The blocked thread is unavailable to execute + other actors until the invocation of `receive` returns. + +- The added `link` and `unlink` methods allow an actor to link and unlink + itself to and from another actor, respectively. Linking can be used + for monitoring and reacting to the termination of another actor. In + particular, linking affects the behavior of invoking `exit` as + explained in the API documentation of the `Actor` trait. + +- The `trapExit` member allows reacting to the termination of linked + actors independently of the exit reason (that is, it does not matter + whether the exit reason is `'normal` or not). If an actor's `trapExit` + member is set to `true`, this actor will never terminate because of + linked actors. Instead, whenever one of its linked actors terminates + it will receive a message of type `Exit`. The `Exit` case class has two + members: `from` refers to the actor that terminated; `reason` refers to + the exit reason. + +#### Termination and execution states + +When terminating the execution of an actor, the exit reason can be set +explicitly by invoking the following variant of `exit`: + + def exit(reason: AnyRef): Nothing + +An actor that terminates with an exit reason different from the symbol +`'normal` propagates its exit reason to all actors linked to it. If an +actor terminates because of an uncaught exception, its exit reason is +an instance of the `UncaughtException` case class. + +The `Actor` trait adds two new execution states. An actor waiting to +receive a message using `receive` is in state +`Actor.State.Blocked`. An actor waiting to receive a message using +`receiveWithin` is in state `Actor.State.TimedBlocked`. + +## Control structures + +The `Reactor` trait defines control structures that simplify programming +with the non-returning `react` operation. Normally, an invocation of +`react` does not return. If the actor should execute code subsequently, +then one can either pass the actor's continuation code explicitly to +`react`, or one can use one of the following control structures which +hide these continuations. + +The most basic control structure is `andThen`. It allows registering a +closure that is executed once the actor has finished executing +everything else. + + actor { + { + react { + case "hello" => // processing "hello" + }: Unit + } andThen { + println("hi there") + } + } + +For example, the above actor prints a greeting after it has processed +the `"hello"` message. Even though the invocation of `react` does not +return, we can use `andThen` to register the code which prints the +greeting as the actor's continuation. + +Note that there is a *type ascription* that follows the `react` +invocation (`: Unit`). Basically, it lets you treat the result of +`react` as having type `Unit`, which is legal, since the result of an +expression can always be dropped. This is necessary to do here, since +`andThen` cannot be a member of type `Nothing` which is the result +type of `react`. Treating the result type of `react` as `Unit` allows +the application of an implicit conversion which makes the `andThen` +member available. + +The API provides a few more control structures: + +- `loop { ... }`. Loops indefinitely, executing the code in braces in + each iteration. Invoking `react` inside the loop body causes the + actor to react to a message as usual. Subsequently, execution + continues with the next iteration of the same loop. + +- `loopWhile (c) { ... }`. Executes the code in braces while the + condition `c` returns `true`. Invoking `react` in the loop body has + the same effect as in the case of `loop`. + +- `continue`. Continues with the execution of the current continuation + closure. Invoking `continue` inside the body of a `loop` or + `loopWhile` will cause the actor to finish the current iteration and + continue with the next iteration. If the current continuation has + been registered using `andThen`, execution continues with the + closure passed as the second argument to `andThen`. + +The control structures can be used anywhere in the body of a `Reactor`'s +`act` method and in the bodies of methods (transitively) called by +`act`. For actors created using the `actor { ... }` shorthand the control +structures can be imported from the `Actor` object. + +#### Futures + +The `ReplyReactor` and `Actor` traits support result-bearing message +send operations (the `!!` methods) that immediately return a +*future*. A future, that is, an instance of the `Future` trait, is a +handle that can be used to retrieve the response to such a message +send-with-future. + +The sender of a message send-with-future can wait for the future's +response by *applying* the future. For example, sending a message using +`val fut = a !! msg` allows the sender to wait for the result of the +future as follows: `val res = fut()`. + +In addition, a `Future` can be queried to find out whether its result +is available without blocking using the `isSet` method. + +A message send-with-future is not the only way to obtain a +future. Futures can also be created from computations directly. +In the following example, the computation body is started to +run concurrently, returning a future for its result: + + val fut = Future { body } + // ... + fut() // wait for future + +What makes futures special in the context of actors is the possibility +to retrieve their result using the standard actor-based receive +operations, such as `receive` etc. Moreover, it is possible to use the +event-based operations `react` and `reactWithin`. This enables an actor to +wait for the result of a future without blocking its underlying +thread. + +The actor-based receive operations are made available through the +future's `inputChannel`. For a future of type `Future[T]`, its type is +`InputChannel[T]`. Example: + + val fut = a !! msg + // ... + fut.inputChannel.react { + case Response => // ... + } + +## Channels + +Channels can be used to simplify the handling of messages that have +different types but that are sent to the same actor. The hierarchy of +channels is divided into `OutputChannel`s and `InputChannel`s. + +`OutputChannel`s can be sent messages. An `OutputChannel` `out` +supports the following operations. + +- `out ! msg`. Asynchronously sends `msg` to `out`. A reference to the + sending actor is transferred as in the case where `msg` is sent + directly to an actor. + +- `out forward msg`. Asynchronously forwards `msg` to `out`. The + sending actor is determined as in the case where `msg` is forwarded + directly to an actor. + +- `out.receiver`. Returns the unique actor that is receiving messages + sent to the `out` channel. + +- `out.send(msg, from)`. Asynchronously sends `msg` to `out` supplying + `from` as the sender of the message. + +Note that the `OutputChannel` trait has a type parameter that specifies +the type of messages that can be sent to the channel (using `!`, +`forward`, and `send`). The type parameter is contravariant: + + trait OutputChannel[-Msg] + +Actors can receive messages from `InputChannel`s. Like `OutputChannel`, +the `InputChannel` trait has a type parameter that specifies the type of +messages that can be received from the channel. The type parameter is +covariant: + + trait InputChannel[+Msg] + +An `InputChannel[Msg]` `in` supports the following operations. + +- `in.receive { case Pat1 => ... ; case Patn => ... }` (and similarly, + `in.receiveWithin`). Receives a message from `in`. Invoking + `receive` on an input channel has the same semantics as the standard + `receive` operation for actors. The only difference is that the + partial function passed as an argument has type + `PartialFunction[Msg, R]` where `R` is the return type of `receive`. + +- `in.react { case Pat1 => ... ; case Patn => ... }` (and similarly, + `in.reactWithin`). Receives a message from `in` using the + event-based `react` operation. Like `react` for actors, the return + type is `Nothing`, indicating that invocations of this method never + return. Like the `receive` operation above, the partial function + passed as an argument has a more specific type: + + PartialFunction[Msg, Unit] + +### Creating and sharing channels + +Channels are created using the concrete `Channel` class. It extends both +`InputChannel` and `OutputChannel`. A channel can be shared either by +making the channel visible in the scopes of multiple actors, or by +sending it in a message. + +The following example demonstrates scope-based sharing. + + actor { + var out: OutputChannel[String] = null + val child = actor { + react { + case "go" => out ! "hello" + } + } + val channel = new Channel[String] + out = channel + child ! "go" + channel.receive { + case msg => println(msg.length) + } + } + +Running this example prints the string `"5"` to the console. Note that +the `child` actor has only access to `out` which is an +`OutputChannel[String]`. The `channel` reference, which can also be used +to receive messages, is hidden. However, care must be taken to ensure +the output channel is initialized to a concrete channel before the +`child` sends messages to it. This is done using the `"go"` message. When +receiving from `channel` using `channel.receive` we can make use of the +fact that `msg` is of type `String`; therefore, it provides a `length` +member. + +An alternative way to share channels is by sending them in +messages. The following example demonstrates this. + + case class ReplyTo(out: OutputChannel[String]) + + val child = actor { + react { + case ReplyTo(out) => out ! "hello" + } + } + + actor { + val channel = new Channel[String] + child ! ReplyTo(channel) + channel.receive { + case msg => println(msg.length) + } + } + +The `ReplyTo` case class is a message type that we use to distribute a +reference to an `OutputChannel[String]`. When the `child` actor receives a +`ReplyTo` message it sends a string to its output channel. The second +actor receives a message on that channel as before. + +## Schedulers + +A `Reactor` (or an instance of a subtype) is executed using a +*scheduler*. The `Reactor` trait introduces the `scheduler` member which +returns the scheduler used to execute its instances: + + def scheduler: IScheduler + +The run-time system executes actors by submitting tasks to the +scheduler using one of the `execute` methods defined in the `IScheduler` +trait. Most of the trait's other methods are only relevant when +implementing a new scheduler from scratch, which is rarely necessary. + +The default schedulers used to execute instances of `Reactor` and `Actor` +detect the situation when all actors have finished their +execution. When this happens, the scheduler shuts itself down +(terminating any threads used by the scheduler). However, some +schedulers, such as the `SingleThreadedScheduler` (in package `scheduler`) +have to be shut down explicitly by invoking their `shutdown` method. + +The easiest way to create a custom scheduler is by extending +`SchedulerAdapter`, implementing the following abstract member: + + def execute(fun: => Unit): Unit + +Typically, a concrete implementation would use a thread pool to +execute its by-name argument `fun`. + +## Remote Actors + +This section describes the remote actors API. Its main interface is +the [`RemoteActor`](http://www.scala-lang.org/api/2.9.1/scala/actors/remote/RemoteActor$.html) object in package `scala.actors.remote`. This object +provides methods to create and connect to remote actor instances. In +the code snippets shown below we assume that all members of +`RemoteActor` have been imported; the full list of imports that we use +is as follows: + + import scala.actors._ + import scala.actors.Actor._ + import scala.actors.remote._ + import scala.actors.remote.RemoteActor._ + +### Starting remote actors + +A remote actor is uniquely identified by a [`Symbol`](http://www.scala-lang.org/api/2.9.1/scala/Symbol.html). This symbol is +unique to the JVM instance on which the remote actor is executed. A +remote actor identified with name `'myActor` can be created as follows. + + class MyActor extends Actor { + def act() { + alive(9000) + register('myActor, self) + // ... + } + } + +Note that a name can only be registered with a single (alive) actor at +a time. For example, to register an actor *A* as `'myActor`, and then +register another actor *B* as `'myActor`, one would first have to wait +until *A* terminated. This requirement applies across all ports, so +simply registering *B* on a different port as *A* is not sufficient. + +### Connecting to remote actors + +Connecting to a remote actor is just as simple. To obtain a remote +reference to a remote actor running on machine `myMachine`, on port +8000, with name `'anActor`, use `select` in the following manner: + + val myRemoteActor = select(Node("myMachine", 8000), 'anActor) + +The actor returned from `select` has type `AbstractActor` which provides +essentially the same interface as a regular actor, and thus supports +the usual message send operations: + + myRemoteActor ! "Hello!" + receive { + case response => println("Response: " + response) + } + myRemoteActor !? "What is the meaning of life?" match { + case 42 => println("Success") + case oops => println("Failed: " + oops) + } + val future = myRemoteActor !! "What is the last digit of PI?" + +Note that `select` is lazy; it does not actually initiate any network +connections. It simply creates a new `AbstractActor` instance which is +ready to initiate a new network connection when needed (for instance, +when `!` is invoked). diff --git a/_overviews/core/architecture-of-scala-collections.md b/_overviews/core/architecture-of-scala-collections.md new file mode 100644 index 0000000000..5cb894f1e9 --- /dev/null +++ b/_overviews/core/architecture-of-scala-collections.md @@ -0,0 +1,1042 @@ +--- +layout: singlepage-overview +title: The Architecture of Scala Collections + +partof: collections-architecture + +languages: [zh-cn] + +permalink: /overviews/core/:title.html +--- + +**Martin Odersky and Lex Spoon** + +These pages describe the architecture of the Scala collections +framework in detail. Compared to +[the Scala 2.8 Collections API]({{ site.baseurl }}/overviews/collections/introduction.html) you +will find out more about the internal workings of the framework. You +will also learn how this architecture helps you define your own +collections in a few lines of code, while reusing the overwhelming +part of collection functionality from the framework. + +[The Scala 2.8 Collections API]({{ site.baseurl }}/overviews/collections/introduction.html) +contains a large number of collection +operations, which exist uniformly on many different collection +implementations. Implementing every collection operation anew for +every collection type would lead to an enormous amount of code, most +of which would be copied from somewhere else. Such code duplication +could lead to inconsistencies over time, when an operation is added or +modified in one part of the collection library but not in others. The +principal design objective of the new collections framework was to +avoid any duplication, defining every operation in as few places as +possible. (Ideally, everything should be defined in one place only, +but there are a few exceptions where things needed to be redefined.) +The design approach was to implement most operations in collection +"templates" that can be flexibly inherited from individual base +classes and implementations. The following pages explain these +templates and other classes and traits that constitute the "building +blocks" of the framework, as well as the construction principles they +support. + +## Builders ## + +An outline of the `Builder` trait: + + package scala.collection.mutable + + trait Builder[-Elem, +To] { + def +=(elem: Elem): this.type + def result(): To + def clear(): Unit + def mapResult[NewTo](f: To => NewTo): Builder[Elem, NewTo] = ... + } + +Almost all collection operations are implemented in terms of +*traversals* and *builders*. Traversals are handled by `Traversable`'s +`foreach` method, and building new collections is handled by instances +of class `Builder`. The listing above presents a slightly abbreviated +outline of this trait. + +You can add an element `x` to a builder `b` with `b += x`. There's also +syntax to add more than one element at once, for instance `b += (x, y)`. +Adding another collection with `b ++= xs` works as for buffers (in fact, +buffers are an enriched +version of builders). The `result()` method returns a collection from a +builder. The state of the builder is undefined after taking its +result, but it can be reset into a new empty state using +`clear()`. Builders are generic in both the element type, `Elem`, and in +the type, `To`, of collections they return. + +Often, a builder can refer to some other builder for assembling the +elements of a collection, but then would like to transform the result +of the other builder, for example to give it a different type. This +task is simplified by method `mapResult` in class `Builder`. Suppose for +instance you have an array buffer `buf`. Array buffers are builders for +themselves, so taking the `result()` of an array buffer will return the +same buffer. If you want to use this buffer to produce a builder that +builds arrays, you can use `mapResult` like this: + + scala> val buf = new ArrayBuffer[Int] + buf: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer() + + scala> val bldr = buf mapResult (_.toArray) + bldr: scala.collection.mutable.Builder[Int,Array[Int]] + = ArrayBuffer() + +The result value, `bldr`, is a builder that uses the array buffer, `buf`, +to collect elements. When a result is demanded from `bldr`, the result +of `buf` is computed, which yields the array buffer `buf` itself. This +array buffer is then mapped with `_.toArray` to an array. So the end +result is that `bldr` is a builder for arrays. + +## Factoring out common operations ## + +### Outline of trait TraversableLike ### + + package scala.collection + + trait TraversableLike[+Elem, +Repr] { + def newBuilder: Builder[Elem, Repr] // deferred + def foreach[U](f: Elem => U): Unit // deferred + ... + def filter(p: Elem => Boolean): Repr = { + val b = newBuilder + foreach { elem => if (p(elem)) b += elem } + b.result + } + } + +The main design objectives of the collection library redesign were to +have, at the same time, natural types and maximal sharing of +implementation code. In particular, Scala's collections follow the +"same-result-type" principle: wherever possible, a transformation +method on a collection will yield a collection of the same type. For +instance, the `filter` operation should yield, on every collection type, +an instance of the same collection type. Applying `filter` on a `List` +should give a `List`; applying it on a `Map` should give a `Map`, and so +on. In the rest of this section, you will find out how this is +achieved. + +The Scala collection library avoids code duplication and achieves the +"same-result-type" principle by using generic builders and traversals +over collections in so-called *implementation traits*. These traits are +named with a `Like` suffix; for instance, `IndexedSeqLike` is the +implementation trait for `IndexedSeq`, and similarly, `TraversableLike` is +the implementation trait for `Traversable`. Collection traits such as +`Traversable` or `IndexedSeq` inherit all their concrete method +implementations from these traits. Implementation traits have two type +parameters instead of one for normal collections. They parameterize +not only over the collection's element type, but also over the +collection's *representation type*, i.e., the type of the underlying +collection, such as `Seq[T]` or `List[T]`. For instance, here is the +header of trait `TraversableLike`: + + trait TraversableLike[+Elem, +Repr] { ... } + +The type parameter, `Elem`, stands for the element type of the +traversable whereas the type parameter `Repr` stands for its +representation. There are no constraints on `Repr`. In particular `Repr` +might be instantiated to a type that is itself not a subtype of +`Traversable`. That way, classes outside the collections hierarchy such +as `String` and `Array` can still make use of all operations defined in a +collection implementation trait. + +Taking `filter` as an example, this operation is defined once for all +collection classes in the trait `TraversableLike`. An outline of the +relevant code is shown in the above [outline of trait +`TraversableLike`](#outline-of-trait-traversablelike). The trait declares +two abstract methods, `newBuilder` +and `foreach`, which are implemented in concrete collection classes. The +`filter` operation is implemented in the same way for all collections +using these methods. It first constructs a new builder for the +representation type `Repr`, using `newBuilder`. It then traverses all +elements of the current collection, using `foreach`. If an element `x` +satisfies the given predicate `p` (i.e., `p(x)` is `true`), it is added to +the builder. Finally, the elements collected in the builder are +returned as an instance of the `Repr` collection type by calling the +builder's `result` method. + +A bit more complicated is the `map` operation on collections. For +instance, if `f` is a function from `String` to `Int`, and `xs` is a +`List[String]`, then `xs map f` should give a `List[Int]`. Likewise, +if `ys` is an `Array[String]`, then `ys map f` should give an +`Array[Int]`. The question is how do we achieve that without duplicating +the definition of the `map` method in lists and arrays. The +`newBuilder`/`foreach` framework shown in +[trait `TraversableLike`](#outline-of-trait-traversablelike) is +not sufficient for this because it only allows creation of new +instances of the same collection *type* whereas `map` needs an +instance of the same collection *type constructor*, but possibly with +a different element type. + +What's more, even the result type constructor of a function like `map` +might depend in non-trivial ways on the other argument types. Here is +an example: + + scala> import collection.immutable.BitSet + import collection.immutable.BitSet + + scala> val bits = BitSet(1, 2, 3) + bits: scala.collection.immutable.BitSet = BitSet(1, 2, 3) + + scala> bits map (_ * 2) + res13: scala.collection.immutable.BitSet = BitSet(2, 4, 6) + + scala> bits map (_.toFloat) + res14: scala.collection.immutable.Set[Float] + = Set(1.0, 2.0, 3.0) + +If you `map` the doubling function `_ * 2` over a bit set you obtain +another bit set. However, if you map the function `(_.toFloat)` over the +same bit set, the result is a general `Set[Float]`. Of course, it can't +be a bit set because bit sets contain `Int`s, not `Float`s. + +Note that `map`'s result type depends on the type of function that's +passed to it. If the result type of that function argument is again an +`Int`, the result of `map` is a `BitSet`, but if the result type of the +function argument is something else, the result of `map` is just a +`Set`. You'll find out soon how this type-flexibility is achieved in +Scala. + +The problem with `BitSet` is not an isolated case. Here are two more +interactions with the interpreter that both map a function over a `Map`: + + scala> Map("a" -> 1, "b" -> 2) map { case (x, y) => (y, x) } + res3: scala.collection.immutable.Map[Int,java.lang.String] + = Map(1 -> a, 2 -> b) + + scala> Map("a" -> 1, "b" -> 2) map { case (x, y) => y } + res4: scala.collection.immutable.Iterable[Int] + = List(1, 2) + +The first function swaps two arguments of a key/value pair. The result +of mapping this function is again a map, but now going in the other +direction. In fact, the first expression yields the inverse of the +original map, provided it is invertible. The second function, however, +maps the key/value pair to an integer, namely its value component. In +that case, we cannot form a `Map` from the results, but we can still +form an `Iterable`, a supertrait of `Map`. + +You might ask, why not restrict `map` so that it can always return the +same kind of collection? For instance, on bit sets `map` could accept +only `Int`-to-`Int` functions and on `Map`s it could only accept +pair-to-pair functions. Not only are such restrictions undesirable +from an object-oriented modelling point of view, they are illegal +because they would violate the Liskov substitution principle: A `Map` *is* +an `Iterable`. So every operation that's legal on an `Iterable` must also +be legal on a `Map`. + +Scala solves this problem instead with overloading: not the simple +form of overloading inherited by Java (that would not be flexible +enough), but the more systematic form of overloading that's provided +by implicit parameters. + +Implementation of `map` in `TraversableLike`: + + def map[B, That](f: Elem => B) + (implicit bf: CanBuildFrom[Repr, B, That]): That = { + val b = bf(this) + this.foreach(x => b += f(x)) + b.result + } + +The listing above shows trait `TraversableLike`'s implementation of +`map`. It's quite similar to the implementation of `filter` shown in [trait +`TraversableLike`](#outline-of-trait-traversablelike). +The principal difference is that where `filter` used +the `newBuilder` method, which is abstract in `TraversableLike`, `map` +uses a *builder factory* that's passed as an additional implicit +parameter of type `CanBuildFrom`. + +The `CanBuildFrom` trait: + + package scala.collection.generic + + trait CanBuildFrom[-From, -Elem, +To] { + // Creates a new builder + def apply(from: From): Builder[Elem, To] + } + +The listing above shows the definition of the trait `CanBuildFrom`, +which represents builder factories. It has three type parameters: `From` indicates +the type for which this builder factory applies, `Elem` indicates the element +type of the collection to be built, and `To` indicates the type of +collection to build. By defining the right implicit +definitions of builder factories, you can tailor the right typing +behavior as needed. Take class `BitSet` as an example. Its companion +object would contain a builder factory of type `CanBuildFrom[BitSet, Int, BitSet]`. +This means that when operating on a `BitSet` you can +construct another `BitSet` provided the element type of the collection to build +is `Int`. If this is not the case, the compiler will check the superclasses, and +fall back to the implicit builder factory defined in +`mutable.Set`'s companion object. The type of this more general builder +factory, where `A` is a generic type parameter, is: + + CanBuildFrom[Set[_], A, Set[A]] + +This means that when operating on an arbitrary `Set` (expressed by the +existential type `Set[_]`) you can build a `Set` again, no matter what the +element type `A` is. Given these two implicit instances of `CanBuildFrom`, +you can then rely on Scala's rules for implicit resolution to pick the +one that's appropriate and maximally specific. + +So implicit resolution provides the correct static types for tricky +collection operations such as `map`. But what about the dynamic types? +Specifically, say you map some function over a `List` value that has +`Iterable` as its static type: + + scala> val xs: Iterable[Int] = List(1, 2, 3) + xs: Iterable[Int] = List(1, 2, 3) + + scala> val ys = xs map (x => x * x) + ys: Iterable[Int] = List(1, 4, 9) + +The static type of `ys` above is `Iterable`, as expected. But its dynamic +type is (and should still be) `List`! This behavior is achieved by one +more indirection. The `apply` method in `CanBuildFrom` is passed the +source collection as argument. Most builder factories for generic +traversables (in fact all except builder factories for leaf classes) +forward the call to a method `genericBuilder` of a collection. The +`genericBuilder` method in turn calls the builder that belongs to the +collection in which it is defined. So Scala uses static implicit +resolution to resolve constraints on the types of `map`, and virtual +dispatch to pick the best dynamic type that corresponds to these +constraints. + +In the current example, the static implicit resolution will pick the +`Iterable`'s `CanBuildFrom`, which calls `genericBuilder` on the value it +received as argument. But at runtime, because of virtual dispatch, it is +`List.genericBuilder` that gets called rather than `Iterable.genericBuilder`, +and so map builds a `List`. + +## Integrating a new collection: RNA sequences ## + +What needs to be done if you want to integrate a new collection class, +so that it can profit from all predefined operations with the right +types? In the next few sections you'll be walked through two examples +that do this, namely sequences of RNA bases and prefix maps implemented +with Patricia tries. + +To start with the first example, we define the four RNA Bases: + + abstract class Base + case object A extends Base + case object U extends Base + case object G extends Base + case object C extends Base + + object Base { + val fromInt: Int => Base = Array(A, U, G, C) + val toInt: Base => Int = Map(A -> 0, U -> 1, G -> 2, C -> 3) + } + +Say you want to create a new sequence type for RNA strands, which are +sequences of bases A (adenine), U (uracil), G (guanine), and C +(cytosine). The definitions for bases are easily set up as shown in the +listing of RNA bases above. + +Every base is defined as a case object that inherits from a common +abstract class `Base`. The `Base` class has a companion object that +defines two functions that map between bases and the integers 0 to +3. You can see in the examples two different ways to use collections +to implement these functions. The `toInt` function is implemented as a +`Map` from `Base` values to integers. The reverse function, `fromInt`, is +implemented as an array. This makes use of the fact that both maps and +arrays *are* functions because they inherit from the `Function1` trait. + +The next task is to define a class for strands of RNA. Conceptually, a +strand of RNA is simply a `Seq[Base]`. However, RNA strands can get +quite long, so it makes sense to invest some work in a compact +representation. Because there are only four bases, a base can be +identified with two bits, and you can therefore store sixteen bases as +two-bit values in an integer. The idea, then, is to construct a +specialized subclass of `Seq[Base]`, which uses this packed +representation. + +### First version of RNA strands class ### + + import collection.IndexedSeqLike + import collection.mutable.{Builder, ArrayBuffer} + import collection.generic.CanBuildFrom + + final class RNA1 private (val groups: Array[Int], + val length: Int) extends IndexedSeq[Base] { + + import RNA1._ + + def apply(idx: Int): Base = { + if (idx < 0 || length <= idx) + throw new IndexOutOfBoundsException + Base.fromInt(groups(idx / N) >> (idx % N * S) & M) + } + } + + object RNA1 { + + // Number of bits necessary to represent group + private val S = 2 + + // Number of groups that fit in an Int + private val N = 32 / S + + // Bitmask to isolate a group + private val M = (1 << S) - 1 + + def fromSeq(buf: Seq[Base]): RNA1 = { + val groups = new Array[Int]((buf.length + N - 1) / N) + for (i <- 0 until buf.length) + groups(i / N) |= Base.toInt(buf(i)) << (i % N * S) + new RNA1(groups, buf.length) + } + + def apply(bases: Base*) = fromSeq(bases) + } + +The [RNA strands class listing](#first-version-of-rna-strands-class) above +presents the first version of this +class. It will be refined later. The class `RNA1` has a constructor that +takes an array of `Int`s as its first argument. This array contains the +packed RNA data, with sixteen bases in each element, except for the +last array element, which might be partially filled. The second +argument, `length`, specifies the total number of bases on the array +(and in the sequence). Class `RNA1` extends `IndexedSeq[Base]`. Trait +`IndexedSeq`, which comes from package `scala.collection.immutable`, +defines two abstract methods, `length` and `apply`. These need to be +implemented in concrete subclasses. Class `RNA1` implements `length` +automatically by defining a parametric field of the same name. It +implements the indexing method `apply` with the code given in [class +`RNA1`](#first-version-of-rna-strands-class). Essentially, `apply` first +extracts an integer value from the +`groups` array, then extracts the correct two-bit number from that +integer using right shift (`>>`) and mask (`&`). The private constants `S`, +`N`, and `M` come from the `RNA1` companion object. `S` specifies the size of +each packet (i.e., two); `N` specifies the number of two-bit packets per +integer; and `M` is a bit mask that isolates the lowest `S` bits in a +word. + +Note that the constructor of class `RNA1` is `private`. This means that +clients cannot create `RNA1` sequences by calling `new`, which makes +sense, because it hides the representation of `RNA1` sequences in terms +of packed arrays from the user. If clients cannot see what the +representation details of RNA sequences are, it becomes possible to +change these representation details at any point in the future without +affecting client code. In other words, this design achieves a good +decoupling of the interface of RNA sequences and its +implementation. However, if constructing an RNA sequence with `new` is +impossible, there must be some other way to create new RNA sequences, +else the whole class would be rather useless. In fact there are two +alternatives for RNA sequence creation, both provided by the `RNA1` +companion object. The first way is method `fromSeq`, which converts a +given sequence of bases (i.e., a value of type `Seq[Base]`) into an +instance of class `RNA1`. The `fromSeq` method does this by packing all +the bases contained in its argument sequence into an array, then +calling `RNA1`'s private constructor with that array and the length of +the original sequence as arguments. This makes use of the fact that a +private constructor of a class is visible in the class's companion +object. + +The second way to create an `RNA1` value is provided by the `apply` method +in the `RNA1` object. It takes a variable number of `Base` arguments and +simply forwards them as a sequence to `fromSeq`. Here are the two +creation schemes in action: + + scala> val xs = List(A, G, U, A) + xs: List[Product with Serializable with Base] = List(A, G, U, A) + + scala> RNA1.fromSeq(xs) + res1: RNA1 = RNA1(A, G, U, A) + + scala> val rna1 = RNA1(A, U, G, G, C) + rna1: RNA1 = RNA1(A, U, G, G, C) + +### Adapting the result type of RNA methods ### + +Here are some more interactions with the `RNA1` abstraction: + + scala> rna1.length + res2: Int = 5 + + scala> rna1.last + res3: Base = C + + scala> rna1.take(3) + res4: IndexedSeq[Base] = Vector(A, U, G) + +The first two results are as expected, but the last result of taking +the first three elements of `rna1` might not be. In fact, you see a +`IndexedSeq[Base]` as static result type and a `Vector` as the dynamic +type of the result value. You might have expected to see an `RNA1` value +instead. But this is not possible because all that was done in [class +`RNA1`](#first-version-of-rna-strands-class) was making `RNA1` extend +`IndexedSeq`. Class `IndexedSeq`, on the other +hand, has a `take` method that returns an `IndexedSeq`, and that's +implemented in terms of `IndexedSeq`'s default implementation, +`Vector`. So that's what you were seeing on the last line of the +previous interaction. + +Now that you understand why things are the way they are, the next +question should be what needs to be done to change them? One way to do +this would be to override the `take` method in class `RNA1`, maybe like +this: + + def take(count: Int): RNA1 = RNA1.fromSeq(super.take(count)) + +This would do the job for `take`. But what about `drop`, or `filter`, or +`init`? In fact there are over fifty methods on sequences that return +again a sequence. For consistency, all of these would have to be +overridden. This looks less and less like an attractive +option. Fortunately, there is a much easier way to achieve the same +effect, as shown in the next section. + + +### Second version of RNA strands class ### + + final class RNA2 private ( + val groups: Array[Int], + val length: Int + ) extends IndexedSeq[Base] with IndexedSeqLike[Base, RNA2] { + + import RNA2._ + + override def newBuilder: Builder[Base, RNA2] = + new ArrayBuffer[Base] mapResult fromSeq + + def apply(idx: Int): Base = // as before + } + +The RNA class needs to inherit not only from `IndexedSeq`, but +also from its implementation trait `IndexedSeqLike`. This is shown in +the above listing of class `RNA2`. The new implementation differs from +the previous one in only two aspects. First, class `RNA2` now also +extends `IndexedSeqLike[Base, RNA2]`. Second, it provides a builder for +RNA strands. The `IndexedSeqLike` trait +implements all concrete methods of `IndexedSeq` in an extensible +way. For instance, the return type of methods like `take`, `drop`, `filter`, +or `init` is the second type parameter passed to class `IndexedSeqLike`, +i.e., in class `RNA2` it is `RNA2` itself. + +To be able to do this, `IndexedSeqLike` bases itself on the `newBuilder` +abstraction, which creates a builder of the right kind. Subclasses of +trait `IndexedSeqLike` have to override `newBuilder` to return collections +of their own kind. In class `RNA2`, the `newBuilder` method returns a +builder of type `Builder[Base, RNA2]`. + +To construct this builder, it first creates an `ArrayBuffer`, which +itself is a `Builder[Base, ArrayBuffer]`. It then transforms the +`ArrayBuffer` builder by calling its `mapResult` method to an `RNA2` +builder. The `mapResult` method expects a transformation function from +`ArrayBuffer` to `RNA2` as its parameter. The function given is simply +`RNA2.fromSeq`, which converts an arbitrary base sequence to an `RNA2` +value (recall that an array buffer is a kind of sequence, so +`RNA2.fromSeq` can be applied to it). + +If you had left out the `newBuilder` definition, you would have gotten +an error message like the following: + + RNA2.scala:5: error: overriding method newBuilder in trait + TraversableLike of type => scala.collection.mutable.Builder[Base,RNA2]; + method newBuilder in trait GenericTraversableTemplate of type + => scala.collection.mutable.Builder[Base,IndexedSeq[Base]] has + incompatible type + class RNA2 private (val groups: Array[Int], val length: Int) + ^ + one error found + +The error message is quite long and complicated, which reflects the +intricate way the collection libraries are put together. It's best to +ignore the information about where the methods come from, because in +this case it detracts more than it helps. What remains is that a +method `newBuilder` with result type `Builder[Base, RNA2]` needed to be +defined, but a method `newBuilder` with result type +`Builder[Base,IndexedSeq[Base]]` was found. The latter does not override +the former. The first method, whose result type is `Builder[Base, RNA2]`, +is an abstract method that got instantiated at this type in +[class `RNA2`](#second-version-of-rna-strands-class) by passing the +`RNA2` type parameter to `IndexedSeqLike`. The +second method, of result type `Builder[Base,IndexedSeq[Base]]`, is +what's provided by the inherited `IndexedSeq` class. In other words, the +`RNA2` class is invalid without a definition of `newBuilder` with the +first result type. + +With the refined implementation of the [`RNA2` class](#second-version-of-rna-strands-class), +methods like `take`, +`drop`, or `filter` work now as expected: + + scala> val rna2 = RNA2(A, U, G, G, C) + rna2: RNA2 = RNA2(A, U, G, G, C) + + scala> rna2 take 3 + res5: RNA2 = RNA2(A, U, G) + + scala> rna2 filter (U !=) + res6: RNA2 = RNA2(A, G, G, C) + +### Dealing with map and friends ### + +However, there is another class of methods in collections that are not +dealt with yet. These methods do not always return the collection type +exactly. They might return the same kind of collection, but with a +different element type. The classical example of this is the `map` +method. If `s` is a `Seq[Int]`, and `f` is a function from `Int` to `String`, +then `s.map(f)` would return a `Seq[String]`. So the element type changes +between the receiver and the result, but the kind of collection stays +the same. + +There are a number of other methods that behave like `map`. For some of +them you would expect this (e.g., `flatMap`, `collect`), but for others +you might not. For instance, the append method, `++`, also might return +a result of different type as its arguments--appending a list of +`String` to a list of `Int` would give a list of `Any`. How should these +methods be adapted to RNA strands? The desired behavior would be to get +back an RNA strand when mapping bases to bases or appending two RNA strands +with `++`: + + scala> val rna = RNA(A, U, G, G, C) + rna: RNA = RNA(A, U, G, G, C) + + scala> rna map { case A => U case b => b } + res7: RNA = RNA(U, U, G, G, C) + + scala> rna ++ rna + res8: RNA = RNA(A, U, G, G, C, A, U, G, G, C) + +On the other hand, mapping bases to some other type over an RNA strand +cannot yield another RNA strand because the new elements have the +wrong type. It has to yield a sequence instead. In the same vein +appending elements that are not of type `Base` to an RNA strand can +yield a general sequence, but it cannot yield another RNA strand. + + scala> rna map Base.toInt + res2: IndexedSeq[Int] = Vector(0, 1, 2, 2, 3) + + scala> rna ++ List("missing", "data") + res3: IndexedSeq[java.lang.Object] = + Vector(A, U, G, G, C, missing, data) + +This is what you'd expect in the ideal case. But this is not what the +[`RNA2` class](#second-version-of-rna-strands-class) provides. In fact, all +examples will return instances of `Vector`, not just the last two. If you run +the first three commands above with instances of this class you obtain: + + scala> val rna2 = RNA2(A, U, G, G, C) + rna2: RNA2 = RNA2(A, U, G, G, C) + + scala> rna2 map { case A => U case b => b } + res0: IndexedSeq[Base] = Vector(U, U, G, G, C) + + scala> rna2 ++ rna2 + res1: IndexedSeq[Base] = Vector(A, U, G, G, C, A, U, G, G, C) + +So the result of `map` and `++` is never an RNA strand, even if the +element type of the generated collection is `Base`. To see how to do +better, it pays to have a close look at the signature of the `map` +method (or of `++`, which has a similar signature). The `map` method is +originally defined in class `scala.collection.TraversableLike` with the +following signature: + + def map[B, That](f: A => B) + (implicit cbf: CanBuildFrom[Repr, B, That]): That + +Here `A` is the type of elements of the collection, and `Repr` is the type +of the collection itself, that is, the second type parameter that gets +passed to implementation classes such as `TraversableLike` and +`IndexedSeqLike`. The `map` method takes two more type parameters, `B` and +`That`. The `B` parameter stands for the result type of the mapping +function, which is also the element type of the new collection. The +`That` appears as the result type of `map`, so it represents the type of +the new collection that gets created. + +How is the `That` type determined? In fact it is linked to the other +types by an implicit parameter `cbf`, of type `CanBuildFrom[Repr, B, That]`. +These `CanBuildFrom` implicits are defined by the individual +collection classes. Recall that an implicit value of type +`CanBuildFrom[Repr, B, That]` says: "Here is a way, given a collection +of type `Repr` and new elements of type `B`, to build a collection of type +`That` containing those elements". + +Now the behavior of `map` and `++` on `RNA2` sequences becomes +clearer. There is no `CanBuildFrom` instance that creates `RNA2` +sequences, so the next best available `CanBuildFrom` was found in the +companion object of the inherited trait `IndexedSeq`. That implicit +creates `Vector`s (recall that `Vector` is the default implementation +of `IndexedSeq`), and that's what you saw when applying `map` to +`rna2`. + + +### Final version of RNA strands class ### + + final class RNA private (val groups: Array[Int], val length: Int) + extends IndexedSeq[Base] with IndexedSeqLike[Base, RNA] { + + import RNA._ + + // Mandatory re-implementation of `newBuilder` in `IndexedSeq` + override protected[this] def newBuilder: Builder[Base, RNA] = + RNA.newBuilder + + // Mandatory implementation of `apply` in `IndexedSeq` + def apply(idx: Int): Base = { + if (idx < 0 || length <= idx) + throw new IndexOutOfBoundsException + Base.fromInt(groups(idx / N) >> (idx % N * S) & M) + } + + // Optional re-implementation of foreach, + // to make it more efficient. + override def foreach[U](f: Base => U): Unit = { + var i = 0 + var b = 0 + while (i < length) { + b = if (i % N == 0) groups(i / N) else b >>> S + f(Base.fromInt(b & M)) + i += 1 + } + } + } + +### Final version of RNA companion object ### + + object RNA { + + private val S = 2 // number of bits in group + private val M = (1 << S) - 1 // bitmask to isolate a group + private val N = 32 / S // number of groups in an Int + + def fromSeq(buf: Seq[Base]): RNA = { + val groups = new Array[Int]((buf.length + N - 1) / N) + for (i <- 0 until buf.length) + groups(i / N) |= Base.toInt(buf(i)) << (i % N * S) + new RNA(groups, buf.length) + } + + def apply(bases: Base*) = fromSeq(bases) + + def newBuilder: Builder[Base, RNA] = + new ArrayBuffer mapResult fromSeq + + implicit def canBuildFrom: CanBuildFrom[RNA, Base, RNA] = + new CanBuildFrom[RNA, Base, RNA] { + def apply(): Builder[Base, RNA] = newBuilder + def apply(from: RNA): Builder[Base, RNA] = newBuilder + } + } + +To address this shortcoming, you need to define an implicit instance +of `CanBuildFrom` in the companion object of the RNA class. That +instance should have type `CanBuildFrom[RNA, Base, RNA]`. Hence, this +instance states that, given an RNA strand and a new element type `Base`, +you can build another collection which is again an RNA strand. The two +listings above of [class `RNA`](#final-version-of-rna-strands-class) and +[its companion object](#final-version-of-rna-companion-object) show the +details. Compared to [class `RNA2`](#second-version-of-rna-strands-class) +there are two important +differences. First, the `newBuilder` implementation has moved from the +RNA class to its companion object. The `newBuilder` method in class `RNA` +simply forwards to this definition. Second, there is now an implicit +`CanBuildFrom` value in object `RNA`. To create this value you need to +define two `apply` methods in the `CanBuildFrom` trait. Both create a new +builder for an `RNA` collection, but they differ in their argument +list. The `apply()` method simply creates a new builder of the right +type. By contrast, the `apply(from)` method takes the original +collection as argument. This can be useful to adapt the dynamic type +of builder's return type to be the same as the dynamic type of the +receiver. In the case of `RNA` this does not come into play because `RNA` +is a final class, so any receiver of static type `RNA` also has `RNA` as +its dynamic type. That's why `apply(from)` also simply calls `newBuilder`, +ignoring its argument. + +That is it. The final [`RNA` class](#final-version-of-rna-strands-class) +implements all collection methods at +their expected types. Its implementation requires a little bit of +protocol. In essence, you need to know where to put the `newBuilder` +factories and the `canBuildFrom` implicits. On the plus side, with +relatively little code you get a large number of methods automatically +defined. Also, if you don't intend to do bulk operations like `take`, +`drop`, `map`, or `++` on your collection you can choose to not go the extra +length and stop at the implementation shown in for [class `RNA1`](#first-version-of-rna-strands-class). + +The discussion so far centered on the minimal amount of definitions +needed to define new sequences with methods that obey certain +types. But in practice you might also want to add new functionality to +your sequences or to override existing methods for better +efficiency. An example of this is the overridden `foreach` method in +class `RNA`. `foreach` is an important method in its own right because it +implements loops over collections. Furthermore, many other collection +methods are implemented in terms of `foreach`. So it makes sense to +invest some effort optimizing the method's implementation. The +standard implementation of `foreach` in `IndexedSeq` will simply select +every `i`'th element of the collection using `apply`, where `i` ranges from +0 to the collection's length minus one. So this standard +implementation selects an array element and unpacks a base from it +once for every element in an RNA strand. The overriding `foreach` in +class `RNA` is smarter than that. For every selected array element it +immediately applies the given function to all bases contained in +it. So the effort for array selection and bit unpacking is much +reduced. + +## Integrating a new prefix map ## + +As a second example you'll learn how to integrate a new kind of map +into the collection framework. The idea is to implement a mutable map +with `String` as the type of keys by a "Patricia trie". The term +*Patricia* is in fact an abbreviation for "Practical Algorithm to +Retrieve Information Coded in Alphanumeric" and *trie* comes from +re*trie*val (a trie is also called a radix tree or prefix tree). +The idea is to store a set or a map as a tree where subsequent +characters in a search key +uniquely determine a path through the tree. For instance a Patricia trie +storing the strings "abc", "abd", "al", "all" and "xy" would look +like this: + +A sample patricia trie: + + +To find the node corresponding to the string "abc" in this trie, +simply follow the subtree labeled "a", proceed from there to the +subtree labelled "b", to finally reach its subtree labelled "c". If +the Patricia trie is used as a map, the value that's associated with a +key is stored in the nodes that can be reached by the key. If it is a +set, you simply store a marker saying that the node is present in the +set. + +Patricia tries support very efficient lookups and updates. Another +nice feature is that they support selecting a subcollection by giving +a prefix. For instance, in the patricia tree above you can obtain the +sub-collection of all keys that start with an "a" simply by following +the "a" link from the root of the tree. + +Based on these ideas we will now walk you through the implementation +of a map that's implemented as a Patricia trie. We call the map a +`PrefixMap`, which means that it provides a method `withPrefix` that +selects a submap of all keys starting with a given prefix. We'll first +define a prefix map with the keys shown in the running example: + + scala> val m = PrefixMap("abc" -> 0, "abd" -> 1, "al" -> 2, + "all" -> 3, "xy" -> 4) + m: PrefixMap[Int] = Map((abc,0), (abd,1), (al,2), (all,3), (xy,4)) + +Then calling `withPrefix` on `m` will yield another prefix map: + + scala> m withPrefix "a" + res14: PrefixMap[Int] = Map((bc,0), (bd,1), (l,2), (ll,3)) + +### Patricia trie implementation ### + + import collection._ + + class PrefixMap[T] + extends mutable.Map[String, T] + with mutable.MapLike[String, T, PrefixMap[T]] { + + var suffixes: immutable.Map[Char, PrefixMap[T]] = Map.empty + var value: Option[T] = None + + def get(s: String): Option[T] = + if (s.isEmpty) value + else suffixes get (s(0)) flatMap (_.get(s substring 1)) + + def withPrefix(s: String): PrefixMap[T] = + if (s.isEmpty) this + else { + val leading = s(0) + suffixes get leading match { + case None => + suffixes = suffixes + (leading -> empty) + case _ => + } + suffixes(leading) withPrefix (s substring 1) + } + + override def update(s: String, elem: T) = + withPrefix(s).value = Some(elem) + + override def remove(s: String): Option[T] = + if (s.isEmpty) { val prev = value; value = None; prev } + else suffixes get (s(0)) flatMap (_.remove(s substring 1)) + + def iterator: Iterator[(String, T)] = + (for (v <- value.iterator) yield ("", v)) ++ + (for ((chr, m) <- suffixes.iterator; + (s, v) <- m.iterator) yield (chr +: s, v)) + + def += (kv: (String, T)): this.type = { update(kv._1, kv._2); this } + + def -= (s: String): this.type = { remove(s); this } + + override def empty = new PrefixMap[T] + } + + +The previous listing shows the definition of `PrefixMap`. The map has +keys of type `String` and the values are of parametric type `T`. It extends +`mutable.Map[String, T]` and `mutable.MapLike[String, T, PrefixMap[T]]`. +You have seen this pattern already for sequences in the +RNA strand example; then as now inheriting an implementation class +such as `MapLike` serves to get the right result type for +transformations such as `filter`. + +A prefix map node has two mutable fields: `suffixes` and `value`. The +`value` field contains an optional value that's associated with the +node. It is initialized to `None`. The `suffixes` field contains a map +from characters to `PrefixMap` values. It is initialized to the empty +map. + +You might ask why we picked an immutable map as the implementation +type for `suffixes`? Would not a mutable map have been more standard, +since `PrefixMap` as a whole is also mutable? The answer is that +immutable maps that contain only a few elements are very efficient in +both space and execution time. For instance, maps that contain fewer +than 5 elements are represented as a single object. By contrast, the +standard mutable map is a `HashMap`, which typically occupies around 80 +bytes, even if it is empty. So if small collections are common, it's +better to pick immutable over mutable. In the case of Patricia tries, +we'd expect that most nodes except the ones at the very top of the +tree would contain only a few successors. So storing these successors +in an immutable map is likely to be more efficient. + +Now have a look at the first method that needs to be implemented for a +map: `get`. The algorithm is as follows: To get the value associated +with the empty string in a prefix map, simply select the optional +`value` stored in the root of the tree (the current map). +Otherwise, if the key string is +not empty, try to select the submap corresponding to the first +character of the string. If that yields a map, follow up by looking up +the remainder of the key string after its first character in that +map. If the selection fails, the key is not stored in the map, so +return with `None`. The combined selection over an option value `opt` is +elegantly expressed using `opt.flatMap(x => f(x))`. When applied to an +optional value that is `None`, it returns `None`. Otherwise `opt` is +`Some(x)` and the function `f` is applied to the encapsulated value `x`, +yielding a new option, which is returned by the flatmap. + +The next two methods to implement for a mutable map are `+=` and `-=`. In +the implementation of `PrefixMap`, these are defined in terms of two +other methods: `update` and `remove`. + +The `remove` method is very similar to `get`, except that before returning +any associated value, the field containing that value is set to +`None`. The `update` method first calls `withPrefix` to navigate to the tree +node that needs to be updated, then sets the `value` field of that node +to the given value. The `withPrefix` method navigates through the tree, +creating sub-maps as necessary if some prefix of characters is not yet +contained as a path in the tree. + +The last abstract method to implement for a mutable map is +`iterator`. This method needs to produce an iterator that yields all +key/value pairs stored in the map. For any given prefix map this +iterator is composed of the following parts: First, if the map +contains a defined value, `Some(x)`, in the `value` field at its root, +then `("", x)` is the first element returned from the +iterator. Furthermore, the iterator needs to traverse the iterators of +all submaps stored in the `suffixes` field, but it needs to add a +character in front of every key string returned by those +iterators. More precisely, if `m` is the submap reached from the root +through a character `chr`, and `(s, v)` is an element returned from +`m.iterator`, then the root's iterator will return `(chr +: s, v)` +instead. This logic is implemented quite concisely as a concatenation +of two `for` expressions in the implementation of the `iterator` method in +`PrefixMap`. The first `for` expression iterates over `value.iterator`. This +makes use of the fact that `Option` values define an iterator method +that returns either no element, if the option value is `None`, or +exactly one element `x`, if the option value is `Some(x)`. + +Note that there is no `newBuilder` method defined in `PrefixMap`. There is +no need to, because maps and sets come with default builders, which +are instances of class `MapBuilder`. For a mutable map the default +builder starts with an empty map and then adds successive elements +using the map's `+=` method. For immutable maps, the non-destructive +element addition method `+` is used instead of method `+=`. Sets work +in the same way. + +However, in all these cases, to build the right kind of colletion +you need to start with an empty collection of that kind. This is +provided by the `empty` method, which is the last method defined in +`PrefixMap`. This method simply returns a fresh `PrefixMap`. + + +### The companion object for prefix maps ### + + import scala.collection.mutable.{Builder, MapBuilder} + import scala.collection.generic.CanBuildFrom + + object PrefixMap extends { + def empty[T] = new PrefixMap[T] + + def apply[T](kvs: (String, T)*): PrefixMap[T] = { + val m: PrefixMap[T] = empty + for (kv <- kvs) m += kv + m + } + + def newBuilder[T]: Builder[(String, T), PrefixMap[T]] = + new MapBuilder[String, T, PrefixMap[T]](empty) + + implicit def canBuildFrom[T] + : CanBuildFrom[PrefixMap[_], (String, T), PrefixMap[T]] = + new CanBuildFrom[PrefixMap[_], (String, T), PrefixMap[T]] { + def apply(from: PrefixMap[_]) = newBuilder[T] + def apply() = newBuilder[T] + } + } + +We'll now turn to the companion object `PrefixMap`. In fact it is not +strictly necessary to define this companion object, as class `PrefixMap` +can stand well on its own. The main purpose of object `PrefixMap` is to +define some convenience factory methods. It also defines a +`CanBuildFrom` implicit to make typing work out better. + +The two convenience methods are `empty` and `apply`. The same methods are +present for all other collections in Scala's collection framework so +it makes sense to define them here, too. With the two methods, you can +write `PrefixMap` literals like you do for any other collection: + + scala> PrefixMap("hello" -> 5, "hi" -> 2) + res0: PrefixMap[Int] = Map(hello -> 5, hi -> 2) + + scala> PrefixMap.empty[String] + res2: PrefixMap[String] = Map() + +The other member in object `PrefixMap` is an implicit `CanBuildFrom` +instance. It has the same purpose as the `CanBuildFrom` definition in +the [`RNA` companion object](#final-version-of-rna-companion-object) +from the last section: to make methods like `map` return the best possible +type. For instance, consider mapping a function over the key/value +pairs of a `PrefixMap`. As long as that function produces pairs of +strings and some second type, the result collection will again be a +`PrefixMap`. Here's an example: + + scala> res0 map { case (k, v) => (k + "!", "x" * v) } + res8: PrefixMap[String] = Map(hello! -> xxxxx, hi! -> xx) + +The given function argument takes the key/value bindings of the prefix +map `res0` and produces pairs of strings. The result of the `map` is a +`PrefixMap`, this time with value type `String` instead of `Int`. Without +the `canBuildFrom` implicit in `PrefixMap` the result would just have been +a general mutable map, not a prefix map. For convenience, the `PrefixMap` +object defines a `newBuilder` method, but it still just uses the +default `MapBuilder`. + +## Summary ## + +To summarize, if you want to fully integrate a new collection class +into the framework you need to pay attention to the following points: + +1. Decide whether the collection should be mutable or immutable. +2. Pick the right base traits for the collection. +3. Inherit from the right implementation trait to implement most + collection operations. +4. If you want `map` and similar operations to return instances of your + collection type, provide an implicit `CanBuildFrom` in your class's + companion object. + +You have now seen how Scala's collections are built and how you can +add new kinds of collections. Because of Scala's rich support for +abstraction, each new collection type has a large number of +methods without having to reimplement them all over again. + +### Acknowledgement ### + +These pages contain material adapted from the 2nd edition of +[Programming in Scala](http://www.artima.com/shop/programming_in_scala) by +Odersky, Spoon and Venners. We thank Artima for graciously agreeing to its +publication. diff --git a/_overviews/core/binary-compatibility-of-scala-releases.md b/_overviews/core/binary-compatibility-of-scala-releases.md new file mode 100644 index 0000000000..e2cbd0bd72 --- /dev/null +++ b/_overviews/core/binary-compatibility-of-scala-releases.md @@ -0,0 +1,33 @@ +--- +layout: singlepage-overview +title: Binary Compatibility of Scala Releases + +partof: binary-compatibility + +permalink: /overviews/core/:title.html +--- + +When two versions of Scala are binary compatible, it is safe to compile your project on one Scala version and link against another Scala version at run time. Safe run-time linkage (only!) means that the JVM does not throw a (subclass of) [`LinkageError`](http://docs.oracle.com/javase/7/docs/api/java/lang/LinkageError.html) when executing your program in the mixed scenario, assuming that none arise when compiling and running on the same version of Scala. Concretely, this means you may have external dependencies on your run-time classpath that use a different version of Scala than the one you're compiling with, as long as they're binary compatible. In other words, separate compilation on different binary compatible versions does not introduce problems compared to compiling and running everything on the same version of Scala. + +We check binary compatibility automatically with [MiMa](https://github.com/typesafehub/migration-manager). We strive to maintain a similar invariant for the `behavior` (as opposed to just linkage) of the standard library, but this is not checked mechanically (Scala is not a proof assistant so this is out of reach for its type system). + +#### Forwards and Back +We distinguish forwards and backwards compatibility (think of these as properties of a sequence of versions, not of an individual version). Maintaining backwards compatibility means code compiled on an older version will link with code compiled with newer ones. Forwards compatibility allows you to compile on new versions and run on older ones. + +Thus, backwards compatibility precludes the removal of (non-private) methods, as older versions could call them, not knowing they would be removed, whereas forwards compatibility disallows adding new (non-private) methods, because newer programs may come to depend on them, which would prevent them from running on older versions (private methods are exempted here as well, as their definition and call sites must be in the same compilation unit). + +These are strict constraints, but they have worked well for us since Scala 2.10.x. They didn't stop us from fixing large numbers of issues in minor releases. The advantages are clear, so we will maintain this policy for future Scala major releases. + +#### Meta +Note that so far we've only talked about the jars generated by scalac for the standard library and reflection. +Our policies do not extend to the meta-issue: ensuring binary compatibility for bytecode generated from identical sources, by different version of scalac? (The same problem exists for compiling on different JDKs.) While we strive to achieve this, it's not something we can test in general. Notable examples where we know meta-binary compatibility is hard to achieve: specialisation and the optimizer. + +In short, we recommend that library authors use to [MiMa](https://github.com/typesafehub/migration-manager) to verify compatibility of minor versions before releasing. +Compiling identical sources with different versions of the scala compiler (or on different JVM versions!) could result in binary incompatible bytecode. This is rare, and we try to avoid it, but we can't guarantee it will never happen. + +#### Concretely +We guarantee forwards and backwards compatibility of the `"org.scala-lang" % "scala-library" % "2.N.x"` and `"org.scala-lang" % "scala-reflect" % "2.N.x"` artifacts, except for anything under the `scala.reflect.internal` package, as scala-reflect is still experimental. We also strongly discourage relying on the stability of `scala.concurrent.impl` and `scala.reflect.runtime`, though we will only break compatibility for severe bugs here. + +Note that we will only enforce *backwards* binary compatibility for modules (artifacts under the groupId `org.scala-lang.modules`). As they are opt-in, it's less of a burden to require having the latest version on the classpath. (Without forward compatibility, the latest version of the artifact must be on the run-time classpath to avoid linkage errors.) + +Finally, since Scala 2.11, `scala-library-all` aggregates all modules that constitute a Scala release. Note that this means it does not provide forward binary compatibility, whereas the core `scala-library` artifact does. We consider the versions of the modules that `"scala-library-all" % "2.N.x"` depends on to be the canonical ones, that are part of the official Scala distribution. (The distribution itself is defined by the `scala-dist` maven artifact.) diff --git a/_overviews/core/futures.md b/_overviews/core/futures.md new file mode 100644 index 0000000000..6c3704b3ca --- /dev/null +++ b/_overviews/core/futures.md @@ -0,0 +1,1071 @@ +--- +layout: singlepage-overview +title: Futures and Promises + +partof: futures + +languages: [ja, zh-cn] + +permalink: /overviews/core/:title.html +--- + +**By: Philipp Haller, Aleksandar Prokopec, Heather Miller, Viktor Klang, Roland Kuhn, and Vojin Jovanovic** + +## Introduction + +Futures provide a way to reason about performing many operations +in parallel-- in an efficient and non-blocking way. +A [`Future`](http://www.scala-lang.org/api/current/index.html#scala.concurrent.Future) +is a placeholder object for a value that may not yet exist. +Generally, the value of the Future is supplied concurrently and can subsequently be used. +Composing concurrent tasks in this way tends to result in faster, asynchronous, non-blocking parallel code. + +By default, futures and promises are non-blocking, making use of +callbacks instead of typical blocking operations. +To simplify the use of callbacks both syntactically and conceptually, +Scala provides combinators such as `flatMap`, `foreach`, and `filter` used to compose +futures in a non-blocking way. +Blocking is still possible - for cases where it is absolutely +necessary, futures can be blocked on (although this is discouraged). + + + +A typical future looks like this: + + val inverseFuture: Future[Matrix] = Future { + fatMatrix.inverse() // non-blocking long lasting computation + }(executionContext) + + +Or with the more idiomatic: + + implicit val ec: ExecutionContext = ... + val inverseFuture : Future[Matrix] = Future { + fatMatrix.inverse() + } // ec is implicitly passed + + +Both code snippets delegate the execution of `fatMatrix.inverse()` to an `ExecutionContext` and embody the result of the computation in `inverseFuture`. + + +## Execution Context + +Future and Promises revolve around [`ExecutionContext`s](http://www.scala-lang.org/api/current/index.html#scala.concurrent.ExecutionContext), responsible for executing computations. + +An `ExecutionContext` is similar to an [Executor](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Executor.html): +it is free to execute computations in a new thread, in a pooled thread or in the current thread +(although executing the computation in the current thread is discouraged -- more on that below). + +The `scala.concurrent` package comes out of the box with an `ExecutionContext` implementation, a global static thread pool. +It is also possible to convert an `Executor` into an `ExecutionContext`. +Finally, users are free to extend the `ExecutionContext` trait to implement their own execution contexts, +although this should only be done in rare cases. + + +### The Global Execution Context + +`ExecutionContext.global` is an `ExecutionContext` backed by a [ForkJoinPool](http://docs.oracle.com/javase/tutorial/essential/concurrency/forkjoin.html). +It should be sufficient for most situations but requires some care. +A `ForkJoinPool` manages a limited amount of threads (the maximum amount of thread being referred to as *parallelism level*). +The number of concurrently blocking computations can exceed the parallelism level +only if each blocking call is wrapped inside a `blocking` call (more on that below). +Otherwise, there is a risk that the thread pool in the global execution context is starved, +and no computation can proceed. + +By default the `ExecutionContext.global` sets the parallelism level of its underlying fork-join pool to the amount of available processors +([Runtime.availableProcessors](http://docs.oracle.com/javase/7/docs/api/java/lang/Runtime.html#availableProcessors%28%29)). +This configuration can be overridden by setting one (or more) of the following VM attributes: + + * scala.concurrent.context.minThreads - defaults to `Runtime.availableProcessors` + * scala.concurrent.context.numThreads - can be a number or a multiplier (N) in the form 'xN' ; defaults to `Runtime.availableProcessors` + * scala.concurrent.context.maxThreads - defaults to `Runtime.availableProcessors` + +The parallelism level will be set to `numThreads` as long as it remains within `[minThreads; maxThreads]`. + +As stated above the `ForkJoinPool` can increase the amount of threads beyond its `parallelismLevel` in the presence of blocking computation. +As explained in the `ForkJoinPool` API, this is only possible if the pool is explicitly notified: + + import scala.concurrent.Future + import scala.concurrent.forkjoin._ + + // the following is equivalent to `implicit val ec = ExecutionContext.global` + import ExecutionContext.Implicits.global + + Future { + ForkJoinPool.managedBlock( + new ManagedBlocker { + var done = false + + def block(): Boolean = { + try { + myLock.lock() + // ... + } finally { + done = true + } + true + } + + def isReleasable: Boolean = done + } + ) + } + + +Fortunately the concurrent package provides a convenient way for doing so: + + import scala.concurrent.Future + import scala.concurrent.blocking + + Future { + blocking { + myLock.lock() + // ... + } + } + +Note that `blocking` is a general construct that will be discussed more in depth [below](#in_a_future). + +Last but not least, you must remember that the `ForkJoinPool` is not designed for long lasting blocking operations. +Even when notified with `blocking` the pool might not spawn new workers as you would expect, +and when new workers are created they can be as many as 32767. +To give you an idea, the following code will use 32000 threads: + + implicit val ec = ExecutionContext.global + + for( i <- 1 to 32000 ) { + Future { + blocking { + Thread.sleep(999999) + } + } + } + + +If you need to wrap long lasting blocking operations we recommend using a dedicated `ExecutionContext`, for instance by wrapping a Java `Executor`. + + +### Adapting a Java Executor + +Using the `ExecutionContext.fromExecutor` method you can wrap a Java `Executor` into an `ExecutionContext`. +For instance: + + ExecutionContext.fromExecutor(new ThreadPoolExecutor( /* your configuration */ )) + + +### Synchronous Execution Context + +One might be tempted to have an `ExecutionContext` that runs computations within the current thread: + + val currentThreadExecutionContext = ExecutionContext.fromExecutor( + new Executor { + // Do not do this! + def execute(runnable: Runnable) { runnable.run() } + }) + +This should be avoided as it introduces non-determinism in the execution of your future. + + Future { + doSomething + }(ExecutionContext.global).map { + doSomethingElse + }(currentThreadExecutionContext) + +The `doSomethingElse` call might either execute in `doSomething`'s thread or in the main thread, and therefore be either asynchronous or synchronous. +As explained [here](http://blog.ometer.com/2011/07/24/callbacks-synchronous-and-asynchronous/) a callback should not be both. + + + +## Futures + +A `Future` is an object holding a value which may become available at some point. +This value is usually the result of some other computation: + +1. If the computation has not yet completed, we say that the `Future` is **not completed.** +2. If the computation has completed with a value or with an exception, we say that the `Future` is **completed**. + +Completion can take one of two forms: + +1. When a `Future` is completed with a value, we say that the future was **successfully completed** with that value. +2. When a `Future` is completed with an exception thrown by the computation, we say that the `Future` was **failed** with that exception. + +A `Future` has an important property that it may only be assigned +once. +Once a `Future` object is given a value or an exception, it becomes +in effect immutable-- it can never be overwritten. + +The simplest way to create a future object is to invoke the `Future.apply` +method which starts an asynchronous computation and returns a +future holding the result of that computation. +The result becomes available once the future completes. + +Note that `Future[T]` is a type which denotes future objects, whereas +`Future.apply` is a method which creates and schedules an asynchronous +computation, and then returns a future object which will be completed +with the result of that computation. + +This is best shown through an example. + +Let's assume that we want to use a hypothetical API of some +popular social network to obtain a list of friends for a given user. +We will open a new session and then send +a request to obtain a list of friends of a particular user: + + import scala.concurrent._ + import ExecutionContext.Implicits.global + + val session = socialNetwork.createSessionFor("user", credentials) + val f: Future[List[Friend]] = Future { + session.getFriends() + } + +Above, we first import the contents of the `scala.concurrent` package +to make the type `Future` visible. +We will explain the second import shortly. + +We then initialize a session variable which we will use to send +requests to the server, using a hypothetical `createSessionFor` +method. +To obtain the list of friends of a user, a request +has to be sent over a network, which can take a long time. +This is illustrated with the call to the method `getFriends` that returns `List[Friend]`. +To better utilize the CPU until the response arrives, we should not +block the rest of the program-- this computation should be scheduled +asynchronously. The `Future.apply` method does exactly that-- it performs +the specified computation block concurrently, in this case sending +a request to the server and waiting for a response. + +The list of friends becomes available in the future `f` once the server +responds. + +An unsuccessful attempt may result in an exception. In +the following example, the `session` value is incorrectly +initialized, so the computation in the `Future` block will throw a `NullPointerException`. +This future `f` is then failed with this exception instead of being completed successfully: + + val session = null + val f: Future[List[Friend]] = Future { + session.getFriends + } + +The line `import ExecutionContext.Implicits.global` above imports +the default global execution context. +Execution contexts execute tasks submitted to them, and +you can think of execution contexts as thread pools. +They are essential for the `Future.apply` method because +they handle how and when the asynchronous computation is executed. +You can define your own execution contexts and use them with `Future`, +but for now it is sufficient to know that +you can import the default execution context as shown above. + +Our example was based on a hypothetical social network API where +the computation consists of sending a network request and waiting +for a response. +It is fair to offer an example involving an asynchronous computation +which you can try out of the box. Assume you have a text file and +you want to find the position of the first occurrence of a particular keyword. +This computation may involve blocking while the file contents +are being retrieved from the disk, so it makes sense to perform it +concurrently with the rest of the computation. + + val firstOccurrence: Future[Int] = Future { + val source = scala.io.Source.fromFile("myText.txt") + source.toSeq.indexOfSlice("myKeyword") + } + + +### Callbacks + +We now know how to start an asynchronous computation to create a new +future value, but we have not shown how to use the result once it +becomes available, so that we can do something useful with it. +We are often interested in the result of the computation, not just its +side-effects. + +In many future implementations, once the client of the future becomes interested +in its result, it has to block its own computation and wait until the future is completed-- +only then can it use the value of the future to continue its own computation. +Although this is allowed by the Scala `Future` API as we will show later, +from a performance point of view a better way to do it is in a completely +non-blocking way, by registering a callback on the future. +This callback is called asynchronously once the future is completed. If the +future has already been completed when registering the callback, then +the callback may either be executed asynchronously, or sequentially on +the same thread. + +The most general form of registering a callback is by using the `onComplete` +method, which takes a callback function of type `Try[T] => U`. +The callback is applied to the value +of type `Success[T]` if the future completes successfully, or to a value +of type `Failure[T]` otherwise. + +The `Try[T]` is similar to `Option[T]` or `Either[T, S]`, in that it is a monad +potentially holding a value of some type. +However, it has been specifically designed to either hold a value or +some throwable object. +Where an `Option[T]` could either be a value (i.e. `Some[T]`) or no value +at all (i.e. `None`), `Try[T]` is a `Success[T]` when it holds a value +and otherwise `Failure[T]`, which holds an exception. `Failure[T]` holds +more information than just a plain `None` by saying why the value is not +there. +In the same time, you can think of `Try[T]` as a special version +of `Either[Throwable, T]`, specialized for the case when the left +value is a `Throwable`. + +Coming back to our social network example, let's assume we want to +fetch a list of our own recent posts and render them to the screen. +We do so by calling a method `getRecentPosts` which returns +a `List[String]`-- a list of recent textual posts: + + import scala.util.{Success, Failure} + + val f: Future[List[String]] = Future { + session.getRecentPosts + } + + f onComplete { + case Success(posts) => for (post <- posts) println(post) + case Failure(t) => println("An error has occured: " + t.getMessage) + } + +The `onComplete` method is general in the sense that it allows the +client to handle the result of both failed and successful future +computations. To handle only successful results, the `onSuccess` +callback is used (which takes a partial function): + + val f: Future[List[String]] = Future { + session.getRecentPosts + } + + f onSuccess { + case posts => for (post <- posts) println(post) + } + +To handle failed results, the `onFailure` callback is used: + + val f: Future[List[String]] = Future { + session.getRecentPosts + } + + f onFailure { + case t => println("An error has occured: " + t.getMessage) + } + + f onSuccess { + case posts => for (post <- posts) println(post) + } + +The `onFailure` callback is only executed if the future fails, that +is, if it contains an exception. + +Since partial functions have the `isDefinedAt` method, the +`onFailure` method only triggers the callback if it is defined for a +particular `Throwable`. In the following example the registered `onFailure` +callback is never triggered: + + val f = Future { + 2 / 0 + } + + f onFailure { + case npe: NullPointerException => + println("I'd be amazed if this printed out.") + } + +Coming back to the previous example with searching for the first +occurrence of a keyword, you might want to print the position +of the keyword to the screen: + + val firstOccurrence: Future[Int] = Future { + val source = scala.io.Source.fromFile("myText.txt") + source.toSeq.indexOfSlice("myKeyword") + } + + firstOccurrence onSuccess { + case idx => println("The keyword first appears at position: " + idx) + } + + firstOccurrence onFailure { + case t => println("Could not process file: " + t.getMessage) + } + +The `onComplete`, `onSuccess`, and +`onFailure` methods have result type `Unit`, which means invocations +of these methods cannot be chained. Note that this design is intentional, +to avoid suggesting that chained +invocations may imply an ordering on the execution of the registered +callbacks (callbacks registered on the same future are unordered). + +That said, we should now comment on **when** exactly the callback +gets called. Since it requires the value in the future to be available, +it can only be called after the future is completed. +However, there is no guarantee it will be called by the thread +that completed the future or the thread which created the callback. +Instead, the callback is executed by some thread, at some time +after the future object is completed. +We say that the callback is executed **eventually**. + +Furthermore, the order in which the callbacks are executed is +not predefined, even between different runs of the same application. +In fact, the callbacks may not be called sequentially one after the other, +but may concurrently execute at the same time. +This means that in the following example the variable `totalA` may not be set +to the correct number of lower case and upper case `a` characters from the computed +text. + + @volatile var totalA = 0 + + val text = Future { + "na" * 16 + "BATMAN!!!" + } + + text onSuccess { + case txt => totalA += txt.count(_ == 'a') + } + + text onSuccess { + case txt => totalA += txt.count(_ == 'A') + } + +Above, the two callbacks may execute one after the other, in +which case the variable `totalA` holds the expected value `18`. +However, they could also execute concurrently, so `totalA` could +end up being either `16` or `2`, since `+=` is not an atomic +operation (i.e. it consists of a read and a write step which may +interleave arbitrarily with other reads and writes). + +For the sake of completeness the semantics of callbacks are listed here: + +1. Registering an `onComplete` callback on the future +ensures that the corresponding closure is invoked after +the future is completed, eventually. + +2. Registering an `onSuccess` or `onFailure` callback has the same +semantics as `onComplete`, with the difference that the closure is only called +if the future is completed successfully or fails, respectively. + +3. Registering a callback on the future which is already completed +will result in the callback being executed eventually (as implied by +1). + +4. In the event that multiple callbacks are registered on the future, +the order in which they are executed is not defined. In fact, the +callbacks may be executed concurrently with one another. +However, a particular `ExecutionContext` implementation may result +in a well-defined order. + +5. In the event that some of the callbacks throw an exception, the +other callbacks are executed regardless. + +6. In the event that some of the callbacks never complete (e.g. the +callback contains an infinite loop), the other callbacks may not be +executed at all. In these cases, a potentially blocking callback must +use the `blocking` construct (see below). + +7. Once executed, the callbacks are removed from the future object, +thus being eligible for GC. + + +### Functional Composition and For-Comprehensions + +The callback mechanism we have shown is sufficient to chain future +results with subsequent computations. +However, it is sometimes inconvenient and results in bulky code. +We show this with an example. Assume we have an API for +interfacing with a currency trading service. Suppose we want to buy US +dollars, but only when it's profitable. We first show how this could +be done using callbacks: + + val rateQuote = Future { + connection.getCurrentValue(USD) + } + + rateQuote onSuccess { case quote => + val purchase = Future { + if (isProfitable(quote)) connection.buy(amount, quote) + else throw new Exception("not profitable") + } + + purchase onSuccess { + case _ => println("Purchased " + amount + " USD") + } + } + +We start by creating a future `rateQuote` which gets the current exchange +rate. +After this value is obtained from the server and the future successfully +completed, the computation proceeds in the `onSuccess` callback and we are +ready to decide whether to buy or not. +We therefore create another future `purchase` which makes a decision to buy only if it's profitable +to do so, and then sends a request. +Finally, once the purchase is completed, we print a notification message +to the standard output. + +This works, but is inconvenient for two reasons. First, we have to use +`onSuccess`, and we have to nest the second `purchase` future within +it. Imagine that after the `purchase` is completed we want to sell +some other currency. We would have to repeat this pattern within the +`onSuccess` callback, making the code overly indented, bulky and hard +to reason about. + +Second, the `purchase` future is not in the scope with the rest of +the code-- it can only be acted upon from within the `onSuccess` +callback. This means that other parts of the application do not +see the `purchase` future and cannot register another `onSuccess` +callback to it, for example, to sell some other currency. + +For these two reasons, futures provide combinators which allow a +more straightforward composition. One of the basic combinators +is `map`, which, given a future and a mapping function for the value of +the future, produces a new future that is completed with the +mapped value once the original future is successfully completed. +You can reason about mapping futures in the same way you reason +about mapping collections. + +Let's rewrite the previous example using the `map` combinator: + + val rateQuote = Future { + connection.getCurrentValue(USD) + } + + val purchase = rateQuote map { quote => + if (isProfitable(quote)) connection.buy(amount, quote) + else throw new Exception("not profitable") + } + + purchase onSuccess { + case _ => println("Purchased " + amount + " USD") + } + +By using `map` on `rateQuote` we have eliminated one `onSuccess` callback and, +more importantly, the nesting. +If we now decide to sell some other currency, it suffices to use +`map` on `purchase` again. + +But what happens if `isProfitable` returns `false`, hence causing +an exception to be thrown? +In that case `purchase` is failed with that exception. +Furthermore, imagine that the connection was broken and that +`getCurrentValue` threw an exception, failing `rateQuote`. +In that case we'd have no value to map, so the `purchase` would +automatically be failed with the same exception as `rateQuote`. + +In conclusion, if the original future is +completed successfully then the returned future is completed with a +mapped value from the original future. If the mapping function throws +an exception the future is completed with that exception. If the +original future fails with an exception then the returned future also +contains the same exception. This exception propagating semantics is +present in the rest of the combinators, as well. + +One of the design goals for futures was to enable their use in for-comprehensions. +For this reason, futures also have the `flatMap`, `filter` and +`foreach` combinators. The `flatMap` method takes a function that maps the value +to a new future `g`, and then returns a future which is completed once +`g` is completed. + +Lets assume that we want to exchange US dollars for Swiss francs +(CHF). We have to fetch quotes for both currencies, and then decide on +buying based on both quotes. +Here is an example of `flatMap` and `withFilter` usage within for-comprehensions: + + val usdQuote = Future { connection.getCurrentValue(USD) } + val chfQuote = Future { connection.getCurrentValue(CHF) } + + val purchase = for { + usd <- usdQuote + chf <- chfQuote + if isProfitable(usd, chf) + } yield connection.buy(amount, chf) + + purchase onSuccess { + case _ => println("Purchased " + amount + " CHF") + } + +The `purchase` future is completed only once both `usdQuote` +and `chfQuote` are completed-- it depends on the values +of both these futures so its own computation cannot begin +earlier. + +The for-comprehension above is translated into: + + val purchase = usdQuote flatMap { + usd => + chfQuote + .withFilter(chf => isProfitable(usd, chf)) + .map(chf => connection.buy(amount, chf)) + } + +which is a bit harder to grasp than the for-comprehension, but +we analyze it to better understand the `flatMap` operation. +The `flatMap` operation maps its own value into some other future. +Once this different future is completed, the resulting future +is completed with its value. +In our example, `flatMap` uses the value of the `usdQuote` future +to map the value of the `chfQuote` into a third future which +sends a request to buy a certain amount of Swiss francs. +The resulting future `purchase` is completed only once this third +future returned from `map` completes. + +This can be mind-boggling, but fortunately the `flatMap` operation +is seldom used outside for-comprehensions, which are easier to +use and understand. + +The `filter` combinator creates a new future which contains the value +of the original future only if it satisfies some predicate. Otherwise, +the new future is failed with a `NoSuchElementException`. For futures +calling `filter` has exactly the same effect as does calling `withFilter`. + +The relationship between the `collect` and `filter` combinator is similar +to the relationship of these methods in the collections API. + +It is important to note that calling the `foreach` combinator does not +block to traverse the value once it becomes available. +Instead, the function for the `foreach` gets asynchronously +executed only if the future is completed successfully. This means that +the `foreach` has exactly the same semantics as the `onSuccess` +callback. + +Since the `Future` trait can conceptually contain two types of values +(computation results and exceptions), there exists a need for +combinators which handle exceptions. + +Let's assume that based on the `rateQuote` we decide to buy a certain +amount. The `connection.buy` method takes an `amount` to buy and the expected +`quote`. It returns the amount bought. If the +`quote` has changed in the meanwhile, it will throw a +`QuoteChangedException` and it will not buy anything. If we want our +future to contain `0` instead of the exception, we use the `recover` +combinator: + + val purchase: Future[Int] = rateQuote map { + quote => connection.buy(amount, quote) + } recover { + case QuoteChangedException() => 0 + } + +The `recover` combinator creates a new future which holds the same +result as the original future if it completed successfully. If it did +not then the partial function argument is applied to the `Throwable` +which failed the original future. If it maps the `Throwable` to some +value, then the new future is successfully completed with that value. +If the partial function is not defined on that `Throwable`, then the +resulting future is failed with the same `Throwable`. + +The `recoverWith` combinator creates a new future which holds the +same result as the original future if it completed successfully. +Otherwise, the partial function is applied to the `Throwable` which +failed the original future. If it maps the `Throwable` to some future, +then this future is completed with the result of that future. +Its relation to `recover` is similar to that of `flatMap` to `map`. + +Combinator `fallbackTo` creates a new future which holds the result +of this future if it was completed successfully, or otherwise the +successful result of the argument future. In the event that both this +future and the argument future fail, the new future is completed with +the exception from this future, as in the following example which +tries to print US dollar value, but prints the Swiss franc value in +the case it fails to obtain the dollar value: + + val usdQuote = Future { + connection.getCurrentValue(USD) + } map { + usd => "Value: " + usd + "$" + } + val chfQuote = Future { + connection.getCurrentValue(CHF) + } map { + chf => "Value: " + chf + "CHF" + } + + val anyQuote = usdQuote fallbackTo chfQuote + + anyQuote onSuccess { println(_) } + +The `andThen` combinator is used purely for side-effecting purposes. +It returns a new future with exactly the same result as the current +future, regardless of whether the current future failed or not. +Once the current future is completed with the result, the closure +corresponding to the `andThen` is invoked and then the new future is +completed with the same result as this future. This ensures that +multiple `andThen` calls are ordered, as in the following example +which stores the recent posts from a social network to a mutable set +and then renders all the posts to the screen: + + val allposts = mutable.Set[String]() + + Future { + session.getRecentPosts + } andThen { + case Success(posts) => allposts ++= posts + } andThen { + case _ => + clearAll() + for (post <- allposts) render(post) + } + +In summary, the combinators on futures are purely functional. +Every combinator returns a new future which is related to the +future it was derived from. + + +### Projections + +To enable for-comprehensions on a result returned as an exception, +futures also have projections. If the original future fails, the +`failed` projection returns a future containing a value of type +`Throwable`. If the original future succeeds, the `failed` projection +fails with a `NoSuchElementException`. The following is an example +which prints the exception to the screen: + + val f = Future { + 2 / 0 + } + for (exc <- f.failed) println(exc) + +The following example does not print anything to the screen: + + val f = Future { + 4 / 2 + } + for (exc <- f.failed) println(exc) + + + + + + + +### Extending Futures + +Support for extending the Futures API with additional utility methods is planned. +This will allow external frameworks to provide more specialized utilities. + + +## Blocking + +Futures are generally asynchronous and do not block the underlying execution threads. +However, in certain cases, it is necessary to block. +We distinguish two forms of blocking the execution thread: +invoking arbitrary code that blocks the thread from within the future, +and blocking from outside another future, waiting until that future gets completed. + + +### Blocking inside a Future + +As seen with the global `ExecutionContext`, it is possible to notify an `ExecutionContext` of a blocking call with the `blocking` construct. +The implementation is however at the complete discretion of the `ExecutionContext`. While some `ExecutionContext` such as `ExecutionContext.global` +implement `blocking` by means of a `ManagedBlocker`, some execution contexts such as the fixed thread pool: + + ExecutionContext.fromExecutor(Executors.newFixedThreadPool(x)) + +will do nothing, as shown in the following: + + implicit val ec = ExecutionContext.fromExecutor( + Executors.newFixedThreadPool(4)) + Future { + blocking { blockingStuff() } + } + +Has the same effect as + + Future { blockingStuff() } + +The blocking code may also throw an exception. In this case, the exception is forwarded to the caller. + + +### Blocking outside the Future + +As mentioned earlier, blocking on a future is strongly discouraged +for the sake of performance and for the prevention of deadlocks. +Callbacks and combinators on futures are a preferred way to use their results. +However, blocking may be necessary in certain situations and is supported by +the Futures and Promises API. + +In the currency trading example above, one place to block is at the +end of the application to make sure that all of the futures have been completed. +Here is an example of how to block on the result of a future: + + import scala.concurrent._ + import scala.concurrent.duration._ + + def main(args: Array[String]) { + val rateQuote = Future { + connection.getCurrentValue(USD) + } + + val purchase = rateQuote map { quote => + if (isProfitable(quote)) connection.buy(amount, quote) + else throw new Exception("not profitable") + } + + Await.result(purchase, 0 nanos) + } + +In the case that the future fails, the caller is forwarded the +exception that the future is failed with. This includes the `failed` +projection-- blocking on it results in a `NoSuchElementException` +being thrown if the original future is completed successfully. + +Alternatively, calling `Await.ready` waits until the future becomes +completed, but does not retrieve its result. In the same way, calling +that method will not throw an exception if the future is failed. + +The `Future` trait implements the `Awaitable` trait with methods +method `ready()` and `result()`. These methods cannot be called directly +by the clients-- they can only be called by the execution context. + + + +## Exceptions + +When asynchronous computations throw unhandled exceptions, futures +associated with those computations fail. Failed futures store an +instance of `Throwable` instead of the result value. `Future`s provide +the `onFailure` callback method, which accepts a `PartialFunction` to +be applied to a `Throwable`. The following special exceptions are +treated differently: + +1. `scala.runtime.NonLocalReturnControl[_]` -- this exception holds a value +associated with the return. Typically, `return` constructs in method +bodies are translated to `throw`s with this exception. Instead of +keeping this exception, the associated value is stored into the future or a promise. + +2. `ExecutionException` - stored when the computation fails due to an +unhandled `InterruptedException`, `Error` or a +`scala.util.control.ControlThrowable`. In this case the +`ExecutionException` has the unhandled exception as its cause. The rationale +behind this is to prevent propagation of critical and control-flow related +exceptions normally not handled by the client code and at the same time inform +the client in which future the computation failed. + +Fatal exceptions (as determined by `NonFatal`) are rethrown in the thread executing +the failed asynchronous computation. This informs the code managing the executing +threads of the problem and allows it to fail fast, if necessary. See +[`NonFatal`](http://www.scala-lang.org/api/current/index.html#scala.util.control.NonFatal$) +for a more precise description of the semantics. + +## Promises + +So far we have only considered `Future` objects created by +asynchronous computations started using the `future` method. +However, futures can also be created using *promises*. + +While futures are defined as a type of read-only placeholder object +created for a result which doesn't yet exist, a promise can be thought +of as a writable, single-assignment container, which completes a +future. That is, a promise can be used to successfully complete a +future with a value (by "completing" the promise) using the `success` +method. Conversely, a promise can also be used to complete a future +with an exception, by failing the promise, using the `failure` method. + +A promise `p` completes the future returned by `p.future`. This future +is specific to the promise `p`. Depending on the implementation, it +may be the case that `p.future eq p`. + +Consider the following producer-consumer example, in which one computation +produces a value and hands it off to another computation which consumes +that value. This passing of the value is done using a promise. + + import scala.concurrent.{ Future, Promise } + import scala.concurrent.ExecutionContext.Implicits.global + + val p = Promise[T]() + val f = p.future + + val producer = Future { + val r = produceSomething() + p success r + continueDoingSomethingUnrelated() + } + + val consumer = Future { + startDoingSomething() + f onSuccess { + case r => doSomethingWithResult() + } + } + +Here, we create a promise and use its `future` method to obtain the +`Future` that it completes. Then, we begin two asynchronous +computations. The first does some computation, resulting in a value +`r`, which is then used to complete the future `f`, by fulfilling +the promise `p`. The second does some computation, and then reads the result `r` +of the completed future `f`. Note that the `consumer` can obtain the +result before the `producer` task is finished executing +the `continueDoingSomethingUnrelated()` method. + +As mentioned before, promises have single-assignment semantics. As +such, they can be completed only once. Calling `success` on a +promise that has already been completed (or failed) will throw an +`IllegalStateException`. + +The following example shows how to fail a promise. + + val p = Promise[T]() + val f = p.future + + val producer = Future { + val r = someComputation + if (isInvalid(r)) + p failure (new IllegalStateException) + else { + val q = doSomeMoreComputation(r) + p success q + } + } + +Here, the `producer` computes an intermediate result `r`, and checks +whether it's valid. In the case that it's invalid, it fails the +promise by completing the promise `p` with an exception. In this case, +the associated future `f` is failed. Otherwise, the `producer` +continues its computation, and finally completes the future `f` with a +valid result, by completing promise `p`. + +Promises can also be completed with a `complete` method which takes +a potential value `Try[T]`-- either a failed result of type `Failure[Throwable]` or a +successful result of type `Success[T]`. + +Analogous to `success`, calling `failure` and `complete` on a promise that has already +been completed will throw an `IllegalStateException`. + +One nice property of programs written using promises with operations +described so far and futures which are composed through monadic +operations without side-effects is that these programs are +deterministic. Deterministic here means that, given that no exception +is thrown in the program, the result of the program (values observed +in the futures) will always be the same, regardless of the execution +schedule of the parallel program. + +In some cases the client may want to complete the promise only if it +has not been completed yet (e.g., there are several HTTP requests being +executed from several different futures and the client is interested only +in the first HTTP response - corresponding to the first future to +complete the promise). For these reasons methods `tryComplete`, +`trySuccess` and `tryFailure` exist on future. The client should be +aware that using these methods results in programs which are not +deterministic, but depend on the execution schedule. + +The method `completeWith` completes the promise with another +future. After the future is completed, the promise gets completed with +the result of that future as well. The following program prints `1`: + + val f = Future { 1 } + val p = Promise[Int]() + + p completeWith f + + p.future onSuccess { + case x => println(x) + } + +When failing a promise with an exception, three subtypes of `Throwable`s +are handled specially. If the `Throwable` used to break the promise is +a `scala.runtime.NonLocalReturnControl`, then the promise is completed with +the corresponding value. If the `Throwable` used to break the promise is +an instance of `Error`, `InterruptedException`, or +`scala.util.control.ControlThrowable`, the `Throwable` is wrapped as +the cause of a new `ExecutionException` which, in turn, is failing +the promise. + +Using promises, the `onComplete` method of the futures and the `future` construct +you can implement any of the functional composition combinators described earlier. +Let's assume you want to implement a new combinator `first` which takes +two futures `f` and `g` and produces a third future which is completed by either +`f` or `g` (whichever comes first), but only given that it is successful. + +Here is an example of how to do it: + + def first[T](f: Future[T], g: Future[T]): Future[T] = { + val p = promise[T] + + f onSuccess { + case x => p.trySuccess(x) + } + + g onSuccess { + case x => p.trySuccess(x) + } + + p.future + } + +Note that in this implementation, if neither `f` nor `g` succeeds, then `first(f, g)` never completes (either with a value or with an exception). + + + +## Utilities + +To simplify handling of time in concurrent applications `scala.concurrent` + introduces a `Duration` abstraction. `Duration` is not supposed to be yet another + general time abstraction. It is meant to be used with concurrency libraries and + resides in `scala.concurrent` package. + +`Duration` is the base class representing length of time. It can be either finite or infinite. + Finite duration is represented with `FiniteDuration` class which is constructed from `Long` length and + `java.util.concurrent.TimeUnit`. Infinite durations, also extended from `Duration`, + exist in only two instances , `Duration.Inf` and `Duration.MinusInf`. Library also + provides several `Duration` subclasses for implicit conversion purposes and those should + not be used. + +Abstract `Duration` contains methods that allow : + +1. Conversion to different time units (`toNanos`, `toMicros`, `toMillis`, +`toSeconds`, `toMinutes`, `toHours`, `toDays` and `toUnit(unit: TimeUnit)`). +2. Comparison of durations (`<`, `<=`, `>` and `>=`). +3. Arithmetic operations (`+`, `-`, `*`, `/` and `unary_-`). +4. Minimum and maximum between `this` duration and the one supplied in the argument (`min`, `max`). +5. Check if the duration is finite (`isFinite`). + +`Duration` can be instantiated in the following ways: + +1. Implicitly from types `Int` and `Long`. For example `val d = 100 millis`. +2. By passing a `Long` length and a `java.util.concurrent.TimeUnit`. +For example `val d = Duration(100, MILLISECONDS)`. +3. By parsing a string that represent a time period. For example `val d = Duration("1.2 µs")`. + +Duration also provides `unapply` methods so it can be used in pattern matching constructs. +Examples: + + import scala.concurrent.duration._ + import java.util.concurrent.TimeUnit._ + + // instantiation + val d1 = Duration(100, MILLISECONDS) // from Long and TimeUnit + val d2 = Duration(100, "millis") // from Long and String + val d3 = 100 millis // implicitly from Long, Int or Double + val d4 = Duration("1.2 µs") // from String + + // pattern matching + val Duration(length, unit) = 5 millis diff --git a/_overviews/core/implicit-classes.md b/_overviews/core/implicit-classes.md new file mode 100644 index 0000000000..8e9b29214f --- /dev/null +++ b/_overviews/core/implicit-classes.md @@ -0,0 +1,93 @@ +--- +layout: singlepage-overview +title: Implicit Classes + +partof: implicit-classes + +languages: [zh-cn] + +permalink: /overviews/core/:title.html +--- + +**Josh Suereth** + +## Introduction + +Scala 2.10 introduced a new feature called *implicit classes*. An *implicit class* is a class +marked with the `implicit` keyword. This keyword makes the class' primary constructor available +for implicit conversions when the class is in scope. + +Implicit classes were proposed in [SIP-13](http://docs.scala-lang.org/sips/pending/implicit-classes.html). + +## Usage + +To create an implicit class, simply place the `implicit` keyword in front of an appropriate +class. Here's an example: + + object Helpers { + implicit class IntWithTimes(x: Int) { + def times[A](f: => A): Unit = { + def loop(current: Int): Unit = + if(current > 0) { + f + loop(current - 1) + } + loop(x) + } + } + } + +This example creates the implicit class `IntWithTimes`. This class wraps an `Int` value and provides +a new method, `times`. To use this class, just import it into scope and call the `times` method. +Here's an example: + + scala> import Helpers._ + import Helpers._ + + scala> 5 times println("HI") + HI + HI + HI + HI + HI + +For an implicit class to work, its name must be in scope and unambiguous, like any other implicit +value or conversion. + + +## Restrictions + +Implicit classes have the following restrictions: + +**1. They must be defined inside of another `trait`/`class`/`object`.** + + + object Helpers { + implicit class RichInt(x: Int) // OK! + } + implicit class RichDouble(x: Double) // BAD! + + +**2. They may only take one non-implicit argument in their constructor.** + + + implicit class RichDate(date: java.util.Date) // OK! + implicit class Indexer[T](collecton: Seq[T], index: Int) // BAD! + implicit class Indexer[T](collecton: Seq[T])(implicit index: Index) // OK! + + +While it's possible to create an implicit class with more than one non-implicit argument, such classes +aren't used during implicit lookup. + + +**3. There may not be any method, member or object in scope with the same name as the implicit class.** + +*Note: This means an implicit class cannot be a case class*. + + object Bar + implicit class Bar(x: Int) // BAD! + + val x = 5 + implicit class x(y: Int) // BAD! + + implicit case class Baz(x: Int) // BAD! diff --git a/_overviews/core/string-interpolation.md b/_overviews/core/string-interpolation.md new file mode 100644 index 0000000000..a2215e8ef5 --- /dev/null +++ b/_overviews/core/string-interpolation.md @@ -0,0 +1,139 @@ +--- +layout: singlepage-overview +title: String Interpolation + +discourse: true + +partof: string-interpolation + +languages: [es, ja, zh-cn] + +permalink: /overviews/core/:title.html +--- + +**Josh Suereth** + +## Introduction + +Starting in Scala 2.10.0, Scala offers a new mechanism to create strings from your data: String Interpolation. +String Interpolation allows users to embed variable references directly in *processed* string literals. Here's an example: + + val name = "James" + println(s"Hello, $name") // Hello, James + +In the above, the literal `s"Hello, $name"` is a *processed* string literal. This means that the compiler does some additional +work to this literal. A processed string literal is denoted by a set of characters preceding the `"`. String interpolation +was introduced by [SIP-11](http://docs.scala-lang.org/sips/pending/string-interpolation.html), which contains all details of the implementation. + +## Usage + +Scala provides three string interpolation methods out of the box: `s`, `f` and `raw`. + +### The `s` String Interpolator + +Prepending `s` to any string literal allows the usage of variables directly in the string. You've already seen an example here: + + val name = "James" + println(s"Hello, $name") // Hello, James + +Here `$name` is nested inside an `s` processed string. The `s` interpolator knows to insert the value of the `name` variable at this location +in the string, resulting in the string `Hello, James`. With the `s` interpolator, any name that is in scope can be used within a string. + +String interpolators can also take arbitrary expressions. For example: + + println(s"1 + 1 = ${1 + 1}") + +will print the string `1 + 1 = 2`. Any arbitrary expression can be embedded in `${}`. + + +### The `f` Interpolator + +Prepending `f` to any string literal allows the creation of simple formatted strings, similar to `printf` in other languages. When using the `f` +interpolator, all variable references should be followed by a `printf`-style format string, like `%d`. Let's look at an example: + + val height = 1.9d + val name = "James" + println(f"$name%s is $height%2.2f meters tall") // James is 1.90 meters tall + +The `f` interpolator is typesafe. If you try to pass a format string that only works for integers but pass a double, the compiler will issue an +error. For example: + + val height: Double = 1.9d + + scala> f"$height%4d" + :9: error: type mismatch; + found : Double + required: Int + f"$height%4d" + ^ + +The `f` interpolator makes use of the string format utilities available from Java. The formats allowed after the `%` character are outlined in the +[Formatter javadoc](http://docs.oracle.com/javase/1.6.0/docs/api/java/util/Formatter.html#detail). If there is no `%` character after a variable +definition a formatter of `%s` (`String`) is assumed. + + +### The `raw` Interpolator + +The raw interpolator is similar to the `s` interpolator except that it performs no escaping of literals within the string. Here's an example processed string: + + scala> s"a\nb" + res0: String = + a + b + +Here the `s` string interpolator replaced the characters `\n` with a return character. The `raw` interpolator will not do that. + + scala> raw"a\nb" + res1: String = a\nb + +The raw interpolator is useful when you want to avoid having expressions like `\n` turn into a return character. + + +In addition to the three default string interpolators, users can define their own. + +## Advanced Usage + +In Scala, all processed string literals are simple code transformations. Anytime the compiler encounters a string literal of the form: + + id"string content" + +it transforms it into a method call (`id`) on an instance of [StringContext](http://www.scala-lang.org/api/current/index.html#scala.StringContext). +This method can also be available on implicit scope. To define our own string interpolation, we simply need to create an implicit class that adds a new method +to `StringContext`. Here's an example: + + // Note: We extends AnyVal to prevent runtime instantiation. See + // value class guide for more info. + implicit class JsonHelper(val sc: StringContext) extends AnyVal { + def json(args: Any*): JSONObject = sys.error("TODO - IMPLEMENT") + } + + def giveMeSomeJson(x: JSONObject): Unit = ... + + giveMeSomeJson(json"{ name: $name, id: $id }") + +In this example, we're attempting to create a JSON literal syntax using string interpolation. The JsonHelper implicit class must be in scope to use this syntax, and the json method would need a complete implementation. However, the result of such a formatted string literal would not be a string, but a `JSONObject`. + +When the compiler encounters the literal `json"{ name: $name, id: $id }"` it rewrites it to the following expression: + + new StringContext("{ name: ", ", id: ", " }").json(name, id) + +The implicit class is then used to rewrite it to the following: + + new JsonHelper(new StringContext("{ name: ", ", id: ", " }")).json(name, id) + +So, the `json` method has access to the raw pieces of strings and each expression as a value. A simple (buggy) implementation of this method could be: + + implicit class JsonHelper(val sc: StringContext) extends AnyVal { + def json(args: Any*): JSONObject = { + val strings = sc.parts.iterator + val expressions = args.iterator + var buf = new StringBuffer(strings.next) + while(strings.hasNext) { + buf append expressions.next + buf append strings.next + } + parseJson(buf) + } + } + +Each of the string portions of the processed string are exposed in the `StringContext`'s `parts` member. Each of the expression values is passed into the `json` method's `args` parameter. The `json` method takes this and generates a big string which it then parses into JSON. A more sophisticated implementation could avoid having to generate this string and simply construct the JSON directly from the raw strings and expression values. diff --git a/_overviews/core/value-classes.md b/_overviews/core/value-classes.md new file mode 100644 index 0000000000..ef44e45fa9 --- /dev/null +++ b/_overviews/core/value-classes.md @@ -0,0 +1,267 @@ +--- +layout: singlepage-overview +title: Value Classes and Universal Traits + +partof: value-classes + +languages: [ja, zh-cn] + +permalink: /overviews/core/:title.html +--- + +**Mark Harrah** + +## Introduction + +Value classes are a new mechanism in Scala to avoid allocating runtime objects. +This is accomplished through the definition of new `AnyVal` subclasses. +They were proposed in [SIP-15](http://docs.scala-lang.org/sips/pending/value-classes.html). +The following shows a very minimal value class definition: + + class Wrapper(val underlying: Int) extends AnyVal + +It has a single, public `val` parameter that is the underlying runtime representation. +The type at compile time is `Wrapper`, but at runtime, the representation is an `Int`. +A value class can define `def`s, but no `val`s, `var`s, or nested `traits`s, `class`es or `object`s: + + class Wrapper(val underlying: Int) extends AnyVal { + def foo: Wrapper = new Wrapper(underlying * 19) + } + +A value class can only extend *universal traits* and cannot be extended itself. +A *universal trait* is a trait that extends `Any`, only has `def`s as members, and does no initialization. +Universal traits allow basic inheritance of methods for value classes, but they *incur the overhead of allocation*. +For example, + + trait Printable extends Any { + def print(): Unit = println(this) + } + class Wrapper(val underlying: Int) extends AnyVal with Printable + + val w = new Wrapper(3) + w.print() // actually requires instantiating a Wrapper instance + +The remaining sections of this documentation show use cases, details on when allocations do and do not occur, and concrete examples of limitations of value classes. + +## Extension methods + +One use case for value classes is to combine them with implicit classes ([SIP-13](http://docs.scala-lang.org/sips/pending/implicit-classes.html)) for allocation-free extension methods. Using an implicit class provides a more convenient syntax for defining extension methods, while value classes remove the runtime overhead. A good example is the `RichInt` class in the standard library. `RichInt` extends the `Int` type with several methods. Because it is a value class, an instance of `RichInt` doesn't need to be created when using `RichInt` methods. + +The following fragment of `RichInt` shows how it extends `Int` to allow the expression `3.toHexString`: + + implicit class RichInt(val self: Int) extends AnyVal { + def toHexString: String = java.lang.Integer.toHexString(self) + } + +At runtime, this expression `3.toHexString` is optimised to the equivalent of a method call on a static object +(`RichInt$.MODULE$.extension$toHexString(3)`), rather than a method call on a newly instantiated object. + +## Correctness + +Another use case for value classes is to get the type safety of a data type without the runtime allocation overhead. +For example, a fragment of a data type that represents a distance might look like: + + class Meter(val value: Double) extends AnyVal { + def +(m: Meter): Meter = new Meter(value + m.value) + } + +Code that adds two distances, such as + + val x = new Meter(3.4) + val y = new Meter(4.3) + val z = x + y + +will not actually allocate any `Meter` instances, but will only use primitive doubles at runtime. + +*Note: You can use case classes and/or extension methods for cleaner syntax in practice.* + +## When Allocation Is Necessary + +Because the JVM does not support value classes, Scala sometimes needs to actually instantiate a value class. +Full details may be found in [SIP-15](http://docs.scala-lang.org/sips/pending/value-classes.html). + +### Allocation Summary + +A value class is actually instantiated when: + +1. a value class is treated as another type. +2. a value class is assigned to an array. +3. doing runtime type tests, such as pattern matching. + +### Allocation Details + +Whenever a value class is treated as another type, including a universal trait, an instance of the actual value class must be instantiated. +As an example, consider the `Meter` value class: + + trait Distance extends Any + case class Meter(val value: Double) extends AnyVal with Distance + +A method that accepts a value of type `Distance` will require an actual `Meter` instance. +In the following example, the `Meter` classes are actually instantiated: + + def add(a: Distance, b: Distance): Distance = ... + add(Meter(3.4), Meter(4.3)) + +If the signature of `add` were instead: + + def add(a: Meter, b: Meter): Meter = ... + +then allocations would not be necessary. +Another instance of this rule is when a value class is used as a type argument. +For example, the actual Meter instance must be created for even a call to identity: + + def identity[T](t: T): T = t + identity(Meter(5.0)) + +Another situation where an allocation is necessary is when assigning to an array, even if it is an array of that value class. +For example, + + val m = Meter(5.0) + val array = Array[Meter](m) + +The array here contains actual `Meter` instances and not just the underlying double primitives. + +Lastly, type tests such as those done in pattern matching or `asInstanceOf` require actual value class instances: + + case class P(val i: Int) extends AnyVal + + val p = new P(3) + p match { // new P instantiated here + case P(3) => println("Matched 3") + case P(x) => println("Not 3") + } + +## Limitations + +Value classes currently have several limitations, in part because the JVM does not natively support the concept of value classes. +Full details on the implementation of value classes and their limitations may be found in [SIP-15](http://docs.scala-lang.org/sips/pending/value-classes.html). + +### Summary of Limitations + +A value class ... + +1. ... must have only a primary constructor with exactly one public, val parameter whose type is not a value class. (From Scala 2.11.0, the parameter may be non-public.) +2. ... may not have specialized type parameters. +3. ... may not have nested or local classes, traits, or objects +4. ... may not define a equals or hashCode method. +5. ... must be a top-level class or a member of a statically accessible object +6. ... can only have defs as members. In particular, it cannot have lazy vals, vars, or vals as members. +7. ... cannot be extended by another class. + +### Examples of Limitations + +This section provides many concrete consequences of these limitations not already described in the necessary allocations section. + +Multiple constructor parameters are not allowed: + + class Complex(val real: Double, val imag: Double) extends AnyVal + +and the Scala compiler will generate the following error message: + + Complex.scala:1: error: value class needs to have exactly one public val parameter + class Complex(val real: Double, val imag: Double) extends AnyVal + ^ + +Because the constructor parameter must be a `val`, it cannot be a by-name parameter: + + NoByName.scala:1: error: `val' parameters may not be call-by-name + class NoByName(val x: => Int) extends AnyVal + ^ + +Scala doesn't allow lazy val constructor parameters, so that isn't allowed either. +Multiple constructors are not allowed: + + class Secondary(val x: Int) extends AnyVal { + def this(y: Double) = this(y.toInt) + } + + Secondary.scala:2: error: value class may not have secondary constructors + def this(y: Double) = this(y.toInt) + ^ + +A value class cannot have lazy vals or vals as members and cannot have nested classes, traits, or objects: + + class NoLazyMember(val evaluate: () => Double) extends AnyVal { + val member: Int = 3 + lazy val x: Double = evaluate() + object NestedObject + class NestedClass + } + + Invalid.scala:2: error: this statement is not allowed in value class: private[this] val member: Int = 3 + val member: Int = 3 + ^ + Invalid.scala:3: error: this statement is not allowed in value class: lazy private[this] var x: Double = NoLazyMember.this.evaluate.apply() + lazy val x: Double = evaluate() + ^ + Invalid.scala:4: error: value class may not have nested module definitions + object NestedObject + ^ + Invalid.scala:5: error: value class may not have nested class definitions + class NestedClass + ^ + +Note that local classes, traits, and objects are not allowed either, as in the following: + + class NoLocalTemplates(val x: Int) extends AnyVal { + def aMethod = { + class Local + ... + } + } + +A current implementation restriction is that value classes cannot be nested: + + class Outer(val inner: Inner) extends AnyVal + class Inner(val value: Int) extends AnyVal + + Nested.scala:1: error: value class may not wrap another user-defined value class + class Outer(val inner: Inner) extends AnyVal + ^ + +Additionally, structural types cannot use value classes in method parameter or return types: + + class Value(val x: Int) extends AnyVal + object Usage { + def anyValue(v: { def value: Value }): Value = + v.value + } + + Struct.scala:3: error: Result type in structural refinement may not refer to a user-defined value class + def anyValue(v: { def value: Value }): Value = + ^ + +A value class may not extend a non-universal trait and a value class may not itself be extended: + + trait NotUniversal + class Value(val x: Int) extends AnyVal with notUniversal + class Extend(x: Int) extends Value(x) + + Extend.scala:2: error: illegal inheritance; superclass AnyVal + is not a subclass of the superclass Object + of the mixin trait NotUniversal + class Value(val x: Int) extends AnyVal with NotUniversal + ^ + Extend.scala:3: error: illegal inheritance from final class Value + class Extend(x: Int) extends Value(x) + ^ + +The second error messages shows that although the `final` modifier is not explicitly specified for a value class, it is assumed. + +Another limitation that is a result of supporting only one parameter to a class is that a value class must be top-level or a member of a statically accessible object. +This is because a nested value class would require a second parameter that references the enclosing class. +So, this is not allowed: + + class Outer { + class Inner(val x: Int) extends AnyVal + } + + Outer.scala:2: error: value class may not be a member of another class + class Inner(val x: Int) extends AnyVal + ^ + +but this is allowed because the enclosing object is top-level: + + object Outer { + class Inner(val x: Int) extends AnyVal + } diff --git a/_overviews/index.md b/_overviews/index.md new file mode 100644 index 0000000000..71b659a568 --- /dev/null +++ b/_overviews/index.md @@ -0,0 +1,44 @@ +--- +layout: overviews +title: Guides and Overviews +#languages: [ja, zh-cn, es] +permalink: /overviews/:title.html +--- + +{% for category in site.data.overviews %} +

      {{ category.category }}

      + {% if category.description %}

      {{ category.description }}

      {% endif %} +
      + {% for overview in category.overviews %} +
      +
      +
      + {% if overview.icon %} +
      +
      +
      + {% endif %} +

      {{ overview.title }}

      +
      +
      + {% if overview.label-text %}
      {{ overview.label-text }}
      {% endif %} + {% if overview.by %}
      By {{ overview.by }}
      {% endif %} + {% if overview.description %}

      {{ overview.description }}

      {% endif %} + {% if overview.subdocs %} + Contents + + {% endif %} +
      +
      + +
      + {% endfor %} +
      +{% endfor %} diff --git a/overviews/macros.html b/_overviews/macros.html similarity index 90% rename from overviews/macros.html rename to _overviews/macros.html index cd4a72b46a..de581e21bc 100644 --- a/overviews/macros.html +++ b/_overviews/macros.html @@ -1,3 +1,7 @@ +--- +permalink: /overviews/macros.html +--- + diff --git a/_overviews/macros/annotations.md b/_overviews/macros/annotations.md new file mode 100644 index 0000000000..02bc52a4f4 --- /dev/null +++ b/_overviews/macros/annotations.md @@ -0,0 +1,113 @@ +--- +layout: multipage-overview +title: Macro Annotations + +discourse: true + +partof: macros +overview-name: Macros + +num: 10 + +languages: [ja] +permalink: /overviews/macros/:title.html +--- +MACRO PARADISE + +**Eugene Burmako** + +Macro annotations are only available with the macro paradise plugin (in Scala 2.10.x, 2.11.x and 2.12.x alike). +Their inclusion in official Scala might happen in Scala 2.13, but there is no certainty about it yet. +Follow the instructions at the ["Macro Paradise"](/overviews/macros/paradise.html) page to download and use our compiler plugin. + +Note that macro paradise is needed both to compile and to expand macro annotations, +which means that your users will have to also add macro paradise to their builds in order to use your macro annotations. +However, after macro annotations expand, the resulting code will no longer have any references to macro paradise +and won't require its presence at compile-time or at runtime. + +## Walkthrough + +Macro annotations bring textual abstraction to the level of definitions. Annotating any top-level or nested definition with something +that Scala recognizes as a macro will let it expand, possibly into multiple members. Unlike in the previous versions of macro paradise, +macro annotations in 2.0 are done right in the sense that they: 1) apply not just to classes and objects, but to arbitrary definitions, +2) allow expansions of classes to modify or even create companion objects. +This opens a number of new possibilities in code generation land. + +In this walkthrough we will write a silly, but very useful macro that does nothing except for logging the annottees. +As a first step, we define an annotation that inherits `StaticAnnotation` and defines a `macroTransform` macro +(the name `macroTransform` and the signature `annottees: Any*` of that macro are important as they tell the macro engine +that the enclosing annotation is a macro annotation). + + import scala.reflect.macros.Context + import scala.language.experimental.macros + import scala.annotation.StaticAnnotation + import scala.annotation.compileTimeOnly + + @compileTimeOnly("enable macro paradise to expand macro annotations") + class identity extends StaticAnnotation { + def macroTransform(annottees: Any*): Any = macro ??? + } + +First of all, note the `@compileTimeOnly` annotation. It is not mandatory, but is recommended to avoid confusion. +Macro annotations look like normal annotations to the vanilla Scala compiler, so if you forget to enable the macro paradise +plugin in your build, your annotations will silently fail to expand. The `@compileTimeOnly` annotation makes sure that +no reference to the underlying definition is present in the program code after typer, so it will prevent the aforementioned +situation from happening. + +Now, the `macroTransform` macro is supposed to take a list of untyped annottees (in the signature their type is represented as `Any` +for the lack of better notion in Scala) and produce one or several results (a single result can be returned as is, multiple +results have to be wrapped in a `Block` for the lack of better notion in the reflection API). + +At this point you might be wondering. A single annottee and a single result is understandable, but what is the many-to-many +mapping supposed to mean? There are several rules guiding the process: + +1. If a class is annotated and it has a companion, then both are passed into the macro. (But not vice versa - if an object + is annotated and it has a companion class, only the object itself is expanded). +1. If a parameter of a class, method or type member is annotated, then it expands its owner. First comes the annottee, + then the owner and then its companion as specified by the previous rule. +1. Annottees can expand into whatever number of trees of any flavor, and the compiler will then transparently + replace the input trees of the macro with its output trees. +1. If a class expands into both a class and a module having the same name, they become companions. + This way it is possible to generate a companion object for a class even if that companion was not declared explicitly. +1. Top-level expansions must retain the number of annottees, their flavors and their names, with the only exception + that a class might expand into a same-named class plus a same-named module, in which case they automatically become + companions as per previous rule. + +Here's a possible implementation of the `identity` annotation macro. The logic is a bit complicated, because it needs to +take into account the cases when `@identity` is applied to a value or type parameter. Excuse us for a low-tech solution, +but we haven't encapsulated this boilerplate in a helper, because compiler plugins cannot easily change the standard library. +(By the way, this boilerplate can be abstracted away by a suitable annotation macro, and we'll probably provide such a macro +at a later point in the future). + + object identityMacro { + def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = { + import c.universe._ + val inputs = annottees.map(_.tree).toList + val (annottee, expandees) = inputs match { + case (param: ValDef) :: (rest @ (_ :: _)) => (param, rest) + case (param: TypeDef) :: (rest @ (_ :: _)) => (param, rest) + case _ => (EmptyTree, inputs) + } + println((annottee, expandees)) + val outputs = expandees + c.Expr[Any](Block(outputs, Literal(Constant(())))) + } + } + +| Example code | Printout | +|-----------------------------------------------------------|-----------------------------------------------------------------| +| `@identity class C` | `(, List(class C))` | +| `@identity class D; object D` | `(, List(class D, object D))` | +| `class E; @identity object E` | `(, List(object E))` | +| `def twice[@identity T]`
      `(@identity x: Int) = x * 2` | `(type T, List(def twice))`
      `(val x: Int, List(def twice))` | + +In the spirit of Scala macros, macro annotations are as untyped as possible to stay flexible and +as typed as possible to remain useful. On the one hand, macro annottees are untyped, so that we can change their signatures (e.g. lists +of class members). But on the other hand, the thing about all flavors of Scala macros is integration with the typechecker, and +macro annotations are not an exceptions. During expansion we can have all the type information that's possible to have +(e.g. we can reflect against the surrounding program or perform type checks / implicit lookups in the enclosing context). + +## Blackbox vs whitebox + +Macro annotations must be [whitebox](/overviews/macros/blackbox-whitebox.html). +If you declare a macro annotation as [blackbox](/overviews/macros/blackbox-whitebox.html), it will not work. diff --git a/_overviews/macros/blackbox-whitebox.md b/_overviews/macros/blackbox-whitebox.md new file mode 100644 index 0000000000..180099943f --- /dev/null +++ b/_overviews/macros/blackbox-whitebox.md @@ -0,0 +1,57 @@ +--- +layout: multipage-overview +title: Blackbox Vs Whitebox + +discourse: true + +partof: macros +overview-name: Macros + +num: 2 + +languages: [ja] +permalink: /overviews/macros/:title.html +--- +EXPERIMENTAL + +**Eugene Burmako** + +Separation of macros into blackbox ones and whitebox ones is a feature of Scala 2.11.x and Scala 2.12.x. The blackbox/whitebox separation is not supported in Scala 2.10.x. It is also not supported in macro paradise for Scala 2.10.x. + +## Why macros work? + +With macros becoming a part of the official Scala 2.10 release, programmers in research and industry have found creative ways of using macros to address all sorts of problems, far extending our original expectations. + +In fact, macros became an important part of our ecosystem so quickly that just a couple months after the release of Scala 2.10, when macros were introduced in experimental capacity, we had a Scala language team meeting and decided to standardize macros and make them a full-fledged feature of Scala by 2.12. + +UPDATE It turned out that it was not that simple to stabilize macros by Scala 2.12. Our research into that has resulted in establishing a new metaprogramming foundation for Scala, called [scala.meta](http://scalameta.org), whose first beta is expected to be released simultaneously with Scala 2.12 and might later be included in future versions of Scala. In the meanwhile, Scala 2.12 is not going to see any changes to reflection and macros - everything is going to stay experimental as it was in Scala 2.10 and Scala 2.11, and no features are going to be removed. However, even though circumstances under which this document has been written have changed, the information still remains relevant, so please continue reading. + +Macro flavors are plentiful, so we decided to carefully examine them to figure out which ones should be put in the standard. This entails answering a few important questions. Why are macros working so well? Why do people use them? + +Our hypothesis is that this happens because the hard to comprehend notion of metaprogramming expressed in def macros piggybacks on the familiar concept of a typed method call. Thanks to that, the code that users write can absorb more meaning without becoming bloated or losing +compehensibility. + +## Blackbox and whitebox macros + +However sometimes def macros transcend the notion of "just a regular method". For example, it is possible for a macro expansion to yield an expression of a type that is more specific than the return type of a macro. In Scala 2.10, such expansion will retain its precise type as highlighted in the ["Static return type of Scala macros"](http://stackoverflow.com/questions/13669974/static-return-type-of-scala-macros) article at Stack Overflow. + +This curious feature provides additional flexibility, enabling [fake type providers](http://meta.plasm.us/posts/2013/07/11/fake-type-providers-part-2/), [extended vanilla materialization](/sips/pending/source-locations.html), [fundep materialization](/overviews/macros/implicits.html#fundep_materialization) and [extractor macros](https://github.com/scala/scala/commit/84a335916556cb0fe939d1c51f27d80d9cf980dc), but it also sacrifices clarity - both for humans and for machines. + +To concretize the crucial distinction between macros that behave just like normal methods and macros that refine their return types, we introduce the notions of blackbox macros and whitebox macros. Macros that faithfully follow their type signatures are called **blackbox macros** as their implementations are irrelevant to understanding their behaviour (could be treated as black boxes). Macros that can't have precise signatures in Scala's type system are called **whitebox macros** (whitebox def macros do have signatures, but these signatures are only approximations). + +We recognize the importance of both blackbox and whitebox macros, however we feel more confidence in blackbox macros, because they are easier to explain, specify and support. Therefore our plans to standardize macros only include blackbox macros. Later on, we might also include whitebox macros into our plans, but it's too early to tell. + +## Codifying the distinction + +In the 2.11 release, we take first step of standardization by expressing the distinction between blackbox and whitebox macros in signatures of def macros, so that `scalac` can treat such macros differently. This is just a preparatory step, so both blackbox and whitebox macros remain experimental in Scala 2.11. + +We express the distinction by replacing `scala.reflect.macros.Context` with `scala.reflect.macros.blackbox.Context` and `scala.reflect.macros.whitebox.Context`. If a macro impl is defined with `blackbox.Context` as its first argument, then macro defs that are using it are considered blackbox, and analogously for `whitebox.Context`. Of course, the vanilla `Context` is still there for compatibility reasons, but it issues a deprecation warning encouraging to choose between blackbox and whitebox macros. + +Blackbox def macros are treated differently from def macros of Scala 2.10. The following restrictions are applied to them by the Scala typechecker: + +1. When an application of a blackbox macro expands into tree `x`, the expansion is wrapped into a type ascription `(x: T)`, where `T` is the declared return type of the blackbox macro with type arguments and path dependencies applied in consistency with the particular macro application being expanded. This invalidates blackbox macros as an implementation vehicle of [type providers](http://meta.plasm.us/posts/2013/07/11/fake-type-providers-part-2/). +1. When an application of a blackbox macro still has undetermined type parameters after Scala's type inference algorithm has finished working, these type parameters are inferred forcedly, in exactly the same manner as type inference happens for normal methods. This makes it impossible for blackbox macros to influence type inference, prohibiting [fundep materialization](/overviews/macros/implicits.html#fundep_materialization). +1. When an application of a blackbox macro is used as an implicit candidate, no expansion is performed until the macro is selected as the result of the implicit search. This makes it impossible to [dynamically calculate availability of implicit macros](/sips/rejected/source-locations.html). +1. When an application of a blackbox macro is used as an extractor in a pattern match, it triggers an unconditional compiler error, preventing [customizations of pattern matching](https://github.com/paulp/scala/commit/84a335916556cb0fe939d1c51f27d80d9cf980dc) implemented with macros. + +Whitebox def macros work exactly like def macros used to work in Scala 2.10. No restrictions of any kind get applied, so everything that could be done with macros in 2.10 should be possible in 2.11 and 2.12. diff --git a/_overviews/macros/bundles.md b/_overviews/macros/bundles.md new file mode 100644 index 0000000000..a204cd338c --- /dev/null +++ b/_overviews/macros/bundles.md @@ -0,0 +1,52 @@ +--- +layout: multipage-overview +title: Macro Bundles + +discourse: true + +partof: macros +overview-name: Macros + +num: 5 + +languages: [ja] +permalink: /overviews/macros/:title.html +--- +EXPERIMENTAL + +**Eugene Burmako** + +Macro bundles are a feature of Scala 2.11.x and Scala 2.12.x. Macro bundles are not supported in Scala 2.10.x. They are also not supported in macro paradise for Scala 2.10.x. + +## Macro bundles + +In Scala 2.10.x, macro implementations are represented with functions. Once the compiler sees an application of a macro definition, +it calls the macro implementation - as simple as that. However practice shows that just functions are often not enough due to the +following reasons: + +1. Being limited to functions makes modularizing complex macros awkward. It's quite typical to see macro logic concentrate in helper +traits outside macro implementations, turning implementations into trivial wrappers, which just instantiate and call helpers. + +2. Moreover, since macro parameters are path-dependent on the macro context, [special incantations](/overviews/macros/overview.html#writing_bigger_macros) are required to wire implementations and helpers together. + +Macro bundles provide a solution to these problems by allowing macro implementations to be declared in classes that take +`c: scala.reflect.macros.blackbox.Context` or `c: scala.reflect.macros.whitebox.Context` as their constructor parameters, relieving macro implementations from having +to declare the context in their signatures, which simplifies modularization. Referencing macro implementations defined in bundles +works in the same way as with impls defined in objects. You specify a bundle name and then select a method from it, +providing type arguments if necessary. + + import scala.reflect.macros.blackbox.Context + + class Impl(val c: Context) { + def mono = c.literalUnit + def poly[T: c.WeakTypeTag] = c.literal(c.weakTypeOf[T].toString) + } + + object Macros { + def mono = macro Impl.mono + def poly[T] = macro Impl.poly[T] + } + +## Blackbox vs whitebox + +Macro bundles can be used to implement both [blackbox](/overviews/macros/blackbox-whitebox.html) and [whitebox](/overviews/macros/blackbox-whitebox.html) macros. Give the macro bundle constructor parameter the type of `scala.reflect.macros.blackbox.Context` to define a blackbox macro and the type of `scala.reflect.macros.whitebox.Context` to define a whitebox macro. diff --git a/_overviews/macros/changelog211.md b/_overviews/macros/changelog211.md new file mode 100644 index 0000000000..28851fcb26 --- /dev/null +++ b/_overviews/macros/changelog211.md @@ -0,0 +1,189 @@ +--- +layout: multipage-overview +title: Changes in Scala 2.11 + +discourse: true + +partof: macros +overview-name: Macros + +num: 13 + +languages: [ja] +permalink: /overviews/macros/:title.html +--- + +EXPERIMENTAL + +**Eugene Burmako** + +This document lists all major changes to reflection and macros during the development cycle of Scala 2.11.0. First, we provide summaries of the most important fixes and newly introduced features, and then, later in the document, we explain how these changes are going to affect compatibility with Scala 2.10.x, and how it's possible to make your reflection-based code work in both 2.10.x and 2.11.0. + +### Quasiquotes + +Quasiquotes is the single most impressive upgrade for reflection and macros in Scala 2.11.0. Implemented by Denys Shabalin, they have significantly simplified the life of Scala metaprogrammers around the globe. Visit [the dedicated documentation page](/overviews/quasiquotes/intro.html) to learn more about quasiquotes. + +### New macro powers + +1) **[Fundep materialization](http://docs.scala-lang.org/overviews/macros/implicits.html#fundep_materialization)**. Since Scala 2.10.2, implicit whitebox macros can be used to materialize instances of type classes, however such materialized instances can't guide type inference. In Scala 2.11.0, materializers can also affect type inference, helping scalac to infer type arguments for enclosing method applications, something that's used with great success in Shapeless. Even more, with the fix of [SI-3346](https://issues.scala-lang.org/browse/SI-3346), this inference guiding capability can affect both normal methods and implicit conversions alike. Please note, however, that fundep materialization doesn't let one change how Scala's type inference works, but merely provides a way to throw more type constraints into the mix, so it's, for example, impossible to make type inference flow from right to left using fundep materializers. + +2) **[Extractor macros](https://github.com/paulp/scala/commit/84a335916556cb0fe939d1c51f27d80d9cf980dc)**. A prominent new feature in Scala 2.11.0 is [name-based extractors](https://github.com/scala/scala/pull/2848) implemented by Paul Phillips. And as usual, when there's a Scala feature, it's very likely that macros can make use of it. Indeed, with the help of structural types, whitebox macros can be used to write extractors than refine the types of extractees on case-by-case basis. This is the technique that we use internally to implement quasiquotes. + +3) **[Named and default arguments in macros](https://github.com/scala/scala/pull/3543)**. This is something that strictly speaking shouldn't belong to this changelog, because this feature was reverted shortly after being merged into Scala 2.11.0-RC1 due to a tiny mistake that led to a regression, but we've got a patch that makes the macro engine understand named/default arguments in macro applications. Even though the code freeze won't let us bring this change in Scala 2.11.0, we expect to merge it in Scala 2.11.1 at an earliest opportunity. + +4) **[Type macros](http://docs.scala-lang.org/overviews/macros/typemacros.html) and [macro annotations](http://docs.scala-lang.org/overviews/macros/annotations.html)**. Neither type macros, not macro annotations are included of Scala 2.11.0. It is highly unlikely that type macros will ever be included in Scala, but we still deliberate on macro annotations. However, macro annotations are available both for Scala 2.10.x and for Scala 2.11.0 via the [macro paradise plugin](http://docs.scala-lang.org/overviews/macros/annotations.html). + +5) **@compileTimeOnly**. Standard library now features a new `scala.annotations.compileTimeOnly` annotation that tells scalac that its annottees should not be referred to after type checking (which includes macro expansion). The main use case for this annotation is marking helper methods that are only supposed be used only together with an enclosing macro call to indicate parts of arguments of that macro call that need special treatment (e.g. `await` in scala/async or `value` in sbt's new macro-based DSL). For example, scala/async's `await` marked as `@compileTimeOnly` only makes sense inside an `async { ... }` block that compiles it away during its transformation, and using it outside of `async` is a compile-time error thanks to the new annotation. + +### Changes to the macro engine + +6) **[Blackbox/whitebox separation](http://docs.scala-lang.org/overviews/macros/blackbox-whitebox.html)**. Macros whose macro implementations use `scala.reflect.macros.blackbox.Context` (new in Scala 2.11.0) are called blackbox, have reduced power in comparison to macros in 2.10.x, better support in IDEs and better perspectives in becoming part of Scala. Macros whose macro implementations use `scala.reflect.macros.whitebox.Context` (new in Scala 2.11.0) or `scala.reflect.macros.Context` (the only context in Scala 2.10.x, deprecated in Scala 2.11.0) are called whitebox and have at least the same power as macros in 2.10.x. + +7) **[Macro bundles](http://docs.scala-lang.org/overviews/macros/bundles.html)**. It is well-known that path-dependent nature of the current reflection API (that's there in both Scala 2.10.x and Scala 2.11.0) makes it difficult to modularize macros. There are [design patterns](http://docs.scala-lang.org/overviews/macros/overview.html#writing_bigger_macros) that help to overcome this difficulty, but that just leads to proliferation of boilerplate. One of the approaches to dealing with this problem is doing away with cakes altogether, and that's what we're pursing in Project Palladium, but that was too big of a change to pull off in Scala 2.11.0, so we've come up with a workaround that would alleviate the problem until the real solution arrives. Macro bundles are classes that have a single public field of type `Context` and any public method inside a bundle can be referred to as a macro implementation. Such macro implementations can then easily call into other methods of the same class or its superclasses without having to carry the context around, because the bundle already carries the context that everyone inside it can see and refer to. This significantly simplifies writing and maintaining complex macros. + +8) **Relaxed requirements for signatures of macro implementations**. With the advent of quasiquotes, reify is quickly growing out of favor as being too clunky and inflexible. To recognize that we now allow both arguments and return types of macro implementations to be of type `c.Tree` rather than `c.Expr[Something]`. There's no longer a need to write huge type signatures and then spend time and lines of code trying to align your macro implementations with those types. Just take trees in and return trees back - the boilerplate is gone. + +9) **Inference of macro def return types is being phased out**. Given the new scheme of things, where macro implementations can return `c.Tree` instead of `c.Expr[Something]`, it's no longer possible to robustly infer return types of macro defs from return types of macro impls (if a macro impl returns `c.Tree`, what's going to be the type of that tree then?). Therefore, we're phasing out this language mechanism. Macro impls that return `c.Expr[T]` can still be used to infer return types of their macro defs, but that will produce a deprecation warning, whereas trying to use macro impls that return `c.Tree` to infer the return type of a macro def will lead to a compilation error. + +10) **[Changes to how macro expansions typecheck](https://github.com/scala/scala/pull/3495)**. In Scala 2.10.x, macro expansions were typechecked twice: first against the return type of the corresponding macro def (so called innerPt) and second against expected type derived from the enclosing program (so called outerPt). This led to certain rare issues, when the return type misguided type inference and macro expansions ended up having imprecise types. In Scala 2.11.0, the typechecking scheme is changed. Blackbox macros are still typechecked against innerPt and then outerPt, but whitebox macros are first typed without any expected type (i.e. against WildcardType), and only then against innerPt and outerPt. + +11) **Duplication of everything that comes in and goes out**. Unfortunately, data structures central to the reflection API (trees, symbols, types) are either mutable themselves or are transitively mutable. This makes the APIs brittle as it's easy to inadvertently change someone's state in ways that are going to be incompatible with its future clients. We don't have a complete solution for that yet, but we've applied a number of safeguards to our macro engine to somewhat contain the potential for mutation. In particular, we now duplicate all the arguments and return values of macro implementations, as well as all the ins and outs of possibly mutating APIs such as `Context.typeCheck`. + +### Changes to the reflection API + +12) **[Introduction of Universe.internal and Context.internal](https://github.com/xeno-by/scala/commit/114c99691674873393223a11a9aa9168c3f41d77)**. Feedback from the users of the Scala 2.10.x reflection API has given us two important insights. First, certain functionality that we exposed was too low-level and were very out of place in the public API. Second, certain low-level functionality was very important in getting important macros operational. In order to somewhat resolve the tension created by these two development vectors, we've created internal subsections of the public APIs that are: a) clearly demarcated from the blessed parts of the reflection API, b) available to those who know what they are doing and want to implement practically important use cases that we want to support. Follow migration and compatibility notes in the bottom of the document to learn more. + +13) **[Thread safety for runtime reflection](http://docs.scala-lang.org/overviews/reflection/thread-safety.html)**. The most pressing problem in reflection for Scala 2.10.x was its thread unsafety. Attempts to use runtime reflection (e.g. type tags) from multiple threads resulted in weird crashes documented above. We believe to have fixed this problem in Scala 2.11.0-RC1 by introducing a number of locks in critical places of our implementation. On the one hand, the strategy we are using at the moment is sub-optimal in the sense that certain frequently used operations (such as `Symbol.typeSignature` or `TypeSymbol.typeParams`) are hidden behind a global lock, but we plan to optimize that in the future. On the other hand, most of the typical APIs (e.g. `Type.members` or `Type.<:<`) either use thread-local state or don't require synchronization at all, so it's definitely worth a try. + +14) **[Type.typeArgs](https://github.com/xeno-by/scala/commit/0f4e95574081bd9a945fb5b32d157a32af840cd3)**. It is now dead simple to obtain type arguments of a given type. What required a pattern match in Scala 2.10.x is now a simple method invocation. The `typeArgs` method is also joined by `typeParams`, `paramLists`, and `resultType`, making it very easy to perform common type inspection tasks. + +15) **[symbolOf[T]](https://issues.scala-lang.org/browse/SI-8194)**. Scala 2.11.0 introduces a shortcut for a very common `typeOf[T].typeSymbol` operation, making it easier to figure out metadata (annotations, flags, visibility, etc) of given classes and objects. + +16) **[knownDirectSubclasses is deemed to be officially broken](https://issues.scala-lang.org/browse/SI-7046)**. A lot of users who tried to traverse sealed hierarchies of classes have noticed that `ClassSymbol.knownDirectSubclasses` only works if invocations of their macros come after the definitions of those hierarchies in Scala's compilation order. For instance, if a sealed hierarchy is defined in the bottom of a source file, and a macro application is written in the top of the file, then knownDirectSubclasses will return an empty list. This is an issue that is deeply rooted in Scala's internal architecture, and we can't provide a fix for it in the near future. + +17) **showCode**. Along with `Tree.toString` that prints Scala-ish source code and `showRaw(tree)` that prints internal structures of trees, we now have `showCode` that prints compileable Scala source code corresponding to the provided tree, courtesy of Vladimir Nikolaev, who's done an amazing work of bringing this to life. We plan to eventually replace `Tree.toString` with `showCode`, but in Scala 2.11.0 these are two different methods. + +18) **[It is now possible to typecheck in type and pattern modes](https://issues.scala-lang.org/browse/SI-6814)**. A very convenient `Context.typeCheck` and `ToolBox.typeCheck` functionality of Scala 2.10.x had a significant inconvenience - it only worked for expressions, and typechecking something as a type or as a pattern required building dummy expressions. Now `typeCheck` has the mode parameter that take case of that difficulty. + +19) **[Reflective invocations now support value classes](https://github.com/scala/scala/pull/3409)**. Runtime reflection now correctly deals with value classes in parameters of methods and constructors and also correctly unboxes and boxes inputs and outputs to reflective invocations such as `FieldMirror.get`, `FieldMirror.set` and `MethodMirror.apply`. + +20) **[Reflective invocations have become faster](https://github.com/scala/scala/pull/1821)**. With the help of the newly introduced `FieldMirror.bind` and `MethodMirror.bind` APIs, it is now possible to quickly create new mirrors from pre-existing ones, avoiding the necessity to undergo costly mirror initialization. In our tests, invocation-heavy scenarios exhibit up to 20x performance boosts thanks to these new APIs. + +21) **Context.introduceTopLevel**. The `Context.introduceTopLevel` API, which used to be available in early milestone builds of Scala 2.11.0 as a stepping stone towards type macros, was removed from the final release, because type macros were rejected for including in Scala and discontinued in macro paradise. + +### How to make your 2.10.x macros work in 2.11.0 + +22) **Blackbox/whitebox**. All macros in Scala 2.10.x are whitebox and will behave accordingly, being able to refine the advertised return type of their macro defs in their expansions. See the subsequent section of the document for information on how to make macros in Scala 2.10.x behave exactly like blackbox macros in Scala 2.11.0. + +23) **Macro bundles**. Scala 2.11.0 now recognizes certain new shapes of references to macro implementations in right-hand sides of macro defs, and in some very rare situations this might change how existing code is compiled. First of all, no runtime behavior is going to be affected in this case - if a Scala 2.10.x macro def compiles in Scala 2.11.0, then it's going to bind to the same macro impl as before. Secondly, in some cases macro impl references might become ambiguous and fail compilation, but that should be fixable in a backward compatible fashion by simple renaming suggested by the error message. + +24) **Inference of macro def return types**. In Scala 2.11.0, macro defs, whose return types are inferred from associated macro impls, will work consistently with Scala 2.10.x. A deprecation warning will be emitted for such macro defs, but no compilation errors or behavior discrepancies are going to happen. + +25) **Changes to how macro expansions typecheck**. Scala 2.11.0 changes the sequence of expected types used to typecheck whitebox macro expansions (and since all macros in Scala 2.10.x are whitebox, they all can potentially be affected). In rare situations, when a Scala 2.10.x macro expansion relied on a specific shape of an expected type to get its type arguments inferred, it might stop working. In such cases, specifying such type arguments explicitly will fix the problem in a way compatible with both Scala 2.10.x and Scala 2.11.0: [example](https://github.com/milessabin/shapeless/commit/7b192b8d89b3654e58d7bac89427ebbcc5d5adf1#diff-c6aebd4b374deb66f0d5cdeff8484338L69). + +26) **Duplication of everything that comes in and goes out**. In Scala 2.11.0, we consistently duplicate trees that cross boundaries between userland (macro implementation code) and kernel (compiler internals), limiting the scope of mutations of those trees. In extremely rare cases, Scala 2.10.x macros might be relying on such mutations to operate correctly. Such macros will be broken and will have to be rewritten. Don't worry much about this though, because we haven't yet encountered such macros in the wild, so it's most likely that your macros are going to be fine. + +27) **Introduction of Universe.internal and Context.internal**. The following 51 APIs available in Scala 2.10.x have been moved into the `internal` submodule of the reflection cake. There are two ways of fixing these source incompatibilities. The easy one is writing `import compat._` after `import scala.reflect.runtime.universe._` or `import c.universe._`. The hard one is the easy one + applying all migration suggestions provided by deprecating warnings on methods imported from compat. + +| | | | +| ------------------------- | ------------------------------ | ------------------------- | +| typeTagToManifest | Tree.pos_= | Symbol.isSkolem | +| manifestToTypeTag | Tree.setPos | Symbol.deSkolemize | +| newScopeWith | Tree.tpe_= | Symbol.attachments | +| BuildApi.setTypeSignature | Tree.setType | Symbol.updateAttachment | +| BuildApi.flagsFromBits | Tree.defineType | Symbol.removeAttachment | +| BuildApi.emptyValDef | Tree.symbol_= | Symbol.setTypeSignature | +| BuildApi.This | Tree.setSymbol | Symbol.setAnnotations | +| BuildApi.Select | TypeTree.setOriginal | Symbol.setName | +| BuildApi.Ident | Symbol.isFreeTerm | Symbol.setPrivateWithin | +| BuildApi.TypeTree | Symbol.asFreeTerm | captureVariable | +| Tree.freeTerms | Symbol.isFreeType | referenceCapturedVariable | +| Tree.freeTypes | Symbol.asFreeType | capturedVariableType | +| Tree.substituteSymbols | Symbol.newTermSymbol | singleType | +| Tree.substituteTypes | Symbol.newModuleAndClassSymbol | refinedType | +| Tree.substituteThis | Symbol.newMethodSymbol | typeRef | +| Tree.attachments | Symbol.newTypeSymbol | intersectionType | +| Tree.updateAttachment | Symbol.newClassSymbol | polyType | +| Tree.removeAttachment | Symbol.isErroneous | existentialAbstraction | + +28) **Official brokenness of knownDirectSubclasses**. There's nothing that can be done here from your side apart from being aware of limitations of that API. Macros that use `knownDirectSubclasses` will continue to work in Scala 2.11.0 exactly like they did in Scala 2.10.x, without any deprecation warnings. + +29) **Deprecation of Context.enclosingTree-style APIs**. Existing enclosing tree macro APIs face both technical and philosophical problems, so we've made a hard decision to phase them out, deprecating them in Scala 2.11.0 and removing them in Scala 2.12.0. There's no direct replacement for these APIs, just the newly introduced c.internal.enclosingOwner that only covers a subset of their functionality. Follow the discussion at [https://github.com/scala/scala/pull/3354](https://github.com/scala/scala/pull/3354) for more information. + +30) **Other deprecations**. Some of you have -Xfatal-warnings turned on in your builds, so any deprecation might fail compilation. This guide has covered all controversial deprecations, and the rest can be fixed by straightforwardly following deprecation messages. + +31) **Removal of resetAllAttrs**. resetAllAttrs is a very dangerous API and shouldn't have been exposed in the first place. That's why we have removed it without going through a deprecation cycle. There is however a publicly available replacement called `resetLocalAttrs` that should be sufficient in almost all cases, and we recommend using it instead. In an exceptional case when `resetLocalAttrs` doesn't cut it, go for [https://github.com/scalamacros/resetallattrs](https://github.com/scalamacros/resetallattrs). + +32) **Removal of isLocal**. `Symbol.isLocal` wasn't doing what is was advertising, and there was no way to fix it. Therefore we have removed it without any deprecation warnings and are recommending using `Symbol.isPrivateThis` and/or `Symbol.isProtectedThis` instead. + +33) **Removal of isOverride**. Same story as with `Symbol.isLocal`. This method was broken beyond repair, which is why it was removed from the public API. `Symbol.allOverriddenSymbols` (or its newly introduced alias `Symbol.overrides`) should be used instead. + +### How to make your 2.11.0 macros work in 2.10.x + +34) **Quasiquotes**. We don't plan to release quasiquotes as part of the Scala 2.10.x distribution, but they can still be used in Scala 2.10.x by the virtue of the macro paradise plugin. Read [paradise documentation](http://docs.scala-lang.org/overviews/macros/paradise.html) to learn more about what's required to use the compiler plugin, what are the binary compatibility consequences and what are the support guarantees. + +35) **Most of the new functionality doesn't have equivalents in 2.10.x**. We don't plan to backport any of the new functionality, e.g. fundep materialization or macro bundles, to Scala 2.10.x (except for maybe thread safety for runtime reflection). Consult [the roadmap of macro paradise for Scala 2.10.x](http://docs.scala-lang.org/overviews/macros/roadmap.html) to see what features are supported in paradise. + +36) **Blackbox/whitebox**. If you're determined to have your macros blackbox, it's going to require additional effort to have those macros working consistently in both 2.10.x and 2.11.0, because in 2.10.x all macros are whitebox. First of all, make sure that you're not actually using any of [whitebox powers](http://docs.scala-lang.org/overviews/macros/blackbox-whitebox.html#codifying_the_distinction), otherwise you'll have to rewrite your macros first. Secondly, before returning from your macros impls, explicitly upcast the expansions to the type required by their macro defs. (Of course, none of this applies to whitebox macros. If don't mind your macros being whitebox, then you don't have to do anything to ensure cross-compatibility). + + object Macros { + def impl(c: Context) = { + import c.universe._ + q"new { val x = 2 }" + } + + def foo: Any = macro impl + } + + object Test extends App { + // works in Scala 2.10.x and Scala 2.11.0 if foo is whitebox + // doesn't work in Scala 2.11.0 if foo is blackbox + println(Macros.foo.x) + } + + object Macros { + def impl(c: Context) = { + import c.universe._ + q"(new { val x = 2 }): Any" // note the explicit type ascription + } + + def foo: Any = macro impl + } + + object Test extends App { + // consistently doesn't work in Scala 2.10.x and Scala 2.11.0 + // regardless of whether foo is whitebox or blackbox + println(Macros.foo.x) + } + +37) **@compileTimeOnly**. The `compileTimeOnly` annotation is secretly available in `scala-reflect.jar` as `scala.reflect.internal.compileTimeOnly` since Scala 2.10.1 (To the contrast, in Scala 2.11.0 `compileTimeOnly` lives in `scala-library.jar` under the name of `scala.annotations.compileTimeOnly`). If you don't mind the users of your API having to transitively depend on `scala-reflect.jar`, go ahead and use `compileTimeOnly` even in Scala 2.10.x - it will behave in the same fashion as in Scala 2.11.0. + +38) **Changes to how macro expansions typecheck**. Scala 2.11.0 changes the sequence of expected types used to typecheck whitebox macro expansions (and since all macros in Scala 2.10.x are whitebox, they all can potentially be affected), which can theoretically cause type inference problems. This is unlikely to become a problem even when migrating from 2.10.x to 2.11.0, but going in reverse direction has almost non-existent chances of causing issues. If you encounter difficulties, then like with any type inference glitch, try providing an explicit type annotation by upcasting macro expansions to the type that you want. + +39) **Introduction of Universe.internal and Context.internal**. Even though it's hard to imagine how this could work, it is possible to have a macro using internal APIs compileable with both Scala 2.10.x and 2.11.0. Big thanks to Jason Zaugg for showing us the way: + + // scala.reflect.macros.Context is available both in 2.10 and 2.11 + // in Scala 2.11.0 it is deprecated + // and aliased to scala.reflect.macros.whitebox.Context + import scala.reflect.macros.Context + import scala.language.experimental.macros + + // provides a source compatibility stub + // in Scala 2.10.x, it will make `import compat._` compile just fine, + // even though `c.universe` doesn't have `compat` + // in Scala 2.11.0, it will be ignored, becase `import c.universe._` + // brings its own `compat` in scope and that one takes precedence + private object HasCompat { val compat = ??? }; import HasCompat._ + + object Macros { + def impl(c: Context): c.Expr[Int] = { + import c.universe._ + // enables Tree.setType that's been removed in Scala 2.11.0 + import compat._ + c.Expr[Int](Literal(Constant(42)) setType definitions.IntTpe) + } + + def ultimateAnswer: Int = macro impl + } + +40) **Use macro-compat**. [Macro-compat](https://github.com/milessabin/macro-compat) is a small library which allows you to compile macros with Scala 2.10.x which are written to the Scala 2.11/2 macro API. It brings to Scala 2.10: type aliases for the blackbox and whitebox Context types, support for macro bundles, forwarders for the 2.11 API and support for using Tree in the macro def type signatures. diff --git a/_overviews/macros/extractors.md b/_overviews/macros/extractors.md new file mode 100644 index 0000000000..43fb30c911 --- /dev/null +++ b/_overviews/macros/extractors.md @@ -0,0 +1,98 @@ +--- +layout: multipage-overview +title: Extractor Macros + +discourse: true + +partof: macros +overview-name: Macros + +num: 7 + +languages: [ja] +permalink: /overviews/macros/:title.html +--- +EXPERIMENTAL + +**Eugene Burmako** + +Extractor macros are a feature of Scala 2.11.x and Scala 2.12.x, enabled by name-based extractors introduced by Paul Phillips in Scala 2.11.0-M5. Extractor macros are not supported in Scala 2.10.x. They are also not supported in macro paradise for Scala 2.10.x. + +## The pattern + +In a nutshell, given an unapply method (for simplicity, in this +example the scrutinee is of a concrete type, but it's also possible +to have the extractor be polymorphic, as demonstrated in the tests): + + def unapply(x: SomeType) = ??? + +One can write a macro that generates extraction signatures for unapply +on per-call basis, using the target of the calls (`c.prefix`) and the type +of the scrutinee (that comes with `x`), and then communicate these signatures +to the typechecker. + +For example, here's how one can define a macro that simply passes +the scrutinee back to the pattern match (for information on how to +express signatures that involve multiple extractees, visit +[scala/scala#2848](https://github.com/scala/scala/pull/2848)). + + def unapply(x: SomeType) = macro impl + def impl(c: Context)(x: c.Tree) = { + q""" + new { + class Match(x: SomeType) { + def isEmpty = false + def get = x + } + def unapply(x: SomeType) = new Match(x) + }.unapply($x) + """ + } + +In addition to the matcher, which implements domain-specific +matching logic, there's quite a bit of boilerplate here, but +every part of it looks necessary to arrange a non-frustrating dialogue +with the typer. Maybe something better can be done in this department, +but I can't see how, without introducing modifications to the typechecker. + +Even though the pattern uses structural types, somehow no reflective calls +are being generated (as verified by `-Xlog-reflective-calls` and then +by manual examination of the produced code). That's a mystery to me, but +that's also good news, since that means that extractor macros aren't +going to induce performance penalties. + +Almost. Unfortunately, I couldn't turn matchers into value classes +because one can't declare value classes local. Nevertheless, +I'm leaving a canary in place ([neg/t5903e](https://github.com/scala/scala/blob/00624a39ed84c3fd245dd9df7454d4cec4399e13/test/files/neg/t5903e/Macros_1.scala#L1)) that will let us know +once this restriction is lifted. + +## Use cases + +In particular, the pattern can be used to implement shapeshifting +pattern matchers for string interpolators without resorting to dirty +tricks. For example, quasiquote unapplications can be unhardcoded now: + + def doTypedApply(tree: Tree, fun0: Tree, args: List[Tree], ...) = { + ... + fun.tpe match { + case ExtractorType(unapply) if mode.inPatternMode => + // this hardcode in Typers.scala is no longer necessary + if (unapply == QuasiquoteClass_api_unapply) macroExpandUnapply(...) + else doTypedUnapply(tree, fun0, fun, args, mode, pt) + } + } + +Rough implementation strategy here would involve writing an extractor +macro that destructures `c.prefix`, analyzes parts of `StringContext` and +then generates an appropriate matcher as outlined above. + +Follow our test cases at [run/t5903a](https://github.com/scala/scala/tree/00624a39ed84c3fd245dd9df7454d4cec4399e13/test/files/run/t5903a), +[run/t5903b](https://github.com/scala/scala/tree/00624a39ed84c3fd245dd9df7454d4cec4399e13/test/files/run/t5903b), +[run/t5903c](https://github.com/scala/scala/tree/00624a39ed84c3fd245dd9df7454d4cec4399e13/test/files/run/t5903c), +[run/t5903d](https://github.com/scala/scala/tree/00624a39ed84c3fd245dd9df7454d4cec4399e13/test/files/run/t5903d) to see implementations +of this and other use cases for extractor macros. + +## Blackbox vs whitebox + +Extractor macros must be [whitebox](/overviews/macros/blackbox-whitebox.html). +If you declare an extractor macro as [blackbox](/overviews/macros/blackbox-whitebox.html), it will not work. diff --git a/_overviews/macros/implicits.md b/_overviews/macros/implicits.md new file mode 100644 index 0000000000..bc277680b0 --- /dev/null +++ b/_overviews/macros/implicits.md @@ -0,0 +1,164 @@ +--- +layout: multipage-overview +title: Implicit Macros + +discourse: true + +partof: macros +overview-name: Macros + +num: 6 + +languages: [ja] +permalink: /overviews/macros/:title.html +--- +EXPERIMENTAL + +**Eugene Burmako** + +Implicit macros are shipped as an experimental feature of Scala since version 2.10.0, including the upcoming 2.11.0, +but require a critical bugfix in 2.10.2 to become fully operational. Implicit macros do not need macro paradise to work, +neither in 2.10.x, nor in 2.11. + +An extension to implicit macros, +called fundep materialization, is unavailable in 2.10.0 through 2.10.4, but has been implemented in +[macro paradise](/overviews/macros/paradise.html), Scala 2.10.5 and Scala 2.11.x. +Note that in 2.10.0 through 2.10.4, expansion of fundep materializer macros requires macro paradise, +which means that your users will have to add macro paradise to their builds in order to use your fundep materializers. +However, after fundep materializers expand, the resulting code will no longer have any references to macro paradise +and won't require its presence at compile-time or at runtime. Also note that in 2.10.5, expansion of +fundep materializer macros can happen without macro paradise, but then your users will have to enable +the -Yfundep-materialization compiler flag. + +## Implicit macros + +### Type classes + +The example below defines the `Showable` type class, which abstracts over a prettyprinting strategy. +The accompanying `show` method takes two parameters: an explicit one, the target, and an implicit one, +which carries the instance of `Showable`. + + trait Showable[T] { def show(x: T): String } + def show[T](x: T)(implicit s: Showable[T]) = s.show(x) + +After being declared like that, `show` can be called with only the target provided, and `scalac` +will try to infer the corresponding type class instance from the scope of the call site based +on the type of the target. If there is a matching implicit value in scope, it will be inferred +and compilation will succeed, otherwise a compilation error will occur. + + implicit object IntShowable extends Showable[Int] { + def show(x: Int) = x.toString + } + show(42) // "42" + show("42") // compilation error + +### Proliferation of boilerplate + +One of the well-known problems with type classes, in general and in particular in Scala, +is that instance definitions for similar types are frequently very similar, which leads to +proliferation of boilerplate code. + +For example, for a lot of objects prettyprinting means printing the name of their class +and the names and values of the fields. Even though this and similar recipes are very concise, +in practice it is often impossible to implement them concisely, so the programmer is forced +to repeat himself over and over again. + + class C(x: Int) + implicit def cShowable = new Showable[C] { + def show(c: C) = "C(" + c.x + ")" + } + + class D(x: Int) + implicit def dShowable = new Showable[D] { + def show(d: D) = "D(" + d.x + ")" + } + +This very use case can be implemented with runtime reflection, +but oftentimes reflection is either too imprecise because of erasure or +too slow because of the overhead it imposes. + +There also exist generic programming approaches based on type-level programming, for example, +[the `TypeClass` type class technique](http://typelevel.org/blog/2013/06/24/deriving-instances-1.html) introduced by Lars Hupel, +but they also suffer a performance hit in comparison with manually written type class instances. + +### Implicit materializers + +With implicit macros it becomes possible to eliminate the boilerplate by completely removing +the need to manually define type class instances, without sacrificing performance. + + trait Showable[T] { def show(x: T): String } + object Showable { + implicit def materializeShowable[T]: Showable[T] = macro ... + } + +Instead of writing multiple instance definitions, the programmer defines a single `materializeShowable` macro +in the companion object of the `Showable` type class. Members of a companion object belong to implicit scope +of an associated type class, which means that in cases when the programmer does not provide an explicit instance of `Showable`, +the materializer will be called. Upon being invoked, the materializer can acquire a representation of `T` and +generate the appropriate instance of the `Showable` type class. + +A nice thing about implicit macros is that they seamlessly meld into the pre-existing infrastructure of implicit search. +Such standard features of Scala implicits as multi-parametricity and overlapping instances are available to +implicit macros without any special effort from the programmer. For example, it is possible to define a non-macro +prettyprinter for lists of prettyprintable elements and have it transparently integrated with the macro-based materializer. + + implicit def listShowable[T](implicit s: Showable[T]) = + new Showable[List[T]] { + def show(x: List[T]) = { x.map(s.show).mkString("List(", ", ", ")") + } + } + show(List(42)) // prints: List(42) + +In this case, the required instance `Showable[Int]` would be generated by the materializing macro defined above. +Thus, by making macros implicit, they can be used to automate the materialization of type class instances, +while at the same time seamlessly integrating with non-macro implicits. + +## Fundep materialization + +### Problem statement + +The use case, which gave birth to fundep materializers, was provided by Miles Sabin and his [shapeless](https://github.com/milessabin/shapeless) library. In the old version of shapeless, before 2.0.0, Miles has defined the `Iso` trait, +which represents isomorphisms between types. `Iso` can be used to map case classes to tuples and vice versa +(actually, shapeless used Iso's to convert between case classes and HLists, but for simplicity let's use tuples). + + trait Iso[T, U] { + def to(t: T) : U + def from(u: U) : T + } + + case class Foo(i: Int, s: String, b: Boolean) + def conv[C, L](c: C)(implicit iso: Iso[C, L]): L = iso.to(c) + + val tp = conv(Foo(23, "foo", true)) + tp: (Int, String, Boolean) + tp == (23, "foo", true) + +If we try to write an implicit materializer for `Iso`, we will run into a wall. +When typechecking applications of methods like `conv`, scalac has to infer the type argument `L`, +which it has no clue about (and that's no wonder, since this is domain-specific knowledge). As a result, when we define an implicit +macro, which synthesizes `Iso[C, L]`, scalac will helpfully infer `L` as `Nothing` before expanding the macro and then everything will crumble. + +### Proposed solution + +As demonstrated by [https://github.com/scala/scala/pull/2499](https://github.com/scala/scala/pull/2499), the solution to the outlined +problem is extremely simple and elegant. + +In 2.10 we don't allow macro applications to expand until all their type arguments are inferred. However we don't have to do that. +The typechecker can infer as much as it possibly can (e.g. in the running example `C` will be inferred to `Foo` and +`L` will remain uninferred) and then stop. After that we expand the macro and then proceed with type inference using the type of the +expansion to help the typechecker with previously undetermined type arguments. This is how it's implemented in Scala 2.11.0. + +An illustration of this technique in action can be found in our [files/run/t5923c](https://github.com/scala/scala/tree/7b890f71ecd0d28c1a1b81b7abfe8e0c11bfeb71/test/files/run/t5923c) tests. +Note how simple everything is. The `materializeIso` implicit macro just takes its first type argument and uses it to produce an expansion. +We don't need to make sense of the second type argument (which isn't inferred yet), we don't need to interact with type inference - +everything happens automatically. + +Please note that there is [a funny caveat](https://github.com/scala/scala/blob/7b890f71ecd0d28c1a1b81b7abfe8e0c11bfeb71/test/files/run/t5923a/Macros_1.scala) with Nothings that we plan to address later. + +## Blackbox vs whitebox + +Vanilla materializers (covered in the first part of this document) can be both [blackbox](/overviews/macros/blackbox-whitebox.html) and [whitebox](/overviews/macros/blackbox-whitebox.html). + +There is a noticeable distinction between blackbox and whitebox materializers. An error in an expansion of a blackbox implicit macro (e.g. an explicit c.abort call or an expansion typecheck error) will produce a compilation error. An error in an expansion of a whitebox implicit macro will just remove the macro from the list of implicit candidates in the current implicit search, without ever reporting an actual error to the user. This creates a trade-off: blackbox implicit macros feature better error reporting, while whitebox implicit macros are more flexible, being able to dynamically turn themselves off when necessary. + +Fundep materializers must be whitebox. If you declare a fundep materializer as blackbox, it will not work. diff --git a/_overviews/macros/inference.md b/_overviews/macros/inference.md new file mode 100644 index 0000000000..32aad15892 --- /dev/null +++ b/_overviews/macros/inference.md @@ -0,0 +1,16 @@ +--- +layout: multipage-overview +title: Inference-Driving Macros + +discourse: true + +overview-name: Macros + +languages: [ja] +permalink: /overviews/macros/:title.html +--- +EXPERIMENTAL + +**Eugene Burmako** + +The page has been moved to [/overviews/macros/implicits.html](/overviews/macros/implicits.html). diff --git a/_overviews/macros/overview.md b/_overviews/macros/overview.md new file mode 100644 index 0000000000..09c195ca01 --- /dev/null +++ b/_overviews/macros/overview.md @@ -0,0 +1,370 @@ +--- +layout: multipage-overview +title: Def Macros + +discourse: true + +partof: macros +overview-name: Macros + +num: 3 + +languages: [ja] +permalink: /overviews/macros/:title.html +--- +EXPERIMENTAL + +**Eugene Burmako** + +Def macros are shipped as an experimental feature of Scala since version 2.10.0. +A subset of def macros, pending a thorough specification, is tentatively scheduled to become stable in one of the future versions of Scala. + +UPDATE This guide has been written for Scala 2.10.0, and now we're well into the Scala 2.11.x release cycle, +so naturally the contents of the document are outdated. Nevertheless, this guide is not obsolete - +everything written here will still work in both Scala 2.10.x and Scala 2.11.x, so it will be helpful to read it through. +After reading the guide, take a look at the docs on [quasiquotes](/overviews/quasiquotes/intro.html) +and [macro bundles](/overviews/macros/bundles.html) to familiarize yourself with latest developments +that dramatically simplify writing macros. Then it might be a good idea to follow +[our macro workshop](https://github.com/scalamacros/macrology201) for more in-depth examples. + +## Intuition + +Here is a prototypical macro definition: + + def m(x: T): R = macro implRef + +At first glance macro definitions are equivalent to normal function definitions, except for their body, which starts with the conditional keyword `macro` and is followed by a possibly qualified identifier that refers to a static macro implementation method. + +If, during type-checking, the compiler encounters an application of the macro `m(args)`, it will expand that application by invoking the corresponding macro implementation method, with the abstract-syntax trees of the argument expressions args as arguments. The result of the macro implementation is another abstract syntax tree, which will be inlined at the call site and will be type-checked in turn. + +The following code snippet declares a macro definition assert that references a macro implementation Asserts.assertImpl (definition of assertImpl is provided below): + + def assert(cond: Boolean, msg: Any) = macro Asserts.assertImpl + +A call `assert(x < 10, "limit exceeded")` would then lead at compile time to an invocation + + assertImpl(c)(<[ x < 10 ]>, <[ “limit exceeded” ]>) + +where `c` is a context argument that contains information collected by the compiler at the call site, and the other two arguments are abstract syntax trees representing the two expressions `x < 10` and `limit exceeded`. + +In this document, `<[ expr ]>` denotes the abstract syntax tree that represents the expression expr. This notation has no counterpart in our proposed extension of the Scala language. In reality, the syntax trees would be constructed from the types in trait `scala.reflect.api.Trees` and the two expressions above would look like this: + + Literal(Constant("limit exceeded")) + + Apply( + Select(Ident(TermName("x")), TermName("$less"), + List(Literal(Constant(10))))) + +Here is a possible implementation of the `assert` macro: + + import scala.reflect.macros.Context + import scala.language.experimental.macros + + object Asserts { + def raise(msg: Any) = throw new AssertionError(msg) + def assertImpl(c: Context) + (cond: c.Expr[Boolean], msg: c.Expr[Any]) : c.Expr[Unit] = + if (assertionsEnabled) + <[ if (!cond) raise(msg) ]> + else + <[ () ]> + } + +As the example shows, a macro implementation takes several parameter lists. First comes a single parameter, of type `scala.reflect.macros.Context`. This is followed by a list of parameters that have the same names as the macro definition parameters. But where the original macro parameter has type `T`, a macro implementation parameter has type `c.Expr[T]`. `Expr[T]` is a type defined in `Context` that wraps an abstract syntax tree of type `T`. The result type of the `assertImpl` macro implementation is again a wrapped tree, of type `c.Expr[Unit]`. + +Also note that macros are considered an experimental and advanced feature, +so in order to write macros you need to enable them. +Do that either with `import scala.language.experimental.macros` on per-file basis +or with `-language:experimental.macros` (providing a compiler switch) on per-compilation basis. +Your users, however, don't need to enable anything - macros look like normal methods +and can be used as normal methods, without any compiler switches or additional configurations. + +### Generic macros + +Macro definitions and macro implementations may both be generic. If a macro implementation has type parameters, actual type arguments must be given explicitly in the macro definition’s body. Type parameters in an implementation may come with `WeakTypeTag` context bounds. In that case the corresponding type tags describing the actual type arguments instantiated at the application site will be passed along when the macro is expanded. + +The following code snippet declares a macro definition `Queryable.map` that references a macro implementation `QImpl.map`: + + class Queryable[T] { + def map[U](p: T => U): Queryable[U] = macro QImpl.map[T, U] + } + + object QImpl { + def map[T: c.WeakTypeTag, U: c.WeakTypeTag] + (c: Context) + (p: c.Expr[T => U]): c.Expr[Queryable[U]] = ... + } + +Now consider a value `q` of type `Queryable[String]` and a macro call + + q.map[Int](s => s.length) + +The call is expanded to the following reflective macro invocation + + QImpl.map(c)(<[ s => s.length ]>) + (implicitly[WeakTypeTag[String]], implicitly[WeakTypeTag[Int]]) + +## A complete example + +This section provides an end-to-end implementation of a `printf` macro, which validates and applies the format string at compile-time. +For the sake of simplicity the discussion uses console Scala compiler, but as explained below macros are also supported by Maven and sbt. + +Writing a macro starts with a macro definition, which represents the facade of the macro. +Macro definition is a normal function with anything one might fancy in its signature. +Its body, though, is nothing more that a reference to an implementation. +As mentioned above, to define a macro one needs to import `scala.language.experimental.macros` +or to enable a special compiler switch, `-language:experimental.macros`. + + import scala.language.experimental.macros + def printf(format: String, params: Any*): Unit = macro printf_impl + +Macro implementation must correspond to macro definitions that use it (typically there's only one, but there might also be many). In a nutshell, every parameter of type `T` in the signature of a macro definition must correspond to a parameter of type `c.Expr[T]` in the signature of a macro implementation. The full list of rules is quite involved, but it's never a problem, because if the compiler is unhappy, it will print the signature it expects in the error message. + + import scala.reflect.macros.Context + def printf_impl(c: Context)(format: c.Expr[String], params: c.Expr[Any]*): c.Expr[Unit] = ... + +Compiler API is exposed in `scala.reflect.macros.Context`. Its most important part, reflection API, is accessible via `c.universe`. +It's customary to import `c.universe._`, because it includes a lot of routinely used functions and types + + import c.universe._ + +First of all, the macro needs to parse the provided format string. +Macros run during the compile-time, so they operate on trees, not on values. +This means that the format parameter of the `printf` macro will be a compile-time literal, not an object of type `java.lang.String`. +This also means that the code below won't work for `printf(get_format(), ...)`, because in that case `format` won't be a string literal, but rather an AST that represents a function application. + + val Literal(Constant(s_format: String)) = format.tree + +Typical macros (and this macro is not an exception) need to create ASTs (abstract syntax trees) which represent Scala code. +To learn more about generation of Scala code, take a look at [the overview of reflection](http://docs.scala-lang.org/overviews/reflection/overview.html). Along with creating ASTs the code provided below also manipulates types. +Note how we get a hold of Scala types that correspond to `Int` and `String`. +Reflection overview linked above covers type manipulations in detail. +The final step of code generation combines all the generated code into a `Block`. +Note the call to `reify`, which provides a shortcut for creating ASTs. + + val evals = ListBuffer[ValDef]() + def precompute(value: Tree, tpe: Type): Ident = { + val freshName = TermName(c.fresh("eval$")) + evals += ValDef(Modifiers(), freshName, TypeTree(tpe), value) + Ident(freshName) + } + + val paramsStack = Stack[Tree]((params map (_.tree)): _*) + val refs = s_format.split("(?<=%[\\w%])|(?=%[\\w%])") map { + case "%d" => precompute(paramsStack.pop, typeOf[Int]) + case "%s" => precompute(paramsStack.pop, typeOf[String]) + case "%%" => Literal(Constant("%")) + case part => Literal(Constant(part)) + } + + val stats = evals ++ refs.map(ref => reify(print(c.Expr[Any](ref).splice)).tree) + c.Expr[Unit](Block(stats.toList, Literal(Constant(())))) + +The snippet below represents a complete definition of the `printf` macro. +To follow the example, create an empty directory and copy the code to a new file named `Macros.scala`. + + import scala.reflect.macros.Context + import scala.collection.mutable.{ListBuffer, Stack} + + object Macros { + def printf(format: String, params: Any*): Unit = macro printf_impl + + def printf_impl(c: Context)(format: c.Expr[String], params: c.Expr[Any]*): c.Expr[Unit] = { + import c.universe._ + val Literal(Constant(s_format: String)) = format.tree + + val evals = ListBuffer[ValDef]() + def precompute(value: Tree, tpe: Type): Ident = { + val freshName = TermName(c.fresh("eval$")) + evals += ValDef(Modifiers(), freshName, TypeTree(tpe), value) + Ident(freshName) + } + + val paramsStack = Stack[Tree]((params map (_.tree)): _*) + val refs = s_format.split("(?<=%[\\w%])|(?=%[\\w%])") map { + case "%d" => precompute(paramsStack.pop, typeOf[Int]) + case "%s" => precompute(paramsStack.pop, typeOf[String]) + case "%%" => Literal(Constant("%")) + case part => Literal(Constant(part)) + } + + val stats = evals ++ refs.map(ref => reify(print(c.Expr[Any](ref).splice)).tree) + c.Expr[Unit](Block(stats.toList, Literal(Constant(())))) + } + } + +To use the `printf` macro, create another file `Test.scala` in the same directory and put the following code into it. +Note that using a macro is as simple as calling a function. It also doesn't require importing `scala.language.experimental.macros`. + + object Test extends App { + import Macros._ + printf("hello %s!", "world") + } + +An important aspect of macrology is separate compilation. To perform macro expansion, compiler needs a macro implementation in executable form. Thus macro implementations need to be compiled before the main compilation, otherwise you might see the following error: + + ~/Projects/Kepler/sandbox$ scalac -language:experimental.macros Macros.scala Test.scala + Test.scala:3: error: macro implementation not found: printf (the most common reason for that is that + you cannot use macro implementations in the same compilation run that defines them) + pointing to the output of the first phase + printf("hello %s!", "world") + ^ + one error found + + ~/Projects/Kepler/sandbox$ scalac Macros.scala && scalac Test.scala && scala Test + hello world! + +## Tips and tricks + +### Using macros with the command-line Scala compiler + +This scenario is covered in the previous section. In short, compile macros and their usages using separate invocations of `scalac`, and everything should work fine. If you use REPL, then it's even better, because REPL processes every line in a separate compilation run, so you'll be able to define a macro and use it right away. + +### Using macros with Maven or sbt + +The walkthrough in this guide uses the simplest possible command-line compilation, but macros also work with build tools such as Maven and sbt. Check out [https://github.com/scalamacros/sbt-example](https://github.com/scalamacros/sbt-example) or [https://github.com/scalamacros/maven-example](https://github.com/scalamacros/maven-example) for end-to-end examples, but in a nutshell you only need to know two things: +* Macros needs scala-reflect.jar in library dependencies. +* The separate compilation restriction requires macros to be placed in a separate project. + +### Using macros with Scala IDE or Intellij IDEA + +Both in Scala IDE and in Intellij IDEA macros are known to work fine, given they are moved to a separate project. + +### Debugging macros + +Debugging macros (i.e. the logic that drives macro expansion) is fairly straightforward. Since macros are expanded within the compiler, all that you need is to run the compiler under a debugger. To do that, you need to: 1) add all (!) the libraries from the lib directory in your Scala home (which include such jar files as `scala-library.jar`, `scala-reflect.jar` and `scala-compiler.jar`) to the classpath of your debug configuration, 2) set `scala.tools.nsc.Main` as an entry point, 3) provide the `-Dscala.usejavacp=true` system property for the JVM (very important!), 4) set command-line arguments for the compiler as `-cp Test.scala`, where `Test.scala` stands for a test file containing macro invocations to be expanded. After all that is done, you should be able to put a breakpoint inside your macro implementation and launch the debugger. + +What really requires special support in tools is debugging the results of macro expansion (i.e. the code that is generated by a macro). Since this code is never written out manually, you cannot set breakpoints there, and you won't be able to step through it. Scala IDE and Intellij IDEA teams will probably add support for this in their debuggers at some point, but for now the only way to debug macro expansions are diagnostic prints: `-Ymacro-debug-lite` (as described below), which prints out the code emitted by macros, and println to trace the execution of the generated code. + +### Inspecting generated code + +With `-Ymacro-debug-lite` it is possible to see both pseudo-Scala representation of the code generated by macro expansion and raw AST representation of the expansion. Both have their merits: the former is useful for surface analysis, while the latter is invaluable for fine-grained debugging. + + ~/Projects/Kepler/sandbox$ scalac -Ymacro-debug-lite Test.scala + typechecking macro expansion Macros.printf("hello %s!", "world") at + source-C:/Projects/Kepler/sandbox\Test.scala,line-3,offset=52 + { + val eval$1: String = "world"; + scala.this.Predef.print("hello "); + scala.this.Predef.print(eval$1); + scala.this.Predef.print("!"); + () + } + Block(List( + ValDef(Modifiers(), TermName("eval$1"), TypeTree().setType(String), Literal(Constant("world"))), + Apply( + Select(Select(This(TypeName("scala")), TermName("Predef")), TermName("print")), + List(Literal(Constant("hello")))), + Apply( + Select(Select(This(TypeName("scala")), TermName("Predef")), TermName("print")), + List(Ident(TermName("eval$1")))), + Apply( + Select(Select(This(TypeName("scala")), TermName("Predef")), TermName("print")), + List(Literal(Constant("!"))))), + Literal(Constant(()))) + +### Macros throwing unhandled exceptions + +What happens if macro throws an unhandled exception? For example, let's crash the `printf` macro by providing invalid input. +As the printout shows, nothing dramatic happens. Compiler guards itself against misbehaving macros, prints relevant part of a stack trace, and reports an error. + + ~/Projects/Kepler/sandbox$ scala + Welcome to Scala version 2.10.0-20120428-232041-e6d5d22d28 (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_25). + Type in expressions to have them evaluated. + Type :help for more information. + + scala> import Macros._ + import Macros._ + + scala> printf("hello %s!") + :11: error: exception during macro expansion: + java.util.NoSuchElementException: head of empty list + at scala.collection.immutable.Nil$.head(List.scala:318) + at scala.collection.immutable.Nil$.head(List.scala:315) + at scala.collection.mutable.Stack.pop(Stack.scala:140) + at Macros$$anonfun$1.apply(Macros.scala:49) + at Macros$$anonfun$1.apply(Macros.scala:47) + at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:237) + at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:237) + at scala.collection.IndexedSeqOptimized$class.foreach(IndexedSeqOptimized.scala:34) + at scala.collection.mutable.ArrayOps.foreach(ArrayOps.scala:39) + at scala.collection.TraversableLike$class.map(TraversableLike.scala:237) + at scala.collection.mutable.ArrayOps.map(ArrayOps.scala:39) + at Macros$.printf_impl(Macros.scala:47) + + printf("hello %s!") + ^ + +### Reporting warnings and errors + +The canonical way to interact with the user is through the methods of `scala.reflect.macros.FrontEnds`. +`c.error` reports a compilation error, `c.info` issues a warning, `c.abort` reports an error and terminates +execution of a macro. + + scala> def impl(c: Context) = + c.abort(c.enclosingPosition, "macro has reported an error") + impl: (c: scala.reflect.macros.Context)Nothing + + scala> def test = macro impl + defined term macro test: Any + + scala> test + :32: error: macro has reported an error + test + ^ + +Note that at the moment reporting facilities don't support multiple warnings or errors per position as described in +[SI-6910](https://issues.scala-lang.org/browse/SI-6910). This means that only the first error or warning per position +will be reported, and the others will be lost (also errors trump warnings at the same position, even if those are reported earlier). + +### Writing bigger macros + +When the code of a macro implementation grows big enough to warrant modularization beyond the body of the implementation method, it becomes apparent that one needs to carry around the context parameter, because most things of interest are path-dependent on the context. + +One of the approaches is to write a class that takes a parameter of type `Context` and then split the macro implementation into a series of methods of that class. This is natural and simple, except that it's hard to get it right. Here's a typical compilation error. + + scala> class Helper(val c: Context) { + | def generate: c.Tree = ??? + | } + defined class Helper + + scala> def impl(c: Context): c.Expr[Unit] = { + | val helper = new Helper(c) + | c.Expr(helper.generate) + | } + :32: error: type mismatch; + found : helper.c.Tree + (which expands to) helper.c.universe.Tree + required: c.Tree + (which expands to) c.universe.Tree + c.Expr(helper.generate) + ^ + +The problem in this snippet is in a path-dependent type mismatch. The Scala compiler does not understand that `c` in `impl` is the same object as `c` in `Helper`, even though the helper is constructed using the original `c`. + +Luckily just a small nudge is all that is needed for the compiler to figure out what's going on. One of the possible ways of doing that is using refinement types (the example below is the simplest application of the idea; for example, one could also write an implicit conversion from `Context` to `Helper` to avoid explicit instantiations and simplify the calls). + + scala> abstract class Helper { + | val c: Context + | def generate: c.Tree = ??? + | } + defined class Helper + + scala> def impl(c1: Context): c1.Expr[Unit] = { + | val helper = new { val c: c1.type = c1 } with Helper + | c1.Expr(helper.generate) + | } + impl: (c1: scala.reflect.macros.Context)c1.Expr[Unit] + +An alternative approach is to pass the identity of the context in an explicit type parameter. Note how the constructor of `Helper` uses `c.type` to express the fact that `Helper.c` is the same as the original `c`. Scala's type inference can't figure this out on its own, so we need to help it. + + scala> class Helper[C <: Context](val c: C) { + | def generate: c.Tree = ??? + | } + defined class Helper + + scala> def impl(c: Context): c.Expr[Unit] = { + | val helper = new Helper[c.type](c) + | c.Expr(helper.generate) + | } + impl: (c: scala.reflect.macros.Context)c.Expr[Unit] diff --git a/_overviews/macros/paradise.md b/_overviews/macros/paradise.md new file mode 100644 index 0000000000..130d293789 --- /dev/null +++ b/_overviews/macros/paradise.md @@ -0,0 +1,63 @@ +--- +layout: multipage-overview +title: Macro Paradise + +discourse: true + +partof: macros +overview-name: Macros + +num: 11 + +languages: [ja] +permalink: /overviews/macros/:title.html +--- +NEW + +**Eugene Burmako** + +> I have always imagined that paradise will be a kind of library. +> Jorge Luis Borges, "Poem of the Gifts" + +Macro paradise is a plugin for several versions of Scala compilers. +It is designed to reliably work with production releases of scalac, +making latest macro developments available way before they end up in future versions Scala. +Refer to the roadmap for [the list of supported features and versions](/overviews/macros/roadmap.html) +and visit [the paradise announcement](http://scalamacros.org/news/2013/08/07/roadmap-for-macro-paradise.html) +to learn more about our support guarantees. + + ~/210x $ scalac -Xplugin:paradise_*.jar -Xshow-phases + phase name id description + ---------- -- ----------- + parser 1 parse source into ASTs, perform simple desugaring + macroparadise 2 let our powers combine + namer 3 resolve names, attach symbols to trees in paradise + packageobjects 4 load package objects in paradise + typer 5 the meat and potatoes: type the trees in paradise + ... + +Some features in macro paradise bring a compile-time dependency on the macro paradise plugin, +some features do not, however none of those features need macro paradise at runtime. +Proceed to the [the feature list](/overviews/macros/roadmap.html) document for more information. + +Consult [https://github.com/scalamacros/sbt-example-paradise](https://github.com/scalamacros/sbt-example-paradise) +for an end-to-end example, but in a nutshell working with macro paradise is as easy as adding the following two lines +to your build (granted you’ve already [set up sbt](/overviews/macros/overview.html#using_macros_with_maven_or_sbt) +to use macros). + + resolvers += Resolver.sonatypeRepo("releases") + addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full) + +To use macro paradise in Maven follow the instructions provided at Stack Overflow on the page ["Enabling the macro-paradise Scala compiler plugin in Maven projects"](http://stackoverflow.com/questions/19086241/enabling-the-macro-paradise-scala-compiler-plugin-in-maven-projects) (also make sure to add the dependency on the Sonatype snapshots repository and `scala-reflect.jar`). + + + + org.scalamacros + paradise_ + 2.1.0 + + + +Sources of macro paradise are available at [https://github.com/scalamacros/paradise](https://github.com/scalamacros/paradise). +There are branches that support the latest 2.10.x release, the latest 2.11.x release, +snapshots of 2.10.x, 2.11.x and 2.12.x, as well as Scala virtualized. diff --git a/_overviews/macros/quasiquotes.md b/_overviews/macros/quasiquotes.md new file mode 100644 index 0000000000..41bbdd9098 --- /dev/null +++ b/_overviews/macros/quasiquotes.md @@ -0,0 +1,16 @@ +--- +layout: multipage-overview +title: Quasiquotes + +discourse: true + +partof: macros +overview-name: Macros + +num: 4 + +languages: [ja] +permalink: /overviews/macros/:title.html +--- + +Quasiquote guide has been moved to [/overviews/quasiquotes/intro.html](/overviews/quasiquotes/intro.html). diff --git a/_overviews/macros/roadmap.md b/_overviews/macros/roadmap.md new file mode 100644 index 0000000000..6140808a38 --- /dev/null +++ b/_overviews/macros/roadmap.md @@ -0,0 +1,44 @@ +--- +layout: multipage-overview +title: Roadmap + +discourse: true + +partof: macros +overview-name: Macros + +num: 12 + +languages: [ja] +permalink: /overviews/macros/:title.html +--- + +EXPERIMENTAL + +**Eugene Burmako** + +At the moment, we don't plan to introduce new reflection- or macro-related features in Scala 2.12, +so the functionality of Scala 2.12 and Paradise 2.12 is going to be the same as Scala 2.11 and Paradise 2.11 +modulo bugfixes and stability improvements. + +Feature-wise, our main effort is currently targeted at [scala.meta](http://scalameta.org), +the new foundation for metaprogramming Scala, which is simpler, more robust and much more suitable for portability +than the current system based on scala.reflect. We hope that one day scala.meta will supersede scala.reflect +and become the new standard way of doing metaprogramming in Scala. + +| Feature | Scala 2.10 | [Paradise 2.10](/overviews/macros/paradise.html) | Scala 2.11 | [Paradise 2.11](/overviews/macros/paradise.html) | Scala 2.12 | [Paradise 2.12](/overviews/macros/paradise.html) | +|-----------------------------------------------------------------------------------|---------------------------------|--------------------------------------------------|----------------------------|--------------------------------------------------|-----------------------------|--------------------------------------------------| +| [Blackbox/whitebox separation](/overviews/macros/blackbox-whitebox.html) | No | No 1 | Yes | Yes 1 | Yes | Yes 1 | +| [Def macros](/overviews/macros/overview.html) | Yes | Yes 1 | Yes | Yes 1 | Yes | Yes 1 | +| [Macro bundles](/overviews/macros/bundles.html) | No | No 1 | Yes | Yes 1 | Yes | Yes 1 | +| [Implicit macros](/overviews/macros/implicits.html) | Yes (since 2.10.2) | Yes 1 | Yes | Yes 1 | Yes | Yes 1 | +| [Fundep materialization](/overviews/macros/implicits.html#fundep_materialization) | Yes (since 2.10.5) 3 | Yes 2 | Yes | Yes 1 | Yes | Yes 1 | +| [Type providers](/overviews/macros/typeproviders.html) | Partial support (see docs) | Yes 2 | Partial support (see docs) | Yes 2 | Partial support (see docs) | Yes 2 | +| [Quasiquotes](/overviews/quasiquotes/intro.html) | No | Yes 1 | Yes | Yes 1 | Yes | Yes 1 | +| [Type macros](/overviews/macros/typemacros.html) | No | No | No | No | No | No | +| [Untyped macros](/overviews/macros/untypedmacros.html) | No | No | No | No | No | No | +| [Macro annotations](/overviews/macros/annotations.html) | No | Yes 2 | No | Yes 2 | No | Yes 2 | + +

      1 This feature doesn't bring a compile-time or a runtime dependency on macro paradise. This means that neither compiling against your bytecode that uses this feature, nor running this bytecode requires the macro paradise plugin to be present on classpath.

      +

      2 This feature brings a compile-time, but not a runtime dependency on macro paradise. This means that compiling against your bytecode that uses this feature will need the plugin to be added to your users' builds, however running this bytecode or results of macro expansions produced by this bytecode doesn't need additional classpath entries.

      +

      3 Only works under -Yfundep-materialization.

      diff --git a/_overviews/macros/typemacros.md b/_overviews/macros/typemacros.md new file mode 100644 index 0000000000..f638aebf74 --- /dev/null +++ b/_overviews/macros/typemacros.md @@ -0,0 +1,96 @@ +--- +layout: multipage-overview +title: Type Macros + +discourse: true + +overview-name: Macros + +languages: [ja] +permalink: /overviews/macros/:title.html +--- +OBSOLETE + +**Eugene Burmako** + +Type macros used to be available in previous versions of ["Macro Paradise"](/overviews/macros/paradise.html), +but are not supported anymore in macro paradise 2.0. +Visit [the paradise 2.0 announcement](http://scalamacros.org/news/2013/08/05/macro-paradise-2.0.0-snapshot.html) +for an explanation and suggested migration strategy. + +## Intuition + +Just as def macros make the compiler execute custom functions when it sees invocations of certain methods, type macros let one hook into the compiler when certain types are used. The snippet below shows definition and usage of the `H2Db` macro, which generates case classes representing tables in a database along with simple CRUD functionality. + + type H2Db(url: String) = macro impl + + object Db extends H2Db("coffees") + + val brazilian = Db.Coffees.insert("Brazilian", 99, 0) + Db.Coffees.update(brazilian.copy(price = 10)) + println(Db.Coffees.all) + +The full source code of the `H2Db` type macro is provided [at GitHub](https://github.com/xeno-by/typemacros-h2db), and this guide covers its most important aspects. First the macro generates the statically typed database wrapper by connecting to a database at compile-time (tree generation is explained in [the reflection overview](http://docs.scala-lang.org/overviews/reflection/overview.html)). Then it uses the NEW `c.introduceTopLevel` API ([Scaladoc](https://scala-webapps.epfl.ch/jenkins/view/misc/job/macro-paradise211-nightly-main/ws/dists/latest/doc/scala-devel-docs/api/index.html#scala.reflect.macros.Synthetics)) to insert the generated wrapper into the list of top-level definitions maintained by the compiler. Finally, the macro returns an `Apply` node, which represents a super constructor call to the generated class. NOTE that type macros are supposed to expand into `c.Tree`, unlike def macros, which expand into `c.Expr[T]`. That's because `Expr`s represent terms, while type macros expand into types. + + type H2Db(url: String) = macro impl + + def impl(c: Context)(url: c.Expr[String]): c.Tree = { + val name = c.freshName(c.enclosingImpl.name).toTypeName + val clazz = ClassDef(..., Template(..., generateCode())) + c.introduceTopLevel(c.enclosingPackage.pid.toString, clazz) + val classRef = Select(c.enclosingPackage.pid, name) + Apply(classRef, List(Literal(Constant(c.eval(url))))) + } + + object Db extends H2Db("coffees") + // equivalent to: object Db extends Db$1("coffees") + +Instead of generating a synthetic class and expanding into a reference to it, a type macro can transform its host instead by returning a `Template` tree. Inside scalac both class and object definitions are internally represented as thin wrappers over `Template` trees, so by expanding into a template, type macro has a possibility to rewrite the entire body of the affected class or object. You can see a full-fledged example of this technique [at GitHub](https://github.com/xeno-by/typemacros-lifter). + + type H2Db(url: String) = macro impl + + def impl(c: Context)(url: c.Expr[String]): c.Tree = { + val Template(_, _, existingCode) = c.enclosingTemplate + Template(..., existingCode ++ generateCode()) + } + + object Db extends H2Db("coffees") + // equivalent to: object Db { + // + // + // } + +## Details + +Type macros represent a hybrid between def macros and type members. On the one hand, they are defined like methods (e.g. they can have value arguments, type parameters with context bounds, etc). On the other hand, they belong to the namespace of types and, as such, they can only be used where types are expected (see an exhaustive example [at GitHub](https://github.com/scalamacros/kepler/blob/paradise/macros211/test/files/run/macro-typemacros-used-in-funny-places-a/Test_2.scala)), they can only override types or other type macros, etc. + +| Feature | Def macros | Type macros | Type members | +|--------------------------------|------------|-------------|--------------| +| Are split into defs and impl | Yes | Yes | No | +| Can have value parameters | Yes | Yes | No | +| Can have type parameters | Yes | Yes | Yes | +| ... with variance annotations | No | No | Yes | +| ... with context bounds | Yes | Yes | No | +| Can be overloaded | Yes | Yes | No | +| Can be inherited | Yes | Yes | Yes | +| Can override and be overridden | Yes | Yes | Yes | + +In Scala programs type macros can appear in one of five possible roles: type role, applied type role, parent type role, new role and annotation role. Depending on the role in which a macro is used, which can be inspected with the NEW `c.macroRole` API ([Scaladoc](https://scala-webapps.epfl.ch/jenkins/view/misc/job/macro-paradise-nightly-main/ws/dists/latest/doc/scala-devel-docs/api/index.html#scala.reflect.macros.Enclosures)), its list of allowed expansions is different. + +| Role | Example | Class | Non-class? | Apply? | Template? | +|--------------|-------------------------------------------------|-------|------------|--------|-----------| +| Type | `def x: TM(2)(3) = ???` | Yes | Yes | No | No | +| Applied type | `class C[T: TM(2)(3)]` | Yes | Yes | No | No | +| Parent type | `class C extends TM(2)(3)`
      `new TM(2)(3){}` | Yes | No | Yes | Yes | +| New | `new TM(2)(3)` | Yes | No | Yes | No | +| Annotation | `@TM(2)(3) class C` | Yes | No | Yes | No | + +To put it in a nutshell, expansion of a type macro replace the usage of a type macro with a tree it returns. To find out whether an expansion makes sense, mentally replace some usage of a macro with its expansion and check whether the resulting program is correct. + +For example, a type macro used as `TM(2)(3)` in `class C extends TM(2)(3)` can expand into `Apply(Ident(TypeName("B")), List(Literal(Constant(2))))`, because that would result in `class C extends B(2)`. However the same expansion wouldn't make sense if `TM(2)(3)` was used as a type in `def x: TM(2)(3) = ???`, because `def x: B(2) = ???` (given that `B` itself is not a type macro; if it is, it will be recursively expanded and the result of the expansion will determine validity of the program). + +## Tips and tricks + +### Generating classes and objects + +With type macros you might increasingly find yourself in a zone where `reify` is not applicable, as explained [at StackOverflow](http://stackoverflow.com/questions/13795490/how-to-use-type-calculated-in-scala-macro-in-a-reify-clause). In that case consider using [quasiquotes](/overviews/quasiquotes/intro.html), another experimental feature from macro paradise, as an alternative to manual tree construction. diff --git a/_overviews/macros/typeproviders.md b/_overviews/macros/typeproviders.md new file mode 100644 index 0000000000..99a67437aa --- /dev/null +++ b/_overviews/macros/typeproviders.md @@ -0,0 +1,144 @@ +--- +layout: multipage-overview +title: Type Providers + +discourse: true + +partof: macros +overview-name: Macros + +num: 8 + +languages: [ja] +permalink: /overviews/macros/:title.html +--- +EXPERIMENTAL + +**Eugene Burmako** + +Type providers aren't implemented as a dedicated macro flavor, but are rather built on top of the functionality +that Scala macros already provide. + +There are two strategies of emulating type providers: one based on structural types (referred to as "anonymous type providers") +and one based on macro annotations (referred to as "public type providers"). The former builds on functionality available +in 2.10.x, 2.11.x and 2.12.x, while the latter requires macro paradise. Both strategies can be used to implement erased type providers +as described below. + +Note that macro paradise is needed both to compile and to expand macro annotations, +which means that both authors and users of public type providers will have to add macro paradise to their builds. +However, after macro annotations expand, the resulting code will no longer have any references to macro paradise +and won't require its presence at compile-time or at runtime. + +Recently we've given a talk about macro-based type providers in Scala, summarizing the state of the art and providing +concrete examples. Slides and accompanying code can be found at [https://github.com/travisbrown/type-provider-examples](https://github.com/travisbrown/type-provider-examples). + +## Introduction + +Type providers are a strongly-typed type-bridging mechanism, which enables information-rich programming in F# 3.0. +A type provider is a compile-time facility, which is capable of generating definitions and their implementations +based on static parameters describing datasources. Type providers can operate in two modes: non-erased and erased. +The former is similar to textual code generation in the sense that every generated type becomes bytecode, while +in the latter case generated types only manifest themselves during type checking, but before bytecode generation +get erased to programmer-provided upper bounds. + +In Scala, macro expansions can generate whatever code the programmer likes, including `ClassDef`, `ModuleDef`, `DefDef`, +and other definition nodes, so the code generation part of type providers is covered. Keeping that in mind, in order +to emulate type providers we need to solve two more challenges: + +1. Make generated definitions publicly visible (def macros, the only available macro flavor in Scala 2.10, 2.11 and 2.12, +are local in the sense that the scope of their expansions is limited: [https://groups.google.com/d/msg/scala-user/97ARwwoaq2U/kIGWeiqSGzcJ](https://groups.google.com/d/msg/scala-user/97ARwwoaq2U/kIGWeiqSGzcJ)). +1. Make generated definitions optionally erasable (Scala supports erasure for a number of language constructs, +e.g. for abstract type members and value classes, but the mechanism is not extensible, which means that macro writers can't customize it). + +## Anonymous type providers + +Even though the scope of definitions introduced by expansions of def macros is limited to those expansions, +these definitions can escape their scopes by turning into structural types. For instance, consider the `h2db` macro that +takes a connection string and generates a module that encapsulates the given database, expanding as follows. + + def h2db(connString: String): Any = macro ... + + // an invocation of the `h2db` macro + val db = h2db("jdbc:h2:coffees.h2.db") + + // expands into the following code + val db = { + trait Db { + case class Coffee(...) + val Coffees: Table[Coffee] = ... + } + new Db {} + } + +It is true that noone outside the macro expansion block would be able to refer to the `Coffee` class directly, +however if we inspect the type of `db`, we will find something fascinating. + + scala> val db = h2db("jdbc:h2:coffees.h2.db") + db: AnyRef { + type Coffee { val name: String; val price: Int; ... } + val Coffees: Table[this.Coffee] + } = $anon$1... + +As we can see, when the typechecker tried to infer a type for `db`, it took all the references to locally declared classes +and replaced them with structural types that contain all publicly visible members of those classes. The resulting type +captures the essence of the generated classes, providing a statically typed interface to their members. + + scala> db.Coffees.all + res1: List[Db$1.this.Coffee] = List(Coffee(Brazilian,99,0)) + +This approach to type providers is quite neat, because it can be used with production versions of Scala, however +it has performance problems caused by the fact that Scala emits reflective calls when compiling accesses to members +of structural types. There are several strategies of dealing with that, but this margin is too narrow to contain them +so I refer you to an amazing blog series by Travis Brown for details: [post 1](http://meta.plasm.us/posts/2013/06/19/macro-supported-dsls-for-schema-bindings/), [post 2](http://meta.plasm.us/posts/2013/07/11/fake-type-providers-part-2/), [post 3](http://meta.plasm.us/posts/2013/07/12/vampire-methods-for-structural-types/). + +## Public type providers + +With the help of [macro paradise](/overviews/macros/paradise.html) and its [macro annotations](/overviews/macros/annotations.html), it becomes +possible to easily generate publicly visible classes, without having to apply workarounds based on structural types. The annotation-based +solution is very straightforward, so I won't be writing much about it here. + + class H2Db(connString: String) extends StaticAnnotation { + def macroTransform(annottees: Any*) = macro ... + } + + @H2Db("jdbc:h2:coffees.h2.db") object Db + println(Db.Coffees.all) + Db.Coffees.insert("Brazilian", 99, 0) + +### Addressing the erasure problem + +We haven't looked into this in much detail, but there's a hypothesis that a combination of type members +and singleton types can provide an equivalent of erased type providers in F#. Concretely, classes that we don't want to erase +should be declared as usual, whereas classes that should be erased to a given upper bound should be declared as type aliases +to that upper bound parameterized by a singleton type that carries unique identifiers. With that approach, every new generated type +would still incur the overhead of additional bytecode to the metadata of type aliases, but that bytecode would be significantly smaller +than bytecode of a full-fledged class. This technique applies to both anonymous and public type providers. + + object Netflix { + type Title = XmlEntity["http://.../Title".type] + def Titles: List[Title] = ... + type Director = XmlEntity["http://.../Director".type] + def Directors: List[Director] = ... + ... + } + + class XmlEntity[Url] extends Dynamic { + def selectDynamic(field: String) = macro XmlEntity.impl + } + + object XmlEntity { + def impl(c: Context)(field: c.Tree) = { + import c.universe._ + val TypeRef(_, _, tUrl) = c.prefix.tpe + val ConstantType(Constant(sUrl: String)) = tUrl + val schema = loadSchema(sUrl) + val Literal(Constant(sField: String)) = field + if (schema.contains(sField)) q"${c.prefix}($sField)" + else c.abort(s"value $sField is not a member of $sUrl") + } + } + +## Blackbox vs whitebox + +Both anonymous and public type providers must be [whitebox](/overviews/macros/blackbox-whitebox.html). +If you declare a type provider macro as [blackbox](/overviews/macros/blackbox-whitebox.html), it will not work. diff --git a/_overviews/macros/untypedmacros.md b/_overviews/macros/untypedmacros.md new file mode 100644 index 0000000000..5b1a57c0bd --- /dev/null +++ b/_overviews/macros/untypedmacros.md @@ -0,0 +1,77 @@ +--- +layout: multipage-overview +title: Untyped Macros + +discourse: true + +overview-name: Macros + +languages: [ja] +permalink: /overviews/macros/:title.html +--- +OBSOLETE + +**Eugene Burmako** + +Untyped macros used to be available in previous versions of ["Macro Paradise"](/overviews/macros/paradise.html), +but are not supported anymore in macro paradise 2.0. +Visit [the paradise 2.0 announcement](http://scalamacros.org/news/2013/08/05/macro-paradise-2.0.0-snapshot.html) +for an explanation and suggested migration strategy. + +## Intuition + +Being statically typed is great, but sometimes that is too much of a burden. Take for example, the latest experiment of Alois Cochard with +implementing enums using type macros - the so called [Enum Paradise](https://github.com/aloiscochard/enum-paradise). Here's how Alois has +to write his type macro, which synthesizes an enumeration module from a lightweight spec: + + object Days extends Enum('Monday, 'Tuesday, 'Wednesday...) + +Instead of using clean identifier names, e.g. `Monday` or `Friday`, we have to quote those names, so that the typechecker doesn't complain +about non-existent identifiers. What a bummer - in the `Enum` macro we want to introduce new bindings, not to look up for existing ones, +but the compiler won't let us, because it thinks it needs to typecheck everything that goes into the macro. + +Let's take a look at how the `Enum` macro is implemented by inspecting the signatures of its macro definition and implementation. There we can +see that the macro definition signature says `symbol: Symbol*`, forcing the compiler to typecheck the corresponding argument: + + type Enum(symbol: Symbol*) = macro Macros.enum + object Macros { + def enum(c: Context)(symbol: c.Expr[Symbol]*): c.Tree = ... + } + +Untyped macros provide a notation and a mechanism to tell the compiler that you know better how to typecheck given arguments of your macro. +To do that, simply replace macro definition parameter types with underscores and macro implementation parameter types with `c.Tree`: + + type Enum(symbol: _*) = macro Macros.enum + object Macros { + def enum(c: Context)(symbol: c.Tree*): c.Tree = ... + } + +## Details + +The cease-typechecking underscore can be used in exactly three places in Scala programs: 1) as a parameter type in a macro, +2) as a vararg parameter type in a macro, 3) as a return type of a macro. Usages outside macros or as parts of complex types won't work. +The former will lead to a compile error, the latter, as in e.g. `List[_]`, will produce existential types as usual. + +Note that untyped macros enable extractor macros: [SI-5903](https://issues.scala-lang.org/browse/SI-5903). In 2.10.x, it is possible +to declare `unapply` or `unapplySeq` as macros, but usability of such macros is extremely limited as described in the discussion +of the linked JIRA issue. Untyped macros make the full power of textual abstraction available in pattern matching. The +[test/files/run/macro-expand-unapply-c](https://github.com/scalamacros/kepler/tree/paradise/macros211/test/files/run/macro-expand-unapply-c) +unit test provides details on this matter. + +If a macro has one or more untyped parameters, then when typing its expansions, the typechecker will do nothing to its arguments +and will pass them to the macro untyped. Even if some of the parameters do have type annotations, they will currently be ignored. This +is something we plan on improving: [SI-6971](https://issues.scala-lang.org/browse/SI-6971). Since arguments aren't typechecked, you +also won't having implicits resolved and type arguments inferred (however, you can do both with `c.typeCheck` and `c.inferImplicitValue`). + +Explicitly provided type arguments will be passed to the macro as is. If type arguments aren't provided, they will be inferred as much as +possible without typechecking the value arguments and passed to the macro in that state. Note that type arguments still get typechecked, but +this restriction might also be lifted in the future: [SI-6972](https://issues.scala-lang.org/browse/SI-6972). + +If a def macro has untyped return type, then the first of the two typechecks employed after its expansion will be omitted. A refresher: +the first typecheck of a def macro expansion is performed against the return type of its definitions, the second typecheck is performed +against the expected type of the expandee. More information can be found at Stack Overflow: [Static return type of Scala macros](http://stackoverflow.com/questions/13669974/static-return-type-of-scala-macros). Type macros never underwent the first typecheck, so +nothing changes for them (and you won't be able to specify any return type for a type macro to begin with). + +Finally the untyped macros patch enables using `c.Tree` instead of `c.Expr[T]` everywhere in signatures of macro implementations. +Both for parameters and return types, all four combinations of untyped/typed in macro def and tree/expr in macro impl are supported. +Check our unit tests for more information: [test/files/run/macro-untyped-conformance](https://github.com/scalamacros/kepler/blob/b55bda4860a205c88e9ae27015cf2d6563cc241d/test/files/run/macro-untyped-conformance/Impls_Macros_1.scala). diff --git a/_overviews/macros/usecases.md b/_overviews/macros/usecases.md new file mode 100644 index 0000000000..0ece8fc36a --- /dev/null +++ b/_overviews/macros/usecases.md @@ -0,0 +1,33 @@ +--- +layout: multipage-overview +title: Use Cases + +discourse: true + +partof: macros +overview-name: Macros + +num: 1 + +languages: [ja] +permalink: /overviews/macros/:title.html +--- + +EXPERIMENTAL + +**Eugene Burmako** + +Since their release as an experimental feature of Scala 2.10, macros have brought previously impossible or prohibitively complex things +to the realm of possible. Both commercial and research users of Scala use macros to bring their ideas to life. +At EPFL we are leveraging macros to power our research. Lightbend also employs macros in a number of projects. +Macros are also popular in the community and have already given rise to a number of interesting applications. + +The recent talk ["What Are Macros Good For?"](http://scalamacros.org/paperstalks/2013-07-17-WhatAreMacrosGoodFor.pdf) +describes and systemizes uses that macros found among Scala 2.10 users. The thesis of the talk is that macros are good for +code generation, static checking and DSLs, illustrated with a number of examples from research and industry. + +We have also published a paper in the Scala'13 workshop, +["Scala Macros: Let Our Powers Combine!"](http://scalamacros.org/paperstalks/2013-04-22-LetOurPowersCombine.pdf), +covering the state of the art of macrology in Scala 2.10 from a more academic point of view. +In the paper we show how the rich syntax and static types of Scala synergize with macros and +explore how macros enable new and unique ways to use pre-existing language features. diff --git a/_overviews/parallel-collections/architecture.md b/_overviews/parallel-collections/architecture.md new file mode 100644 index 0000000000..d5173e915f --- /dev/null +++ b/_overviews/parallel-collections/architecture.md @@ -0,0 +1,120 @@ +--- +layout: multipage-overview +title: Architecture of the Parallel Collections Library + +discourse: true + +partof: parallel-collections +overview-name: Parallel Collections + +num: 5 + +languages: [ja, zh-cn, es, ru] +permalink: /overviews/parallel-collections/:title.html +--- + +Like the normal, sequential collections library, Scala's parallel collections +library contains a large number of collection operations which exist uniformly +on many different parallel collection implementations. And like the sequential +collections library, Scala's parallel collections library seeks to prevent +code duplication by likewise implementing most operations in terms of parallel +collection "templates" which need only be defined once and can be flexibly +inherited by many different parallel collection implementations. + +The benefits of this approach are greatly eased **maintenance** and +**extensibility**. In the case of maintenance-- by having a single +implementation of a parallel collections operation inherited by all parallel +collections, maintenance becomes easier and more robust; bug fixes propagate +down the class hierarchy, rather than needing implementations to be +duplicated. For the same reasons, the entire library becomes easier to +extend-- new collection classes can simply inherit most of their operations. + +## Core Abstractions + +The aforementioned "template" traits implement most parallel operations in +terms of two core abstractions-- `Splitter`s and `Combiner`s. + +### Splitters + +The job of a `Splitter`, as its name suggests, is to split a parallel +collection into a non-trival partition of its elements. The basic idea is to +split the collection into smaller parts until they are small enough to be +operated on sequentially. + + trait Splitter[T] extends Iterator[T] { + def split: Seq[Splitter[T]] + } + +Interestingly, `Splitter`s are implemented as `Iterator`s, meaning that apart +from splitting, they are also used by the framework to traverse a parallel +collection (that is, they inherit standard methods on `Iterator`s such as +`next` and `hasNext`.) What's unique about this "splitting iterator" is that, +its `split` method splits `this` (again, a `Splitter`, a type of `Iterator`) +further into additional `Splitter`s which each traverse over **disjoint** +subsets of elements of the whole parallel collection. And similar to normal +`Iterator`s, a `Splitter` is invalidated after its `split` method is invoked. + +In general, collections are partitioned using `Splitter`s into subsets of +roughly the same size. In cases where more arbitrarily-sized partions are +required, in particular on parallel sequences, a `PreciseSplitter` is used, +which inherits `Splitter` and additionally implements a precise split method, +`psplit`. + +### Combiners + +`Combiner`s can be thought of as a generalized `Builder`, from Scala's sequential +collections library. Each parallel collection provides a separate `Combiner`, +in the same way that each sequential collection provides a `Builder`. + +While in the case of sequential collections, elements can be added to a +`Builder`, and a collection can be produced by invoking the `result` method, +in the case of parallel collections, a `Combiner` has a method called +`combine` which takes another `Combiner` and produces a new `Combiner` that +contains the union of both's elements. After `combine` has been invoked, both +`Combiner`s become invalidated. + + trait Combiner[Elem, To] extends Builder[Elem, To] { + def combine(other: Combiner[Elem, To]): Combiner[Elem, To] + } + +The two type parameters `Elem` and `To` above simply denote the element type +and the type of the resulting collection, respectively. + +_Note:_ Given two `Combiner`s, `c1` and `c2` where `c1 eq c2` is `true` +(meaning they're the same `Combiner`), invoking `c1.combine(c2)` always does +nothing and simpy returns the receiving `Combiner`, `c1`. + +## Hierarchy + +Scala's parallel collection's draws much inspiration from the design of +Scala's (sequential) collections library-- as a matter of fact, it mirrors the +regular collections framework's corresponding traits, as shown below. + +[]({{ site.baseurl }}/resources/images/parallel-collections-hierarchy.png) + +
      Hierarchy of Scala's Collections and Parallel Collections Libraries
      +
      + +The goal is of course to integrate parallel collections as tightly as possible +with sequential collections, so as to allow for straightforward substitution +of sequential and parallel collections. + +In order to be able to have a reference to a collection which may be either +sequential or parallel (such that it's possible to "toggle" between a parallel +collection and a sequential collection by invoking `par` and `seq`, +respectively), there has to exist a common supertype of both collection types. +This is the origin of the "general" traits shown above, `GenTraversable`, +`GenIterable`, `GenSeq`, `GenMap` and `GenSet`, which don't guarantee in-order +or one-at-a-time traversal. Corresponding sequential or parallel traits +inherit from these. For example, a `ParSeq` and `Seq` are both subtypes of a +general sequence `GenSeq`, but they are in no inheritance relationship with +respect to each other. + +For a more detailed discussion of hierarchy shared between sequential and +parallel collections, see the technical report. \[[1][1]\] + +## References + +1. [On a Generic Parallel Collection Framework, Aleksandar Prokopec, Phil Bawgell, Tiark Rompf, Martin Odersky, June 2011][1] + +[1]: http://infoscience.epfl.ch/record/165523/files/techrep.pdf "flawed-benchmark" diff --git a/_overviews/parallel-collections/concrete-parallel-collections.md b/_overviews/parallel-collections/concrete-parallel-collections.md new file mode 100644 index 0000000000..558755cb55 --- /dev/null +++ b/_overviews/parallel-collections/concrete-parallel-collections.md @@ -0,0 +1,261 @@ +--- +layout: multipage-overview +title: Concrete Parallel Collection Classes + +discourse: true + +partof: parallel-collections +overview-name: Parallel Collections + +num: 2 + +languages: [ja, zh-cn, es, ru] +permalink: /overviews/parallel-collections/:title.html +--- + +## Parallel Array + +A [ParArray](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/parallel/mutable/ParArray.html) +sequence holds a linear, +contiguous array of elements. This means that the elements can be accessed and +updated efficiently by modifying the underlying array. Traversing the +elements is also very efficient for this reason. Parallel arrays are like +arrays in the sense that their size is constant. + + scala> val pa = scala.collection.parallel.mutable.ParArray.tabulate(1000)(x => 2 * x + 1) + pa: scala.collection.parallel.mutable.ParArray[Int] = ParArray(1, 3, 5, 7, 9, 11, 13,... + + scala> pa reduce (_ + _) + res0: Int = 1000000 + + scala> pa map (x => (x - 1) / 2) + res1: scala.collection.parallel.mutable.ParArray[Int] = ParArray(0, 1, 2, 3, 4, 5, 6, 7,... + +Internally, splitting a parallel array +[splitter]({{ site.baseurl }}/overviews/parallel-collections/architecture.html#core_abstractions) +amounts to creating two new splitters with their iteration indices updated. +[Combiners]({{ site.baseurl }}/overviews/parallel-collections/architecture.html#core_abstractions) +are slightly more involved.Since for most transformer methods (e.g. `flatMap`, `filter`, `takeWhile`, +etc.) we don't know the number of elements (and hence, the array size) in +advance, each combiner is essentially a variant of an array buffer with an +amortized constant time `+=` operation. Different processors add elements to +separate parallel array combiners, which are then combined by chaining their +internal arrays. The underlying array is only allocated and filled in parallel +after the total number of elements becomes known. For this reason, transformer +methods are slightly more expensive than accessor methods. Also, note that the +final array allocation proceeds sequentially on the JVM, so this can prove to +be a sequential bottleneck if the mapping operation itself is very cheap. + +By calling the `seq` method, parallel arrays are converted to `ArraySeq` +collections, which are their sequential counterparts. This conversion is +efficient, and the `ArraySeq` is backed by the same underlying array as the +parallel array it was obtained from. + + +## Parallel Vector + +A [ParVector](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/parallel/immutable/ParVector.html) +is an immutable sequence with a low-constant factor logarithmic access and +update time. + + scala> val pv = scala.collection.parallel.immutable.ParVector.tabulate(1000)(x => x) + pv: scala.collection.parallel.immutable.ParVector[Int] = ParVector(0, 1, 2, 3, 4, 5, 6, 7, 8, 9,... + + scala> pv filter (_ % 2 == 0) + res0: scala.collection.parallel.immutable.ParVector[Int] = ParVector(0, 2, 4, 6, 8, 10, 12, 14, 16, 18,... + +Immutable vectors are represented by 32-way trees, so +[splitter]({{ site.baseurl }}/overviews/parallel-collections/architecture.html#core_abstractions)s +are split by assigning subtrees to each splitter. +[Combiners]({{ site.baseurl }}/overviews/parallel-collections/architecture.html#core_abstractions) +currently keep a vector of +elements and are combined by lazily copying the elements. For this reason, +transformer methods are less scalable than those of a parallel array. Once the +vector concatenation operation becomes available in a future Scala release, +combiners will be combined using concatenation and transformer methods will +become much more efficient. + +Parallel vector is a parallel counterpart of the sequential +[Vector](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Vector.html), +so conversion between the two takes constant time. + + +## Parallel Range + +A [ParRange](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/parallel/immutable/ParRange.html) +is an ordered sequence of elements equally spaced apart. A parallel range is +created in a similar way as the sequential +[Range](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Range.html): + + scala> 1 to 3 par + res0: scala.collection.parallel.immutable.ParRange = ParRange(1, 2, 3) + + scala> 15 to 5 by -2 par + res1: scala.collection.parallel.immutable.ParRange = ParRange(15, 13, 11, 9, 7, 5) + +Just as sequential ranges have no builders, parallel ranges have no +[combiner]({{ site.baseurl }}/overviews/parallel-collections/architecture.html#core_abstractions)s. +Mapping the elements of a parallel range produces a parallel vector. +Sequential ranges and parallel ranges can be converted efficiently one from +another using the `seq` and `par` methods. + + +## Parallel Hash Tables + +Parallel hash tables store their elements in an underlying array and place +them in the position determined by the hash code of the respective element. +Parallel mutable hash sets +([mutable.ParHashSet](http://www.scala-lang.org/api/{{ site.scala-version}}/scala/collection/parallel/mutable/ParHashSet.html)) +and parallel mutable hash maps +([mutable.ParHashMap](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/parallel/mutable/ParHashMap.html)) +are based on hash tables. + + scala> val phs = scala.collection.parallel.mutable.ParHashSet(1 until 2000: _*) + phs: scala.collection.parallel.mutable.ParHashSet[Int] = ParHashSet(18, 327, 736, 1045, 773, 1082,... + + scala> phs map (x => x * x) + res0: scala.collection.parallel.mutable.ParHashSet[Int] = ParHashSet(2181529, 2446096, 99225, 2585664,... + +Parallel hash table combiners sort elements into buckets according to their +hashcode prefix. They are combined by simply concatenating these buckets +together. Once the final hash table is to be constructed (i.e. combiner +`result` method is called), the underlying array is allocated and the elements +from different buckets are copied in parallel to different contiguous segments +of the hash table array. + +Sequential hash maps and hash sets can be converted to their parallel variants +using the `par` method. Parallel hash tables internally require a size map +which tracks the number of elements in different chunks of the hash table. +What this means is that the first time that a sequential hash table is +converted into a parallel hash table, the table is traversed and the size map +is created - for this reason, the first call to `par` takes time linear in the +size of the hash table. Further modifications to the hash table maintain the +state of the size map, so subsequent conversions using `par` and `seq` have +constant complexity. Maintenance of the size map can be turned on and off +using the `useSizeMap` method of the hash table. Importantly, modifications in +the sequential hash table are visible in the parallel hash table, and vice +versa. + + +## Parallel Hash Tries + +Parallel hash tries are a parallel counterpart of the immutable hash tries, +which are used to represent immutable sets and maps efficiently. They are +supported by classes +[immutable.ParHashSet](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/parallel/immutable/ParHashSet.html) +and +[immutable.ParHashMap](http://www.scala-lang.org/api/{{ site.scala-version}}/scala/collection/parallel/immutable/ParHashMap.html). + + scala> val phs = scala.collection.parallel.immutable.ParHashSet(1 until 1000: _*) + phs: scala.collection.parallel.immutable.ParHashSet[Int] = ParSet(645, 892, 69, 809, 629, 365, 138, 760, 101, 479,... + + scala> phs map { x => x * x } sum + res0: Int = 332833500 + +Similar to parallel hash tables, parallel hash trie +[combiners]({{ site.baseurl }}/overviews/parallel-collections/architecture.html#core_abstractions) +pre-sort the +elements into buckets and construct the resulting hash trie in parallel by +assigning different buckets to different processors, which construct the +subtries independently. + +Parallel hash tries can be converted back and forth to sequential hash tries +by using the `seq` and `par` method in constant time. + + +## Parallel Concurrent Tries + +A [concurrent.TrieMap](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/concurrent/TrieMap.html) +is a concurrent thread-safe map, whereas a +[mutable.ParTrieMap](http://www.scala-lang.org/api/{{ site.scala-version}}/scala/collection/parallel/mutable/ParTrieMap.html) +is its parallel counterpart. While most concurrent data structures do not guarantee +consistent traversal if the data structure is modified during traversal, +Ctries guarantee that updates are only visible in the next iteration. This +means that you can mutate the concurrent trie while traversing it, like in the +following example which outputs square roots of number from 1 to 99: + + scala> val numbers = scala.collection.parallel.mutable.ParTrieMap((1 until 100) zip (1 until 100): _*) map { case (k, v) => (k.toDouble, v.toDouble) } + numbers: scala.collection.parallel.mutable.ParTrieMap[Double,Double] = ParTrieMap(0.0 -> 0.0, 42.0 -> 42.0, 70.0 -> 70.0, 2.0 -> 2.0,... + + scala> while (numbers.nonEmpty) { + | numbers foreach { case (num, sqrt) => + | val nsqrt = 0.5 * (sqrt + num / sqrt) + | numbers(num) = nsqrt + | if (math.abs(nsqrt - sqrt) < 0.01) { + | println(num, nsqrt) + | numbers.remove(num) + | } + | } + | } + (1.0,1.0) + (2.0,1.4142156862745097) + (7.0,2.64576704419029) + (4.0,2.0000000929222947) + ... + + +[Combiners]({{ site.baseurl }}/overviews/parallel-collections/architecture.html#core_abstractions) +are implemented as `TrieMap`s under the hood-- since this is a +concurrent data structure, only one combiner is constructed for the entire +transformer method invocation and shared by all the processors. + +As with all parallel mutable collections, `TrieMap`s and parallel `ParTrieMap`s obtained +by calling `seq` or `par` methods are backed by the same store, so +modifications in one are visible in the other. Conversions happen in constant +time. + + +## Performance characteristics + +Performance characteristics of sequence types: + +| | head | tail | apply | update| prepend | append | insert | +| -------- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | +| `ParArray` | C | L | C | C | L | L | L | +| `ParVector` | eC | eC | eC | eC | eC | eC | - | +| `ParRange` | C | C | C | - | - | - | - | + +Performance characteristics of set and map types: + +| | lookup | add | remove | +| -------- | ---- | ---- | ---- | +| **immutable** | | | | +| `ParHashSet`/`ParHashMap`| eC | eC | eC | +| **mutable** | | | | +| `ParHashSet`/`ParHashMap`| C | C | C | +| `ParTrieMap` | eC | eC | eC | + + +### Key + +The entries in the above two tables are explained as follows: + +| | | +| --- | ---- | +| **C** | The operation takes (fast) constant time. | +| **eC** | The operation takes effectively constant time, but this might depend on some assumptions such as maximum length of a vector or distribution of hash keys.| +| **aC** | The operation takes amortized constant time. Some invocations of the operation might take longer, but if many operations are performed on average only constant time per operation is taken. | +| **Log** | The operation takes time proportional to the logarithm of the collection size. | +| **L** | The operation is linear, that is it takes time proportional to the collection size. | +| **-** | The operation is not supported. | + +The first table treats sequence types--both immutable and mutable--with the following operations: + +| | | +| --- | ---- | +| **head** | Selecting the first element of the sequence. | +| **tail** | Producing a new sequence that consists of all elements except the first one. | +| **apply** | Indexing. | +| **update** | Functional update (with `updated`) for immutable sequences, side-effecting update (with `update` for mutable sequences. | +| **prepend**| Adding an element to the front of the sequence. For immutable sequences, this produces a new sequence. For mutable sequences it modified the existing sequence. | +| **append** | Adding an element and the end of the sequence. For immutable sequences, this produces a new sequence. For mutable sequences it modified the existing sequence. | +| **insert** | Inserting an element at an arbitrary position in the sequence. This is only supported directly for mutable sequences. | + +The second table treats mutable and immutable sets and maps with the following operations: + +| | | +| --- | ---- | +| **lookup** | Testing whether an element is contained in set, or selecting a value associated with a key. | +| **add** | Adding a new element to a set or key/value pair to a map. | +| **remove** | Removing an element from a set or a key from a map. | +| **min** | The smallest element of the set, or the smallest key of a map. | diff --git a/_overviews/parallel-collections/configuration.md b/_overviews/parallel-collections/configuration.md new file mode 100644 index 0000000000..80f077888e --- /dev/null +++ b/_overviews/parallel-collections/configuration.md @@ -0,0 +1,86 @@ +--- +layout: multipage-overview +title: Configuring Parallel Collections + +discourse: true + +partof: parallel-collections +overview-name: Parallel Collections + +num: 2 + +languages: [ja, zh-cn, es, ru] +permalink: /overviews/parallel-collections/:title.html +--- + +## Task support + +Parallel collections are modular in the way operations are scheduled. Each +parallel collection is parametrized with a task support object which is +responsible for scheduling and load-balancing tasks to processors. + +The task support object internally keeps a reference to a thread pool +implementation and decides how and when tasks are split into smaller tasks. To +learn more about the internals of how exactly this is done, see the tech +report \[[1][1]\]. + +There are currently a few task support implementations available for parallel +collections. The `ForkJoinTaskSupport` uses a fork-join pool internally and is +used by default on JVM 1.6 or greater. The less efficient +`ThreadPoolTaskSupport` is a fallback for JVM 1.5 and JVMs that do not support +the fork join pools. The `ExecutionContextTaskSupport` uses the default +execution context implementation found in `scala.concurrent`, and it reuses +the thread pool used in `scala.concurrent` (this is either a fork join pool or +a thread pool executor, depending on the JVM version). The execution context +task support is set to each parallel collection by default, so parallel +collections reuse the same fork-join pool as the future API. + +Here is a way to change the task support of a parallel collection: + + scala> import scala.collection.parallel._ + import scala.collection.parallel._ + + scala> val pc = mutable.ParArray(1, 2, 3) + pc: scala.collection.parallel.mutable.ParArray[Int] = ParArray(1, 2, 3) + + scala> pc.tasksupport = new ForkJoinTaskSupport(new scala.concurrent.forkjoin.ForkJoinPool(2)) + pc.tasksupport: scala.collection.parallel.TaskSupport = scala.collection.parallel.ForkJoinTaskSupport@4a5d484a + + scala> pc map { _ + 1 } + res0: scala.collection.parallel.mutable.ParArray[Int] = ParArray(2, 3, 4) + +The above sets the parallel collection to use a fork-join pool with +parallelism level 2. To set the parallel collection to use a thread pool +executor: + + scala> pc.tasksupport = new ThreadPoolTaskSupport() + pc.tasksupport: scala.collection.parallel.TaskSupport = scala.collection.parallel.ThreadPoolTaskSupport@1d914a39 + + scala> pc map { _ + 1 } + res1: scala.collection.parallel.mutable.ParArray[Int] = ParArray(2, 3, 4) + +When a parallel collection is serialized, the task support field is omitted +from serialization. When deserializing a parallel collection, the task support +field is set to the default value-- the execution context task support. + +To implement a custom task support, extend the `TaskSupport` trait and +implement the following methods: + + def execute[R, Tp](task: Task[R, Tp]): () => R + + def executeAndWaitResult[R, Tp](task: Task[R, Tp]): R + + def parallelismLevel: Int + +The `execute` method schedules a task asynchronously and returns a future to +wait on the result of the computation. The `executeAndWait` method does the +same, but only returns when the task is completed. The `parallelismLevel` +simply returns the targeted number of cores that the task support uses to +schedule tasks. + + +## References + +1. [On a Generic Parallel Collection Framework, June 2011][1] + + [1]: http://infoscience.epfl.ch/record/165523/files/techrep.pdf "parallel-collections" diff --git a/_overviews/parallel-collections/conversions.md b/_overviews/parallel-collections/conversions.md new file mode 100644 index 0000000000..9997c5539a --- /dev/null +++ b/_overviews/parallel-collections/conversions.md @@ -0,0 +1,81 @@ +--- +layout: multipage-overview +title: Parallel Collection Conversions + +discourse: true + +partof: parallel-collections +overview-name: Parallel Collections + +num: 3 + +languages: [ja, zh-cn, es, ru] +permalink: /overviews/parallel-collections/:title.html +--- + +## Converting between sequential and parallel collections + +Every sequential collection can be converted to its parallel variant +using the `par` method. Certain sequential collections have a +direct parallel counterpart. For these collections the conversion is +efficient-- it occurs in constant time, since both the sequential and +the parallel collection have the same data-structural representation +(one exception is mutable hash maps and hash sets which are slightly +more expensive to convert the first time `par` is called, but +subsequent invocations of `par` take constant time). It should be +noted that for mutable collections, changes in the sequential collection are +visible in its parallel counterpart if they share the underlying data-structure. + +| Sequential | Parallel | +| ------------- | -------------- | +| **mutable** | | +| `Array` | `ParArray` | +| `HashMap` | `ParHashMap` | +| `HashSet` | `ParHashSet` | +| `TrieMap` | `ParTrieMap` | +| **immutable** | | +| `Vector` | `ParVector` | +| `Range` | `ParRange` | +| `HashMap` | `ParHashMap` | +| `HashSet` | `ParHashSet` | + +Other collections, such as lists, queues or streams, are inherently sequential +in the sense that the elements must be accessed one after the other. These +collections are converted to their parallel variants by copying the elements +into a similar parallel collection. For example, a functional list is +converted into a standard immutable parallel sequence, which is a parallel +vector. + +Every parallel collection can be converted to its sequential variant +using the `seq` method. Converting a parallel collection to a +sequential collection is always efficient-- it takes constant +time. Calling `seq` on a mutable parallel collection yields a +sequential collection which is backed by the same store-- updates to +one collection will be visible in the other one. + + +## Converting between different collection types + +Orthogonal to converting between sequential and parallel collections, +collections can be converted between different collection types. For +example, while calling `toSeq` converts a sequential set to a +sequential sequence, calling `toSeq` on a parallel set converts it to +a parallel sequence. The general rule is that if there is a +parallel version of `X`, then the `toX` method converts the collection +into a `ParX` collection. + +Here is a summary of all conversion methods: + +| Method | Return Type | +| -------------- | -------------- | +| `toArray` | `Array` | +| `toList` | `List` | +| `toIndexedSeq` | `IndexedSeq` | +| `toStream` | `Stream` | +| `toIterator` | `Iterator` | +| `toBuffer` | `Buffer` | +| `toTraversable`| `GenTraverable`| +| `toIterable` | `ParIterable` | +| `toSeq` | `ParSeq` | +| `toSet` | `ParSet` | +| `toMap` | `ParMap` | diff --git a/_overviews/parallel-collections/ctries.md b/_overviews/parallel-collections/ctries.md new file mode 100644 index 0000000000..c5c8e9e079 --- /dev/null +++ b/_overviews/parallel-collections/ctries.md @@ -0,0 +1,178 @@ +--- +layout: multipage-overview +title: Concurrent Tries + +discourse: true + +partof: parallel-collections +overview-name: Parallel Collections + +num: 4 + +languages: [ja, zh-cn, es, ru] +permalink: /overviews/parallel-collections/:title.html +--- + +Most concurrent data structures do not guarantee consistent +traversal if the data structure is modified during traversal. +This is, in fact, the case with most mutable collections, too. +Concurrent tries are special in the sense that they allow you to modify +the trie being traversed itself. The modifications are only visible in the +subsequent traversal. This holds both for sequential concurrent tries and their +parallel counterparts. The only difference between the two is that the +former traverses its elements sequentially, whereas the latter does so in +parallel. + +This is a nice property that allows you to write some algorithms more +easily. Typically, these are algorithms that process a dataset of elements +iteratively, in which different elements need a different number of +iterations to be processed. + +The following example computes the square roots of a set of numbers. Each iteration +iteratively updates the square root value. Numbers whose square roots converged +are removed from the map. + + case class Entry(num: Double) { + var sqrt = num + } + + val length = 50000 + + // prepare the list + val entries = (1 until length) map { num => Entry(num.toDouble) } + val results = ParTrieMap() + for (e <- entries) results += ((e.num, e)) + + // compute square roots + while (results.nonEmpty) { + for ((num, e) <- results) { + val nsqrt = 0.5 * (e.sqrt + e.num / e.sqrt) + if (math.abs(nsqrt - e.sqrt) < 0.01) { + results.remove(num) + } else e.sqrt = nsqrt + } + } + +Note that in the above Babylonian method square root computation +(\[[3][3]\]) some numbers may converge much faster than the others. For +this reason, we want to remove them from `results` so that only those +elements that need to be worked on are traversed. + +Another example is the breadth-first search algorithm, which iteratively expands the nodefront +until either it finds some path to the target or there are no more +nodes to expand. We define a node on a 2D map as a tuple of +`Int`s. We define the `map` as a 2D array of booleans which denote is +the respective slot occupied or not. We then declare 2 concurrent trie +maps-- `open` which contains all the nodes which have to be +expanded (the nodefront), and `closed` which contains all the nodes which have already +been expanded. We want to start the search from the corners of the map and +find a path to the center of the map-- we initialize the `open` map +with appropriate nodes. Then we iteratively expand all the nodes in +the `open` map in parallel until there are no more nodes to expand. +Each time a node is expanded, it is removed from the `open` map and +placed in the `closed` map. +Once done, we output the path from the target to the initial node. + + val length = 1000 + + // define the Node type + type Node = (Int, Int); + type Parent = (Int, Int); + + // operations on the Node type + def up(n: Node) = (n._1, n._2 - 1); + def down(n: Node) = (n._1, n._2 + 1); + def left(n: Node) = (n._1 - 1, n._2); + def right(n: Node) = (n._1 + 1, n._2); + + // create a map and a target + val target = (length / 2, length / 2); + val map = Array.tabulate(length, length)((x, y) => (x % 3) != 0 || (y % 3) != 0 || (x, y) == target) + def onMap(n: Node) = n._1 >= 0 && n._1 < length && n._2 >= 0 && n._2 < length + + // open list - the nodefront + // closed list - nodes already processed + val open = ParTrieMap[Node, Parent]() + val closed = ParTrieMap[Node, Parent]() + + // add a couple of starting positions + open((0, 0)) = null + open((length - 1, length - 1)) = null + open((0, length - 1)) = null + open((length - 1, 0)) = null + + // greedy bfs path search + while (open.nonEmpty && !open.contains(target)) { + for ((node, parent) <- open) { + def expand(next: Node) { + if (onMap(next) && map(next._1)(next._2) && !closed.contains(next) && !open.contains(next)) { + open(next) = node + } + } + expand(up(node)) + expand(down(node)) + expand(left(node)) + expand(right(node)) + closed(node) = parent + open.remove(node) + } + } + + // print path + var pathnode = open(target) + while (closed.contains(pathnode)) { + print(pathnode + "->") + pathnode = closed(pathnode) + } + println() + +There is a Game of Life example on GitHub which uses Ctries to +selectively simulate only those parts of the Game of Life automaton which +are currently active \[[4][4]\]. +It also includes a Swing-based visualization of the Game of Life simulation, +in which you can observe how tweaking the parameters affects performance. + +The concurrent tries also support a linearizable, lock-free, constant +time `snapshot` operation. This operation creates a new concurrent +trie with all the elements at a specific point in time, thus in effect +capturing the state of the trie at a specific point. +The `snapshot` operation merely creates +a new root for the concurrent trie. Subsequent updates lazily rebuild the part of +the concurrent trie relevant to the update and leave the rest of the concurrent trie +intact. First of all, this means that the snapshot operation by itself is not expensive +since it does not copy the elements. Second, since the copy-on-write optimization copies +only parts of the concurrent trie, subsequent modifications scale horizontally. +The `readOnlySnapshot` method is slightly more efficient than the +`snapshot` method, but returns a read-only map which cannot be +modified. Concurrent tries also support a linearizable, constant-time +`clear` operation based on the snapshot mechanism. +To learn more about how concurrent tries and snapshots work, see \[[1][1]\] and \[[2][2]\]. + +The iterators for concurrent tries are based on snapshots. Before the iterator +object gets created, a snapshot of the concurrent trie is taken, so the iterator +only traverses the elements in the trie at the time at which the snapshot was created. +Naturally, the iterators use the read-only snapshot. + +The `size` operation is also based on the snapshot. A straightforward implementation, the `size` +call would just create an iterator (i.e. a snapshot) and traverse the elements to count them. +Every call to `size` would thus require time linear in the number of elements. However, concurrent +tries have been optimized to cache sizes of their different parts, thus reducing the complexity +of the `size` method to amortized logarithmic time. In effect, this means that after calling +`size` once, subsequent calls to `size` will require a minimum amount of work, typically recomputing +the size only for those branches of the trie which have been modified since the last `size` call. +Additionally, size computation for parallel concurrent tries is performed in parallel. + + + + +## References + +1. [Cache-Aware Lock-Free Concurrent Hash Tries][1] +2. [Concurrent Tries with Efficient Non-Blocking Snapshots][2] +3. [Methods of computing square roots][3] +4. [Game of Life simulation][4] + + [1]: http://infoscience.epfl.ch/record/166908/files/ctries-techreport.pdf "Ctries-techreport" + [2]: http://lampwww.epfl.ch/~prokopec/ctries-snapshot.pdf "Ctries-snapshot" + [3]: http://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method "babylonian-method" + [4]: https://github.com/axel22/ScalaDays2012-TrieMap "game-of-life-ctries" diff --git a/_overviews/parallel-collections/custom-parallel-collections.md b/_overviews/parallel-collections/custom-parallel-collections.md new file mode 100644 index 0000000000..3d3a958de2 --- /dev/null +++ b/_overviews/parallel-collections/custom-parallel-collections.md @@ -0,0 +1,329 @@ +--- +layout: multipage-overview +title: Creating Custom Parallel Collections + +discourse: true + +partof: parallel-collections +overview-name: Parallel Collections + +num: 6 + +languages: [ja, zh-cn, es, ru] +permalink: /overviews/parallel-collections/:title.html +--- + +## Parallel collections without combiners + +Just as it is possible to define custom sequential collections without +defining their builders, it is possible to define parallel collections without +defining their combiners. The consequence of not having a combiner is that +transformer methods (e.g. `map`, `flatMap`, `collect`, `filter`, ...) will by +default return a standard collection type which is nearest in the hierarchy. +For example, ranges do not have builders, so mapping elements of a range +creates a vector. + +In the following example we define a parallel string collection. Since strings +are logically immutable sequences, we have parallel strings inherit +`immutable.ParSeq[Char]`: + + class ParString(val str: String) + extends immutable.ParSeq[Char] { + +Next, we define methods found in every immutable sequence: + + def apply(i: Int) = str.charAt(i) + + def length = str.length + +We have to also define the sequential counterpart of this parallel collection. +In this case, we return the `WrappedString` class: + + def seq = new collection.immutable.WrappedString(str) + +Finally, we have to define a splitter for our parallel string collection. We +name the splitter `ParStringSplitter` and have it inherit a sequence splitter, +that is, `SeqSplitter[Char]`: + + def splitter = new ParStringSplitter(str, 0, str.length) + + class ParStringSplitter(private var s: String, private var i: Int, private val ntl: Int) + extends SeqSplitter[Char] { + + final def hasNext = i < ntl + + final def next = { + val r = s.charAt(i) + i += 1 + r + } + +Above, `ntl` represents the total length of the string, `i` is the current +position and `s` is the string itself. + +Parallel collection iterators or splitters require a few more methods in +addition to `next` and `hasNext` found in sequential collection iterators. +First of all, they have a method called `remaining` which returns the number +of elements this splitter has yet to traverse. Next, they have a method called +`dup` which duplicates the current splitter. + + def remaining = ntl - i + + def dup = new ParStringSplitter(s, i, ntl) + +Finally, methods `split` and `psplit` are used to create splitters which +traverse subsets of the elements of the current splitter. Method `split` has +the contract that it returns a sequence of splitters which traverse disjoint, +non-overlapping subsets of elements that the current splitter traverses, none +of which is empty. If the current splitter has 1 or less elements, then +`split` just returns a sequence of this splitter. Method `psplit` has to +return a sequence of splitters which traverse exactly as many elements as +specified by the `sizes` parameter. If the `sizes` parameter specifies less +elements than the current splitter, then an additional splitter with the rest +of the elements is appended at the end. If the `sizes` parameter requires more +elements than there are remaining in the current splitter, it will append an +empty splitter for each size. Finally, calling either `split` or `psplit` +invalidates the current splitter. + + def split = { + val rem = remaining + if (rem >= 2) psplit(rem / 2, rem - rem / 2) + else Seq(this) + } + + def psplit(sizes: Int*): Seq[ParStringSplitter] = { + val splitted = new ArrayBuffer[ParStringSplitter] + for (sz <- sizes) { + val next = (i + sz) min ntl + splitted += new ParStringSplitter(s, i, next) + i = next + } + if (remaining > 0) splitted += new ParStringSplitter(s, i, ntl) + splitted + } + } + } + +Above, `split` is implemented in terms of `psplit`, which is often the case +with parallel sequences. Implementing a splitter for parallel maps, sets or +iterables is often easier, since it does not require `psplit`. + +Thus, we obtain a parallel string class. The only downside is that calling transformer methods +such as `filter` will not produce a parallel string, but a parallel vector instead, which +may be suboptimal - producing a string again from the vector after filtering may be costly. + + +## Parallel collections with combiners + +Lets say we want to `filter` the characters of the parallel string, to get rid +of commas for example. As noted above, calling `filter` produces a parallel +vector and we want to obtain a parallel string (since some interface in the +API might require a sequential string). + +To avoid this, we have to write a combiner for the parallel string collection. +We will also inherit the `ParSeqLike` trait this time to ensure that return +type of `filter` is more specific - a `ParString` instead of a `ParSeq[Char]`. +The `ParSeqLike` has a third type parameter which specifies the type of the +sequential counterpart of the parallel collection (unlike sequential `*Like` +traits which have only two type parameters). + + class ParString(val str: String) + extends immutable.ParSeq[Char] + with ParSeqLike[Char, ParString, collection.immutable.WrappedString] + +All the methods remain the same as before, but we add an additional protected method `newCombiner` which +is internally used by `filter`. + + protected[this] override def newCombiner: Combiner[Char, ParString] = new ParStringCombiner + +Next we define the `ParStringCombiner` class. Combiners are subtypes of +builders and they introduce an additional method called `combine`, which takes +another combiner as an argument and returns a new combiner which contains the +elements of both the current and the argument combiner. The current and the +argument combiner are invalidated after calling `combine`. If the argument is +the same object as the current combiner, then `combine` just returns the +current combiner. This method is expected to be efficient, having logarithmic +running time with respect to the number of elements in the worst case, since +it is called multiple times during a parallel computation. + +Our `ParStringCombiner` will internally maintain a sequence of string +builders. It will implement `+=` by adding an element to the last string +builder in the sequence, and `combine` by concatenating the lists of string +builders of the current and the argument combiner. The `result` method, which +is called at the end of the parallel computation, will produce a parallel +string by appending all the string builders together. This way, elements are +copied only once at the end instead of being copied every time `combine` is +called. Ideally, we would like to parallelize this process and copy them in +parallel (this is being done for parallel arrays), but without tapping into +the internal representation of strings this is the best we can do-- we have to +live with this sequential bottleneck. + + private class ParStringCombiner extends Combiner[Char, ParString] { + var sz = 0 + val chunks = new ArrayBuffer[StringBuilder] += new StringBuilder + var lastc = chunks.last + + def size: Int = sz + + def +=(elem: Char): this.type = { + lastc += elem + sz += 1 + this + } + + def clear = { + chunks.clear + chunks += new StringBuilder + lastc = chunks.last + sz = 0 + } + + def result: ParString = { + val rsb = new StringBuilder + for (sb <- chunks) rsb.append(sb) + new ParString(rsb.toString) + } + + def combine[U <: Char, NewTo >: ParString](other: Combiner[U, NewTo]) = if (other eq this) this else { + val that = other.asInstanceOf[ParStringCombiner] + sz += that.sz + chunks ++= that.chunks + lastc = chunks.last + this + } + } + + +## How do I implement my combiner in general? + +There are no predefined recipes-- it depends on the data-structure at +hand, and usually requires a bit of ingenuity on the implementer's +part. However there are a few approaches usually taken: + +1. Concatenation and merge. Some data-structures have efficient +implementations (usually logarithmic) of these operations. +If the collection at hand is backed by such a data-structure, +its combiner can be the collection itself. Finger trees, +ropes and various heaps are particularly suitable for such an approach. + +2. Two-phase evaluation. An approach taken in parallel arrays and +parallel hash tables, it assumes the elements can be efficiently +partially sorted into concatenable buckets from which the final +data-structure can be constructed in parallel. In the first phase +different processors populate these buckets independently and +concatenate the buckets together. In the second phase, the data +structure is allocated and different processors populate different +parts of the data structure in parallel using elements from disjoint +buckets. +Care must be taken that different processors never modify the same +part of the data structure, otherwise subtle concurrency errors may occur. +This approach is easily applicable to random access sequences, as we +have shown in the previous section. + +3. A concurrent data-structure. While the last two approaches actually +do not require any synchronization primitives in the data-structure +itself, they assume that it can be constructed concurrently in a way +such that two different processors never modify the same memory +location. There exists a large number of concurrent data-structures +that can be modified safely by multiple processors-- concurrent skip lists, +concurrent hash tables, split-ordered lists, concurrent avl trees, to +name a few. +An important consideration in this case is that the concurrent +data-structure has a horizontally scalable insertion method. +For concurrent parallel collections the combiner can be the collection +itself, and a single combiner instance is shared between all the +processors performing a parallel operation. + + +## Integration with the collections framework + +Our `ParString` class is not complete yet. Although we have implemented a +custom combiner which will be used by methods such as `filter`, `partition`, +`takeWhile` or `span`, most transformer methods require an implicit +`CanBuildFrom` evidence (see Scala collections guide for a full explanation). +To make it available and completely integrate `ParString` with the collections +framework, we have to mix an additional trait called `GenericParTemplate` and +define the companion object of `ParString`. + + class ParString(val str: String) + extends immutable.ParSeq[Char] + with GenericParTemplate[Char, ParString] + with ParSeqLike[Char, ParString, collection.immutable.WrappedString] { + + def companion = ParString + +Inside the companion object we provide an implicit evidence for the `CanBuildFrom` parameter. + + object ParString { + implicit def canBuildFrom: CanCombineFrom[ParString, Char, ParString] = + new CanCombinerFrom[ParString, Char, ParString] { + def apply(from: ParString) = newCombiner + def apply() = newCombiner + } + + def newBuilder: Combiner[Char, ParString] = newCombiner + + def newCombiner: Combiner[Char, ParString] = new ParStringCombiner + + def apply(elems: Char*): ParString = { + val cb = newCombiner + cb ++= elems + cb.result + } + } + + + +## Further customizations-- concurrent and other collections + +Implementing a concurrent collection (unlike parallel collections, concurrent +collections are ones that can be concurrently modified, like +`collection.concurrent.TrieMap`) is not always straightforward. Combiners in +particular often require a lot of thought. In most _parallel_ collections +described so far, combiners use a two-step evaluation. In the first step the +elements are added to the combiners by different processors and the combiners +are merged together. In the second step, after all the elements are available, +the resulting collection is constructed. + +Another approach to combiners is to construct the resulting collection as the +elements. This requires the collection to be thread-safe-- a combiner must +allow _concurrent_ element insertion. In this case one combiner is shared by +all the processors. + +To parallelize a concurrent collection, its combiners must override the method +`canBeShared` to return `true`. This will ensure that only one combiner is +created when a parallel operation is invoked. Next, the `+=` method must be +thread-safe. Finally, method `combine` still returns the current combiner if +the current combiner and the argument combiner are the same, and is free to +throw an exception otherwise. + +Splitters are divided into smaller splitters to achieve better load balancing. +By default, information returned by the `remaining` method is used to decide +when to stop dividing the splitter. For some collections, calling the +`remaining` method may be costly and some other means should be used to decide +when to divide the splitter. In this case, one should override the +`shouldSplitFurther` method in the splitter. + +The default implementation divides the splitter if the number of remaining +elements is greater than the collection size divided by eight times the +parallelism level. + + def shouldSplitFurther[S](coll: ParIterable[S], parallelismLevel: Int) = + remaining > thresholdFromSize(coll.size, parallelismLevel) + +Equivalently, a splitter can hold a counter on how many times it was split and +implement `shouldSplitFurther` by returning `true` if the split count is +greater than `3 + log(parallelismLevel)`. This avoids having to call +`remaining`. + +Furthermore, if calling `remaining` is not a cheap operation for a particular +collection (i.e. it requires evaluating the number of elements in the +collection), then the method `isRemainingCheap` in splitters should be +overridden to return `false`. + +Finally, if the `remaining` method in splitters is extremely cumbersome to +implement, you can override the method `isStrictSplitterCollection` in its +collection to return `false`. Such collections will fail to execute some +methods which rely on splitters being strict, i.e. returning a correct value +in the `remaining` method. Importantly, this does not effect methods used in +for-comprehensions. diff --git a/_overviews/parallel-collections/overview.md b/_overviews/parallel-collections/overview.md new file mode 100644 index 0000000000..d3ac71baeb --- /dev/null +++ b/_overviews/parallel-collections/overview.md @@ -0,0 +1,277 @@ +--- +layout: multipage-overview +title: Overview + +discourse: true + +partof: parallel-collections +overview-name: Parallel Collections + +num: 1 + +languages: [ja, zh-cn, es, ru] +permalink: /overviews/parallel-collections/:title.html +--- + +**Aleksandar Prokopec, Heather Miller** + +## Motivation + +Amidst the shift in recent years by processor manufacturers from single to +multi-core architectures, academia and industry alike have conceded that +_Popular Parallel Programming_ remains a formidable challenge. + +Parallel collections were included in the Scala standard library in an effort +to facilitate parallel programming by sparing users from low-level +parallelization details, meanwhile providing them with a familiar and simple +high-level abstraction. The hope was, and still is, that implicit parallelism +behind a collections abstraction will bring reliable parallel execution one +step closer to the workflow of mainstream developers. + +The idea is simple-- collections are a well-understood and frequently-used +programming abstraction. And given their regularity, they're able to be +efficiently parallelized, transparently. By allowing a user to "swap out" +sequential collections for ones that are operated on in parallel, Scala's +parallel collections take a large step forward in enabling parallelism to be +easily brought into more code. + +Take the following, sequential example, where we perform a monadic operation +on some large collection: + + val list = (1 to 10000).toList + list.map(_ + 42) + +To perform the same operation in parallel, one must simply invoke the `par` +method on the sequential collection, `list`. After that, one can use a +parallel collection in the same way one would normally use a sequential +collection. The above example can be parallelized by simply doing the +following: + + list.par.map(_ + 42) + +The design of Scala's parallel collections library is inspired by and deeply +integrated with Scala's (sequential) collections library (introduced in 2.8). +It provides a parallel counterpart to a number of important data structures +from Scala's (sequential) collection library, including: + +* `ParArray` +* `ParVector` +* `mutable.ParHashMap` +* `mutable.ParHashSet` +* `immutable.ParHashMap` +* `immutable.ParHashSet` +* `ParRange` +* `ParTrieMap` (`collection.concurrent.TrieMap`s are new in 2.10) + +In addition to a common architecture, Scala's parallel collections library +additionally shares _extensibility_ with the sequential collections library. +That is, like normal sequential collections, users can integrate their own +collection types and automatically inherit all of the predefined (parallel) +operations available on the other parallel collections in the standard +library. + +## Some Examples + +To attempt to illustrate the generality and utility of parallel collections, +we provide a handful of simple example usages, all of which are transparently +executed in parallel. + +_Note:_ Some of the following examples operate on small collections, which +isn't recommended. They're provided as examples for illustrative purposes +only. As a general heuristic, speed-ups tend to be noticeable when the size of +the collection is large, typically several thousand elements. (For more +information on the relationship between the size of a parallel collection and +performance, please see the +[appropriate subsection]({{ site.baseurl}}/overviews/parallel-collections/performance.html#how_big_should_a_collection_be_to_go_parallel) of the [performance]({{ site.baseurl }}/overviews/parallel-collections/performance.html) +section of this guide.) + +#### map + +Using a parallel `map` to transform a collection of `String` to all-uppercase: + + scala> val lastNames = List("Smith","Jones","Frankenstein","Bach","Jackson","Rodin").par + lastNames: scala.collection.parallel.immutable.ParSeq[String] = ParVector(Smith, Jones, Frankenstein, Bach, Jackson, Rodin) + + scala> lastNames.map(_.toUpperCase) + res0: scala.collection.parallel.immutable.ParSeq[String] = ParVector(SMITH, JONES, FRANKENSTEIN, BACH, JACKSON, RODIN) + +#### fold + +Summing via `fold` on a `ParArray`: + + scala> val parArray = (1 to 10000).toArray.par + parArray: scala.collection.parallel.mutable.ParArray[Int] = ParArray(1, 2, 3, ... + + scala> parArray.fold(0)(_ + _) + res0: Int = 50005000 + +#### filter + +Using a parallel `filter` to select the last names that come alphabetically +after the letter "I". + + scala> val lastNames = List("Smith","Jones","Frankenstein","Bach","Jackson","Rodin").par + lastNames: scala.collection.parallel.immutable.ParSeq[String] = ParVector(Smith, Jones, Frankenstein, Bach, Jackson, Rodin) + + scala> lastNames.filter(_.head >= 'J') + res0: scala.collection.parallel.immutable.ParSeq[String] = ParVector(Smith, Jones, Jackson, Rodin) + +## Creating a Parallel Collection + +Parallel collections are meant to be used in exactly the same way as +sequential collections-- the only noteworthy difference is how to _obtain_ a +parallel collection. + +Generally, one has two choices for creating a parallel collection: + +First, by using the `new` keyword and a proper import statement: + + import scala.collection.parallel.immutable.ParVector + val pv = new ParVector[Int] + +Second, by _converting_ from a sequential collection: + + val pv = Vector(1,2,3,4,5,6,7,8,9).par + +What's important to expand upon here are these conversion methods-- sequential +collections can be converted to parallel collections by invoking the +sequential collection's `par` method, and likewise, parallel collections can +be converted to sequential collections by invoking the parallel collection's +`seq` method. + +_Of Note:_ Collections that are inherently sequential (in the sense that the +elements must be accessed one after the other), like lists, queues, and +streams, are converted to their parallel counterparts by copying the elements +into a similar parallel collection. An example is `List`-- it's converted into +a standard immutable parallel sequence, which is a `ParVector`. Of course, the +copying required for these collection types introduces an overhead not +incurred by any other collection types, like `Array`, `Vector`, `HashMap`, etc. + +For more information on conversions on parallel collections, see the +[conversions]({{ site.baseurl }}/overviews/parallel-collections/conversions.html) +and [concrete parallel collection classes]({{ site.baseurl }}/overviews/parallel-collections/concrete-parallel-collections.html) +sections of this guide. + +## Semantics + +While the parallel collections abstraction feels very much the same as normal +sequential collections, it's important to note that its semantics differs, +especially with regards to side-effects and non-associative operations. + +In order to see how this is the case, first, we visualize _how_ operations are +performed in parallel. Conceptually, Scala's parallel collections framework +parallelizes an operation on a parallel collection by recursively "splitting" +a given collection, applying an operation on each partition of the collection +in parallel, and re-"combining" all of the results that were completed in +parallel. + +These concurrent, and "out-of-order" semantics of parallel collections lead to +the following two implications: + +1. **Side-effecting operations can lead to non-determinism** +2. **Non-associative operations lead to non-determinism** + +### Side-Effecting Operations + +Given the _concurrent_ execution semantics of the parallel collections +framework, operations performed on a collection which cause side-effects +should generally be avoided, in order to maintain determinism. A simple +example is by using an accessor method, like `foreach` to increment a `var` +declared outside of the closure which is passed to `foreach`. + + scala> var sum = 0 + sum: Int = 0 + + scala> val list = (1 to 1000).toList.par + list: scala.collection.parallel.immutable.ParSeq[Int] = ParVector(1, 2, 3,… + + scala> list.foreach(sum += _); sum + res01: Int = 467766 + + scala> var sum = 0 + sum: Int = 0 + + scala> list.foreach(sum += _); sum + res02: Int = 457073 + + scala> var sum = 0 + sum: Int = 0 + + scala> list.foreach(sum += _); sum + res03: Int = 468520 + +Here, we can see that each time `sum` is reinitialized to 0, and `foreach` is +called again on `list`, `sum` holds a different value. The source of this +non-determinism is a _data race_-- concurrent reads/writes to the same mutable +variable. + +In the above example, it's possible for two threads to read the _same_ value +in `sum`, to spend some time doing some operation on that value of `sum`, and +then to attempt to write a new value to `sum`, potentially resulting in an +overwrite (and thus, loss) of a valuable result, as illustrated below: + + ThreadA: read value in sum, sum = 0 value in sum: 0 + ThreadB: read value in sum, sum = 0 value in sum: 0 + ThreadA: increment sum by 760, write sum = 760 value in sum: 760 + ThreadB: increment sum by 12, write sum = 12 value in sum: 12 + +The above example illustrates a scenario where two threads read the same +value, `0`, before one or the other can sum `0` with an element from their +partition of the parallel collection. In this case, `ThreadA` reads `0` and +sums it with its element, `0+760`, and in the case of `ThreadB`, sums `0` with +its element, `0+12`. After computing their respective sums, they each write +their computed value in `sum`. Since `ThreadA` beats `ThreadB`, it writes +first, only for the value in `sum` to be overwritten shortly after by +`ThreadB`, in effect completely overwriting (and thus losing) the value `760`. + +### Non-Associative Operations + +Given this _"out-of-order"_ semantics, also must be careful to perform only +associative operations in order to avoid non-determinism. That is, given a +parallel collection, `pcoll`, one should be sure that when invoking a +higher-order function on `pcoll`, such as `pcoll.reduce(func)`, the order in +which `func` is applied to the elements of `pcoll` can be arbitrary. A simple, +but obvious example is a non-associative operation such as subtraction: + + scala> val list = (1 to 1000).toList.par + list: scala.collection.parallel.immutable.ParSeq[Int] = ParVector(1, 2, 3,… + + scala> list.reduce(_-_) + res01: Int = -228888 + + scala> list.reduce(_-_) + res02: Int = -61000 + + scala> list.reduce(_-_) + res03: Int = -331818 + +In the above example, we take a `ParVector[Int]`, invoke `reduce`, and pass to +it `_-_`, which simply takes two unnamed elements, and subtracts the first +from the second. Due to the fact that the parallel collections framework spawns +threads which, in effect, independently perform `reduce(_-_)` on different +sections of the collection, the result of two runs of `reduce(_-_)` on the +same collection will not be the same. + +_Note:_ Often, it is thought that, like non-associative operations, non-commutative +operations passed to a higher-order function on a parallel +collection likewise result in non-deterministic behavior. This is not the +case, a simple example is string concatenation-- an associative, but non- +commutative operation: + + scala> val strings = List("abc","def","ghi","jk","lmnop","qrs","tuv","wx","yz").par + strings: scala.collection.parallel.immutable.ParSeq[java.lang.String] = ParVector(abc, def, ghi, jk, lmnop, qrs, tuv, wx, yz) + + scala> val alphabet = strings.reduce(_++_) + alphabet: java.lang.String = abcdefghijklmnopqrstuvwxyz + +The _"out of order"_ semantics of parallel collections only means that +the operation will be executed out of order (in a _temporal_ sense. That is, +non-sequentially), it does not mean that the result will be +re-"*combined*" out of order (in a _spatial_ sense). On the contrary, results +will generally always be reassembled _in order_-- that is, a parallel collection +broken into partitions A, B, C, in that order, will be reassembled once again +in the order A, B, C. Not some other arbitrary order like B, C, A. + +For more on how parallel collections split and combine operations on different +parallel collection types, see the [Architecture]({{ site.baseurl }}/overviews +/parallel-collections/architecture.html) section of this guide. diff --git a/_overviews/parallel-collections/performance.md b/_overviews/parallel-collections/performance.md new file mode 100644 index 0000000000..0a67799735 --- /dev/null +++ b/_overviews/parallel-collections/performance.md @@ -0,0 +1,284 @@ +--- +layout: multipage-overview +title: Measuring Performance + +discourse: true + +partof: parallel-collections +overview-name: Parallel Collections + +num: 8 + +languages: [ja, zh-cn, es, ru] +permalink: /overviews/parallel-collections/:title.html +--- + +## Performance on the JVM + +The performance model on the JVM is sometimes convoluted in commentaries about +it, and as a result is not well understood. For various reasons, some code may +not be as performant or as scalable as expected. Here, we provide a few +examples. + +One of the reasons is that the compilation process for a JVM application is +not the same as that of a statically compiled language (see \[[2][2]\]). The +Java and Scala compilers convert source code into JVM bytecode and do very +little optimization. On most modern JVMs, once the program bytecode is run, it +is converted into machine code for the computer architecture on which it is +being run. This is called the just-in-time compilation. The level of code +optimization is, however, low with just-in-time compilation, since it has to +be fast. To avoid recompiling, the so called HotSpot compiler only optimizes +parts of the code which are executed frequently. What this means for the +benchmark writer is that a program might have different performance each time +it is run. Executing the same piece of code (e.g. a method) multiple times in +the same JVM instance might give very different performance results depending +on whether the particular code was optimized in between the runs. +Additionally, measuring the execution time of some piece of code may include +the time during which the JIT compiler itself was performing the optimization, +thus giving inconsistent results. + +Another hidden execution that takes part on the JVM is the automatic memory +management. Every once in a while, the execution of the program is stopped and +a garbage collector is run. If the program being benchmarked allocates any +heap memory at all (and most JVM programs do), the garbage collector will have +to run, thus possibly distorting the measurement. To amortize the garbage +collection effects, the measured program should run many times to trigger many +garbage collections. + +One common cause of a performance deterioration is also boxing and unboxing +that happens implicitly when passing a primitive type as an argument to a +generic method. At runtime, primitive types are converted to objects which +represent them, so that they could be passed to a method with a generic type +parameter. This induces extra allocations and is slower, also producing +additional garbage on the heap. + +Where parallel performance is concerned, one common issue is memory +contention, as the programmer does not have explicit control about where the +objects are allocated. +In fact, due to GC effects, contention can occur at a later stage in +the application lifetime after objects get moved around in memory. +Such effects need to be taken into consideration when writing a benchmark. + + +## Microbenchmarking example + +There are several approaches to avoid the above effects during measurement. +First of all, the target microbenchmark must be executed enough times to make +sure that the just-in-time compiler compiled it to machine code and that it +was optimized. This is known as the warm-up phase. + +The microbenchmark itself should be run in a separate JVM instance to reduce +noise coming from garbage collection of the objects allocated by different +parts of the program or unrelated just-in-time compilation. + +It should be run using the server version of the HotSpot JVM, which does more +aggressive optimizations. + +Finally, to reduce the chance of a garbage collection occurring in the middle +of the benchmark, ideally a garbage collection cycle should occur prior to the +run of the benchmark, postponing the next cycle as far as possible. + +The `scala.testing.Benchmark` trait is predefined in the Scala standard +library and is designed with above in mind. Here is an example of benchmarking +a map operation on a concurrent trie: + + import collection.parallel.mutable.ParTrieMap + import collection.parallel.ForkJoinTaskSupport + + object Map extends testing.Benchmark { + val length = sys.props("length").toInt + val par = sys.props("par").toInt + val partrie = ParTrieMap((0 until length) zip (0 until length): _*) + + partrie.tasksupport = new ForkJoinTaskSupport(new scala.concurrent.forkjoin.ForkJoinPool(par)) + + def run = { + partrie map { + kv => kv + } + } + } + +The `run` method embodies the microbenchmark code which will be run +repetitively and whose running time will be measured. The object `Map` above +extends the `scala.testing.Benchmark` trait and parses system specified +parameters `par` for the parallelism level and `length` for the number of +elements in the trie. + +After compiling the program above, run it like this: + + java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=1 -Dlength=300000 Map 10 + +The `server` flag specifies that the server VM should be used. The `cp` +specifies the classpath and includes classfiles in the current directory and +the scala library jar. Arguments `-Dpar` and `-Dlength` are the parallelism +level and the number of elements. Finally, `10` means that the benchmark +should be run that many times within the same JVM. + +Running times obtained by setting the `par` to `1`, `2`, `4` and `8` on a +quad-core i7 with hyperthreading: + + Map$ 126 57 56 57 54 54 54 53 53 53 + Map$ 90 99 28 28 26 26 26 26 26 26 + Map$ 201 17 17 16 15 15 16 14 18 15 + Map$ 182 12 13 17 16 14 14 12 12 12 + +We can see above that the running time is higher during the initial runs, but +is reduced after the code gets optimized. Further, we can see that the benefit +of hyperthreading is not high in this example, as going from `4` to `8` +threads results only in a minor performance improvement. + + +## How big should a collection be to go parallel? + +This is a question commonly asked. The answer is somewhat involved. + +The size of the collection at which the parallelization pays of really +depends on many factors. Some of them, but not all, include: + +- Machine architecture. Different CPU types have different + performance and scalability characteristics. Orthogonal to that, + whether the machine is multicore or has multiple processors + communicating via motherboard. +- JVM vendor and version. Different VMs apply different + optimizations to the code at runtime. They implement different memory + management and synchronization techniques. Some do not support + `ForkJoinPool`, reverting to `ThreadPoolExecutor`s, resulting in + more overhead. +- Per-element workload. A function or a predicate for a parallel + operation determines how big is the per-element workload. The + smaller the workload, the higher the number of elements needed to + gain speedups when running in parallel. +- Specific collection. For example, `ParArray` and + `ParTrieMap` have splitters that traverse the collection at + different speeds, meaning there is more per-element work in just the + traversal itself. +- Specific operation. For example, `ParVector` is a lot slower for + transformer methods (like `filter`) than it is for accessor methods (like `foreach`) +- Side-effects. When modifying memory areas concurrently or using + synchronization within the body of `foreach`, `map`, etc., + contention can occur. +- Memory management. When allocating a lot of objects a garbage + collection cycle can be triggered. Depending on how the references + to new objects are passed around, the GC cycle can take more or less time. + +Even in separation, it is not easy to reason about things above and +give a precise answer to what the collection size should be. To +roughly illustrate what the size should be, we give an example of +a cheap side-effect-free parallel vector reduce (in this case, sum) +operation performance on an i7 quad-core processor (not using +hyperthreading) on JDK7: + + import collection.parallel.immutable.ParVector + + object Reduce extends testing.Benchmark { + val length = sys.props("length").toInt + val par = sys.props("par").toInt + val parvector = ParVector((0 until length): _*) + + parvector.tasksupport = new collection.parallel.ForkJoinTaskSupport(new scala.concurrent.forkjoin.ForkJoinPool(par)) + + def run = { + parvector reduce { + (a, b) => a + b + } + } + } + + object ReduceSeq extends testing.Benchmark { + val length = sys.props("length").toInt + val vector = collection.immutable.Vector((0 until length): _*) + + def run = { + vector reduce { + (a, b) => a + b + } + } + } + +We first run the benchmark with `250000` elements and obtain the +following results, for `1`, `2` and `4` threads: + + java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=1 -Dlength=250000 Reduce 10 10 + Reduce$ 54 24 18 18 18 19 19 18 19 19 + java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=2 -Dlength=250000 Reduce 10 10 + Reduce$ 60 19 17 13 13 13 13 14 12 13 + java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=4 -Dlength=250000 Reduce 10 10 + Reduce$ 62 17 15 14 13 11 11 11 11 9 + +We then decrease the number of elements down to `120000` and use `4` +threads to compare the time to that of a sequential vector reduce: + + java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=4 -Dlength=120000 Reduce 10 10 + Reduce$ 54 10 8 8 8 7 8 7 6 5 + java -server -cp .:../../build/pack/lib/scala-library.jar -Dlength=120000 ReduceSeq 10 10 + ReduceSeq$ 31 7 8 8 7 7 7 8 7 8 + +`120000` elements seems to be the around the threshold in this case. + +As another example, we take the `mutable.ParHashMap` and the `map` +method (a transformer method) and run the following benchmark in the same environment: + + import collection.parallel.mutable.ParHashMap + + object Map extends testing.Benchmark { + val length = sys.props("length").toInt + val par = sys.props("par").toInt + val phm = ParHashMap((0 until length) zip (0 until length): _*) + + phm.tasksupport = new collection.parallel.ForkJoinTaskSupport(new scala.concurrent.forkjoin.ForkJoinPool(par)) + + def run = { + phm map { + kv => kv + } + } + } + + object MapSeq extends testing.Benchmark { + val length = sys.props("length").toInt + val hm = collection.mutable.HashMap((0 until length) zip (0 until length): _*) + + def run = { + hm map { + kv => kv + } + } + } + +For `120000` elements we get the following times when ranging the +number of threads from `1` to `4`: + + java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=1 -Dlength=120000 Map 10 10 + Map$ 187 108 97 96 96 95 95 95 96 95 + java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=2 -Dlength=120000 Map 10 10 + Map$ 138 68 57 56 57 56 56 55 54 55 + java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=4 -Dlength=120000 Map 10 10 + Map$ 124 54 42 40 38 41 40 40 39 39 + +Now, if we reduce the number of elements to `15000` and compare that +to the sequential hashmap: + + java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=1 -Dlength=15000 Map 10 10 + Map$ 41 13 10 10 10 9 9 9 10 9 + java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=2 -Dlength=15000 Map 10 10 + Map$ 48 15 9 8 7 7 6 7 8 6 + java -server -cp .:../../build/pack/lib/scala-library.jar -Dlength=15000 MapSeq 10 10 + MapSeq$ 39 9 9 9 8 9 9 9 9 9 + +For this collection and this operation it makes sense +to go parallel when there are above `15000` elements (in general, +it is feasible to parallelize hashmaps and hashsets with fewer +elements than would be required for arrays or vectors). + + + + + +## References + +1. [Anatomy of a flawed microbenchmark, Brian Goetz][1] +2. [Dynamic compilation and performance measurement, Brian Goetz][2] + + [1]: http://www.ibm.com/developerworks/java/library/j-jtp02225/index.html "flawed-benchmark" + [2]: http://www.ibm.com/developerworks/library/j-jtp12214/ "dynamic-compilation" diff --git a/overviews/quasiquotes/definition-details.md b/_overviews/quasiquotes/definition-details.md similarity index 98% rename from overviews/quasiquotes/definition-details.md rename to _overviews/quasiquotes/definition-details.md index de2f333ae3..a74ec7e17b 100644 --- a/overviews/quasiquotes/definition-details.md +++ b/_overviews/quasiquotes/definition-details.md @@ -1,14 +1,16 @@ --- -layout: overview-large +layout: multipage-overview title: Definition and import details discourse: true partof: quasiquotes +overview-name: Quasiquotes + num: 11 -outof: 13 +permalink: /overviews/quasiquotes/:title.html --- -**Denys Shabalin** EXPERIMENTAL +**Denys Shabalin** EXPERIMENTAL ## Modifiers diff --git a/overviews/quasiquotes/expression-details.md b/_overviews/quasiquotes/expression-details.md similarity index 99% rename from overviews/quasiquotes/expression-details.md rename to _overviews/quasiquotes/expression-details.md index 4a97ac427f..35af8c09f8 100644 --- a/overviews/quasiquotes/expression-details.md +++ b/_overviews/quasiquotes/expression-details.md @@ -1,14 +1,17 @@ --- -layout: overview-large +layout: multipage-overview title: Expression details discourse: true partof: quasiquotes +overview-name: Quasiquotes + num: 8 -outof: 13 + +permalink: /overviews/quasiquotes/:title.html --- -**Denys Shabalin** EXPERIMENTAL +**Denys Shabalin** EXPERIMENTAL ## Empty diff --git a/_overviews/quasiquotes/future.md b/_overviews/quasiquotes/future.md new file mode 100644 index 0000000000..64f89f6294 --- /dev/null +++ b/_overviews/quasiquotes/future.md @@ -0,0 +1,20 @@ +--- +layout: multipage-overview +title: Future prospects + +discourse: true + +partof: quasiquotes +overview-name: Quasiquotes + +num: 13 + +permalink: /overviews/quasiquotes/:title.html +--- +**Denys Shabalin** EXPERIMENTAL + +In the [scala.meta](http://scalameta.org) project we are working on the following features that target future versions of Scala: + +* Hygiene: [SI-7823](https://issues.scala-lang.org/browse/SI-7823) +* Alternative to Scheme's ellipsis: [SI-8164](https://issues.scala-lang.org/browse/SI-8164) +* Safety by construction diff --git a/overviews/quasiquotes/hygiene.md b/_overviews/quasiquotes/hygiene.md similarity index 97% rename from overviews/quasiquotes/hygiene.md rename to _overviews/quasiquotes/hygiene.md index ae4aa2090a..fb93165c91 100644 --- a/overviews/quasiquotes/hygiene.md +++ b/_overviews/quasiquotes/hygiene.md @@ -1,14 +1,17 @@ --- -layout: overview-large +layout: multipage-overview title: Hygiene discourse: true partof: quasiquotes +overview-name: Quasiquotes + num: 5 -outof: 13 + +permalink: /overviews/quasiquotes/:title.html --- -**Denys Shabalin, Eugene Burmako** EXPERIMENTAL +**Denys Shabalin, Eugene Burmako** EXPERIMENTAL The notion of hygiene has been widely popularized by macro research in Scheme. A code generator is called hygienic if it ensures the absence of name clashes between regular and generated code, preventing accidental capture of identifiers. As numerous experience reports show, hygiene is of great importance to code generation, because name binding problems are often non-obvious and lack of hygiene might manifest itself in subtle ways. diff --git a/overviews/quasiquotes/intro.md b/_overviews/quasiquotes/intro.md similarity index 97% rename from overviews/quasiquotes/intro.md rename to _overviews/quasiquotes/intro.md index 3ea2be373c..8ae3d6cb58 100644 --- a/overviews/quasiquotes/intro.md +++ b/_overviews/quasiquotes/intro.md @@ -1,14 +1,17 @@ --- -layout: overview-large +layout: multipage-overview title: Introduction discourse: true partof: quasiquotes +overview-name: Quasiquotes + num: 2 -outof: 13 + +permalink: /overviews/quasiquotes/:title.html --- -**Denys Shabalin** EXPERIMENTAL +**Denys Shabalin** EXPERIMENTAL Quasiquotes are a neat notation that lets you manipulate Scala syntax trees with ease: @@ -152,4 +155,3 @@ So, in general, only one `..$` is allowed per given list. Similar restrictions a case q"f(...$a)(...$b)" => // not allowed In this section we only worked with function arguments but the same splicing rules are true for all syntax forms with a variable number of elements. [Syntax summary](/overviews/quasiquotes/syntax-summary.html) and the corresponding details sections demonstrate how you can use splicing with other syntactic forms. - diff --git a/overviews/quasiquotes/lifting.md b/_overviews/quasiquotes/lifting.md similarity index 97% rename from overviews/quasiquotes/lifting.md rename to _overviews/quasiquotes/lifting.md index ee98723912..eea2842479 100644 --- a/overviews/quasiquotes/lifting.md +++ b/_overviews/quasiquotes/lifting.md @@ -1,14 +1,17 @@ --- -layout: overview-large +layout: multipage-overview title: Lifting discourse: true partof: quasiquotes +overview-name: Quasiquotes + num: 3 -outof: 13 + +permalink: /overviews/quasiquotes/:title.html --- -**Denys Shabalin** EXPERIMENTAL +**Denys Shabalin** EXPERIMENTAL Lifting is an extensible way to unquote custom data types in quasiquotes. Its primary use-case is support unquoting of [literal](/overviews/quasiquotes/expression-details.html#literal) values and a number of reflection primitives as trees: @@ -153,5 +156,3 @@ So, in practice, it's much easier to just define a `Liftable` for given universe // ... } - - diff --git a/overviews/quasiquotes/pattern-details.md b/_overviews/quasiquotes/pattern-details.md similarity index 94% rename from overviews/quasiquotes/pattern-details.md rename to _overviews/quasiquotes/pattern-details.md index ac37c5dc37..5a0349bd80 100644 --- a/overviews/quasiquotes/pattern-details.md +++ b/_overviews/quasiquotes/pattern-details.md @@ -1,14 +1,17 @@ --- -layout: overview-large +layout: multipage-overview title: Pattern details discourse: true partof: quasiquotes +overview-name: Quasiquotes + num: 10 -outof: 13 + +permalink: /overviews/quasiquotes/:title.html --- -**Denys Shabalin** EXPERIMENTAL +**Denys Shabalin** EXPERIMENTAL ## Wildcard Pattern diff --git a/overviews/quasiquotes/setup.md b/_overviews/quasiquotes/setup.md similarity index 97% rename from overviews/quasiquotes/setup.md rename to _overviews/quasiquotes/setup.md index c273426707..b1231be7cc 100644 --- a/overviews/quasiquotes/setup.md +++ b/_overviews/quasiquotes/setup.md @@ -1,12 +1,15 @@ --- -layout: overview-large +layout: multipage-overview title: Dependencies and setup discourse: true partof: quasiquotes +overview-name: Quasiquotes + num: 1 -outof: 13 + +permalink: /overviews/quasiquotes/:title.html --- ## Scala 2.11 @@ -71,4 +74,3 @@ Here's a neat sbt snippet taken from [Spire](https://github.com/non/spire) that "org.scalamacros" %% "quasiquotes" % "2.0.0" cross CrossVersion.binary) } } - diff --git a/overviews/quasiquotes/syntax-summary.md b/_overviews/quasiquotes/syntax-summary.md similarity index 98% rename from overviews/quasiquotes/syntax-summary.md rename to _overviews/quasiquotes/syntax-summary.md index dea0a4b8a3..786723f096 100644 --- a/overviews/quasiquotes/syntax-summary.md +++ b/_overviews/quasiquotes/syntax-summary.md @@ -1,14 +1,17 @@ --- -layout: overview-large +layout: multipage-overview title: Syntax summary discourse: true partof: quasiquotes +overview-name: Quasiquotes + num: 7 -outof: 13 + +permalink: /overviews/quasiquotes/:title.html --- -**Denys Shabalin** EXPERIMENTAL +**Denys Shabalin** EXPERIMENTAL ## Expressions diff --git a/overviews/quasiquotes/terminology.md b/_overviews/quasiquotes/terminology.md similarity index 89% rename from overviews/quasiquotes/terminology.md rename to _overviews/quasiquotes/terminology.md index 550fd14f71..54a868ad77 100644 --- a/overviews/quasiquotes/terminology.md +++ b/_overviews/quasiquotes/terminology.md @@ -1,14 +1,17 @@ --- -layout: overview-large -title: Terminology summary +layout: multipage-overview +title: Terminology summary discourse: true partof: quasiquotes +overview-name: Quasiquotes + num: 12 -outof: 13 + +permalink: /overviews/quasiquotes/:title.html --- -EXPERIMENTAL +EXPERIMENTAL * **Quasiquote** (not quasi-quote) can refer to either the quasiquote library or any usage of one its [interpolators](/overviews/quasiquotes/intro.html#interpolators). The name is not hyphenated for the sake of consistency with implementations of the same concept in other languages (e.g. [Scheme and Racket](http://docs.racket-lang.org/reference/quasiquote.html), [Haskell](http://www.haskell.org/haskellwiki/Quasiquotation)) * **Tree** or **AST** (Abstract Syntax Tree) is a representation of a Scala program or a part of it through means of the Scala reflection API's Tree type. @@ -19,4 +22,3 @@ outof: 13 * **Rank** is a degree of flattenning of unquotee: `rank($) == 0`, `rank(..$) == 1`, `rank(...$) == 2`. * [**Lifting**](/overviews/quasiquotes/lifting.html) is a way to unquote non-tree values and transform them into trees with the help of the `Liftable` typeclass. * [**Unlifting**](/overviews/quasiquotes/unlifting.html) is a way to unquote non-tree values out of quasiquote patterns with the help of the `Unliftable` typeclass. - diff --git a/overviews/quasiquotes/type-details.md b/_overviews/quasiquotes/type-details.md similarity index 97% rename from overviews/quasiquotes/type-details.md rename to _overviews/quasiquotes/type-details.md index f86d786956..af87eb505c 100644 --- a/overviews/quasiquotes/type-details.md +++ b/_overviews/quasiquotes/type-details.md @@ -1,14 +1,17 @@ --- -layout: overview-large +layout: multipage-overview title: Type details discourse: true partof: quasiquotes +overview-name: Quasiquotes + num: 9 -outof: 13 + +permalink: /overviews/quasiquotes/:title.html --- -**Denys Shabalin** EXPERIMENTAL +**Denys Shabalin** EXPERIMENTAL ## Empty Type diff --git a/overviews/quasiquotes/unlifting.md b/_overviews/quasiquotes/unlifting.md similarity index 95% rename from overviews/quasiquotes/unlifting.md rename to _overviews/quasiquotes/unlifting.md index 434f5b286b..72dcc8c8f5 100644 --- a/overviews/quasiquotes/unlifting.md +++ b/_overviews/quasiquotes/unlifting.md @@ -1,14 +1,17 @@ --- -layout: overview-large +layout: multipage-overview title: Unlifting discourse: true partof: quasiquotes +overview-name: Quasiquotes + num: 4 -outof: 13 + +permalink: /overviews/quasiquotes/:title.html --- -**Denys Shabalin** EXPERIMENTAL +**Denys Shabalin** EXPERIMENTAL Unlifting is the reverse operation to [lifting](/overviews/quasiquotes/lifting.html): it takes a tree and recovers a value from it: diff --git a/_overviews/quasiquotes/usecases.md b/_overviews/quasiquotes/usecases.md new file mode 100644 index 0000000000..94320ee19b --- /dev/null +++ b/_overviews/quasiquotes/usecases.md @@ -0,0 +1,94 @@ +--- +layout: multipage-overview +title: Use cases + +discourse: true + +partof: quasiquotes +overview-name: Quasiquotes + +num: 6 + +permalink: /overviews/quasiquotes/:title.html +--- +**Denys Shabalin** EXPERIMENTAL + +## AST manipulation in macros and compiler plugins + +Quasiquotes were designed primary as tool for ast manipulation in macros. A common workflow is to deconstruct arguments with quasiquote patterns and then construct a rewritten result with another quasiquote: + + // macro that prints the expression code before executing it + object debug { + def apply[T](x: => T): T = macro impl + def impl(c: Context)(x: c.Tree) = { import c.universe._ + val q"..$stats" = x + val loggedStats = stats.flatMap { stat => + val msg = "executing " + showCode(stat) + List(q"println($msg)", stat) + } + q"..$loggedStats" + } + } + + // usage + object Test extends App { + def faulty: Int = throw new Exception + debug { + val x = 1 + val y = x + faulty + x + y + } + } + + // output + executing val x: Int = 1 + executing val y: Int = x.+(Test.this.faulty) + java.lang.Exception + ... + +To simplify integration with macros we've also made it easier to simply use trees in macro implementations instead of the reify-centric `Expr` api that might be used previously: + + // 2.10 + object Macro { + def apply(x: Int): Int = macro impl + def impl(c: Context)(x: c.Expr[Int]): c.Expr[Int] = { import c.universe._ + c.Expr(q"$x + 1") + } + } + + // in 2.11 you can also do it like that + object Macro { + def apply(x: Int): Int = macro impl + def impl(c: Context)(x: c.Tree) = { import c.universe._ + q"$x + 1" + } + } + +You no longer need to wrap the return value of a macro with `c.Expr`, or to specify the argument types twice, and the return type in `impl` is now optional. + +Quasiquotes can also be used "as is" in compiler plugins as the reflection API is strict subset of the compiler's `Global` API. + +## Just in time compilation + +Thanks to the `ToolBox` API, one can generate, compile and run Scala code at runtime: + + scala> val code = q"""println("compiled and run at runtime!")""" + scala> val compiledCode = toolbox.compile(code) + scala> val result = compiledCode() + compiled and run at runtime! + result: Any = () + +## Offline code generation + +Thanks to the new `showCode` "pretty printer" one can implement an offline code generator that does AST manipulation with the help of quasiquotes, and then serializes that into actual source code right before writing it to disk: + + object OfflineCodeGen extends App { + def generateCode() = + q"package mypackage { class MyClass }" + def saveToFile(path: String, code: Tree) = { + val writer = new java.io.PrintWriter(path) + try writer.write(showCode(code)) + finally writer.close() + } + saveToFile("myfile.scala", generateCode()) + } diff --git a/_overviews/reflection/annotations-names-scopes.md b/_overviews/reflection/annotations-names-scopes.md new file mode 100644 index 0000000000..e5bc6c266d --- /dev/null +++ b/_overviews/reflection/annotations-names-scopes.md @@ -0,0 +1,450 @@ +--- +layout: multipage-overview +title: Annotations, Names, Scopes, and More + +discourse: true + +partof: reflection +overview-name: Reflection + +num: 4 + +languages: [ja] +permalink: /overviews/reflection/:title.html +--- + +EXPERIMENTAL + +## Annotations + +In Scala, declarations can be annotated using subtypes of +`scala.annotation.Annotation`. Furthermore, since Scala integrates with +[Java's annotation system](http://docs.oracle.com/javase/7/docs/technotes/guides/language/annotations.html#_top), +it's possible to work with annotations produced by a +standard Java compiler. + +Annotations can be inspected reflectively if the corresponding annotations +have been persisted, so that they can be read from the classfile containing +the annotated declarations. A custom annotation type can be made persistent by +inheriting from `scala.annotation.StaticAnnotation` or +`scala.annotation.ClassfileAnnotation`. As a result, instances of the +annotation type are stored as special attributes in the corresponding +classfile. Note that subclassing just +`scala.annotation.Annotation` is not enough to have the corresponding metadata +persisted for runtime reflection. Moreover, subclassing +`scala.annotation.ClassfileAnnotation` does not make your annotation visible +as a Java annotation at runtime; that requires writing the annotation class +in Java. + +The API distinguishes between two kinds of annotations: + +- *Java annotations:* annotations on definitions produced by the Java compiler, _i.e.,_ subtypes of `java.lang.annotation.Annotation` attached to program definitions. When read by Scala reflection, the `scala.annotation.ClassfileAnnotation` trait is automatically added as a subclass to every Java annotation. +- *Scala annotations:* annotations on definitions or types produced by the Scala compiler. + +The distinction between Java and Scala annotations is manifested in the +contract of `scala.reflect.api.Annotations#Annotation`, which exposes both +`scalaArgs` and `javaArgs`. For Scala or Java annotations extending +`scala.annotation.ClassfileAnnotation` `scalaArgs` is empty and the arguments +(if any) are stored in `javaArgs`. For all other Scala annotations, the +arguments are stored in `scalaArgs` and `javaArgs` is empty. + +Arguments in `scalaArgs` are represented as typed trees. Note that these trees +are not transformed by any phases following the type-checker. Arguments in +`javaArgs` are represented as a map from `scala.reflect.api.Names#Name` to +`scala.reflect.api.Annotations#JavaArgument`. Instances of `JavaArgument` +represent different kinds of Java annotation arguments: + +- literals (primitive and string constants), +- arrays, and +- nested annotations. + +## Names + +Names are simple wrappers for strings. +[Name](http://www.scala-lang.org/api/current/scala-reflect/index.html#scala.reflect.api.Names$NameApi) +has two subtypes `TermName` and `TypeName` which distinguish names of terms (like +objects or members) and types (like classes, traits, and type members). A term +and a type of the same name can co-exist in the same object. In other words, +types and terms have separate name spaces. + +Names are associated with a universe. Example: + + scala> import scala.reflect.runtime.universe._ + import scala.reflect.runtime.universe._ + + scala> val mapName = TermName("map") + mapName: scala.reflect.runtime.universe.TermName = map + +Above, we're creating a `Name` associated with the runtime reflection universe +(this is also visible in its path-dependent type +`reflect.runtime.universe.TermName`). + +Names are often used to look up members of types. For example, to search for +the `map` method (which is a term) declared in the `List` class, one can do: + + scala> val listTpe = typeOf[List[Int]] + listTpe: scala.reflect.runtime.universe.Type = scala.List[Int] + + scala> listTpe.member(mapName) + res1: scala.reflect.runtime.universe.Symbol = method map + +To search for a type member, one can follow the same procedure, using +`TypeName` instead. It is also possible to rely on implicit conversions to +convert between strings and term or type names: + + scala> listTpe.member("map": TermName) + res2: scala.reflect.runtime.universe.Symbol = method map + +### Standard Names + +Certain names, such as "`_root_`", have special meanings in Scala programs. As +such they are essential for reflectively accessing certain Scala constructs. +For example, reflectively invoking a constructor requires using the +*standard name* `universe.nme.CONSTRUCTOR`, the term name `` which represents the +constructor name on the JVM. + +There are both + +- *standard term names,* _e.g.,_ "``", "`package`", and "`_root_`", and +- *standard type names,* _e.g.,_ "``", "`_`", and "`_*`". + +Some names, such as "package", exist both as a type name and a term name. +Standard names are made available through the `nme` and `tpnme` members of +class `Universe`. For a complete specification of all standard names, see the +[API documentation](http://www.scala-lang.org/api/current/scala-reflect/index.html#scala.reflect.api.StandardNames). + +## Scopes + +A scope object generally maps names to symbols available in a corresponding +lexical scope. Scopes can be nested. The base type exposed in the reflection +API, however, only exposes a minimal interface, representing a scope as an +iterable of [Symbol](http://www.scala-lang.org/api/current/scala-reflect/index.html#scala.reflect.api.Symbols$Symbol)s. + +Additional functionality is exposed in *member scopes* that are returned by +`members` and `declarations` defined in +[scala.reflect.api.Types#TypeApi](http://www.scala-lang.org/api/current/scala-reflect/index.html#scala.reflect.api.Types$TypeApi). +[scala.reflect.api.Scopes#MemberScope](http://www.scala-lang.org/api/current/scala-reflect/index.html#scala.reflect.api.Scopes$MemberScope) +supports the `sorted` method, which sorts members *in declaration order*. + +The following example returns a list of the symbols of all overridden members +of the `List` class, in declaration order: + + scala> val overridden = listTpe.declarations.sorted.filter(_.isOverride) + overridden: List[scala.reflect.runtime.universe.Symbol] = List(method companion, method ++, method +:, method toList, method take, method drop, method slice, method takeRight, method splitAt, method takeWhile, method dropWhile, method span, method reverse, method stringPrefix, method toStream, method foreach) + +## Exprs + +In addition to type `scala.reflect.api.Trees#Tree`, the base type of abstract +syntax trees, typed trees can also be represented as instances of type +[`scala.reflect.api.Exprs#Expr`](http://www.scala-lang.org/api/current/scala-reflect/index.html#scala.reflect.api.Exprs$Expr). +An `Expr` wraps +an abstract syntax tree and an internal type tag to provide access to the type +of the tree. `Expr`s are mainly used to simply and conveniently create typed +abstract syntax trees for use in a macro. In most cases, this involves methods +`reify` and `splice` (see the +[macros guide](http://docs.scala-lang.org/overviews/macros/overview.html) for details). + +## Flags and flag sets + +Flags are used to provide modifiers for abstract syntax trees that represent +definitions via the `flags` field of `scala.reflect.api.Trees#Modifiers`. +Trees that accept modifiers are: + +- `scala.reflect.api.Trees#ClassDef`. Classes and traits. +- `scala.reflect.api.Trees#ModuleDef`. Objects. +- `scala.reflect.api.Trees#ValDef`. Vals, vars, parameters, and self type annotations. +- `scala.reflect.api.Trees#DefDef`. Methods and constructors. +- `scala.reflect.api.Trees#TypeDef`. Type aliases, abstract type members and type parameters. + +For example, to create a class named `C` one would write something like: + + ClassDef(Modifiers(NoFlags), TypeName("C"), Nil, ...) + +Here, the flag set is empty. To make `C` private, one would write something +like: + + ClassDef(Modifiers(PRIVATE), TypeName("C"), Nil, ...) + +Flags can also be combined with the vertical bar operator (`|`). For example, +a private final class is written something like: + + ClassDef(Modifiers(PRIVATE | FINAL), TypeName("C"), Nil, ...) + +The list of all available flags is defined in +`scala.reflect.api.FlagSets#FlagValues`, available via +`scala.reflect.api.FlagSets#Flag`. (Typically, one uses a wildcard import for +this, _e.g.,_ `import scala.reflect.runtime.universe.Flag._`.) + +Definition trees are compiled down to symbols, so that flags on modifiers of +these trees are transformed into flags on the resulting symbols. Unlike trees, +symbols don't expose flags, but rather provide test methods following the +`isXXX` pattern (_e.g.,_ `isFinal` can be used to test finality). In some +cases, these test methods require a conversion using `asTerm`, `asType`, or +`asClass`, as some flags only make sense for certain kinds of symbols. + +*Of note:* This part of the reflection API is being considered a candidate +for redesign. It is quite possible that in future releases of the reflection +API, flag sets could be replaced with something else. + +## Constants + +Certain expressions that the Scala specification calls *constant expressions* +can be evaluated by the Scala compiler at compile time. The following kinds of +expressions are compile-time constants (see [section 6.24 of the Scala language specification](http://scala-lang.org/files/archive/spec/2.11/06-expressions.html#constant-expressions)): + +1. Literals of primitive value classes ([Byte](http://www.scala-lang.org/api/current/index.html#scala.Byte), [Short](http://www.scala-lang.org/api/current/index.html#scala.Short), [Int](http://www.scala-lang.org/api/current/index.html#scala.Int), [Long](http://www.scala-lang.org/api/current/index.html#scala.Long), [Float](http://www.scala-lang.org/api/current/index.html#scala.Float), [Double](http://www.scala-lang.org/api/current/index.html#scala.Double), [Char](http://www.scala-lang.org/api/current/index.html#scala.Char), [Boolean](http://www.scala-lang.org/api/current/index.html#scala.Boolean) and [Unit](http://www.scala-lang.org/api/current/index.html#scala.Unit)) - represented directly as the corresponding type. + +2. String literals - represented as instances of the string. + +3. References to classes, typically constructed with [scala.Predef#classOf](http://www.scala-lang.org/api/current/index.html#scala.Predef$@classOf[T]:Class[T]) - represented as [types](http://www.scala-lang.org/api/current/scala-reflect/index.html#scala.reflect.api.Types$Type). + +4. References to Java enumeration values - represented as [symbols](http://www.scala-lang.org/api/current/scala-reflect/index.html#scala.reflect.api.Symbols$Symbol). + +Constant expressions are used to represent + +- literals in abstract syntax trees (see `scala.reflect.api.Trees#Literal`), and +- literal arguments for Java class file annotations (see `scala.reflect.api.Annotations#LiteralArgument`). + +Example: + + Literal(Constant(5)) + +The above expression creates an AST representing the integer literal `5` in +Scala source code. + +`Constant` is an example of a "virtual case class", _i.e.,_ a class whose +instances can be constructed and matched against as if it were a case class. +Both types `Literal` and `LiteralArgument` have a `value` method returning the +compile-time constant underlying the literal. + +Examples: + + Constant(true) match { + case Constant(s: String) => println("A string: " + s) + case Constant(b: Boolean) => println("A Boolean value: " + b) + case Constant(x) => println("Something else: " + x) + } + assert(Constant(true).value == true) + +Class references are represented as instances of +`scala.reflect.api.Types#Type`. Such a reference can be converted to a runtime +class using the `runtimeClass` method of a `RuntimeMirror` such as +`scala.reflect.runtime.currentMirror`. (This conversion from a type to a +runtime class is necessary, because when the Scala compiler processes a class +reference, the underlying runtime class might not yet have been compiled.) + +Java enumeration value references are represented as symbols (instances of +`scala.reflect.api.Symbols#Symbol`), which on the JVM point to methods that +return the underlying enumeration values. A `RuntimeMirror` can be used to +inspect an underlying enumeration or to get the runtime value of a reference +to an enumeration. + +Example: + + // Java source: + enum JavaSimpleEnumeration { FOO, BAR } + + import java.lang.annotation.*; + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.TYPE}) + public @interface JavaSimpleAnnotation { + Class classRef(); + JavaSimpleEnumeration enumRef(); + } + + @JavaSimpleAnnotation( + classRef = JavaAnnottee.class, + enumRef = JavaSimpleEnumeration.BAR + ) + public class JavaAnnottee {} + + // Scala source: + import scala.reflect.runtime.universe._ + import scala.reflect.runtime.{currentMirror => cm} + + object Test extends App { + val jann = typeOf[JavaAnnottee].typeSymbol.annotations(0).javaArgs + + def jarg(name: String) = jann(TermName(name)) match { + // Constant is always wrapped in a Literal or LiteralArgument tree node + case LiteralArgument(ct: Constant) => value + case _ => sys.error("Not a constant") + } + + val classRef = jarg("classRef").value.asInstanceOf[Type] + println(showRaw(classRef)) // TypeRef(ThisType(), JavaAnnottee, List()) + println(cm.runtimeClass(classRef)) // class JavaAnnottee + + val enumRef = jarg("enumRef").value.asInstanceOf[Symbol] + println(enumRef) // value BAR + + val siblings = enumRef.owner.typeSignature.declarations + val enumValues = siblings.filter(sym => sym.isVal && sym.isPublic) + println(enumValues) // Scope { + // final val FOO: JavaSimpleEnumeration; + // final val BAR: JavaSimpleEnumeration + // } + + val enumClass = cm.runtimeClass(enumRef.owner.asClass) + val enumValue = enumClass.getDeclaredField(enumRef.name.toString).get(null) + println(enumValue) // BAR + } + +## Printers + +Utilities for nicely printing +[`Trees`](http://www.scala-lang.org/api/current/scala-reflect/index.html#scala.reflect.api.Trees) and +[`Types`](http://www.scala-lang.org/api/current/scala-reflect/index.html#scala.reflect.api.Types). + +### Printing Trees + +The method `show` displays the "prettified" representation of reflection +artifacts. This representation provides one with the desugared Java +representation of Scala code. For example: + + scala> import scala.reflect.runtime.universe._ + import scala.reflect.runtime.universe._ + + scala> def tree = reify { final class C { def x = 2 } }.tree + tree: scala.reflect.runtime.universe.Tree + + scala> show(tree) + res0: String = + { + final class C extends AnyRef { + def () = { + super.(); + () + }; + def x = 2 + }; + () + } + +The method `showRaw` displays the internal structure of a given reflection +object as a Scala abstract syntax tree (AST), the representation that the +Scala typechecker operates on. + +Note that while this representation appears to generate correct trees that one +might think would be possible to use in a macro implementation, this is not +usually the case. Symbols aren't fully represented (only their names are). +Thus, this method is best-suited for use simply inspecting ASTs given some +valid Scala code. + + scala> showRaw(tree) + res1: String = Block(List( + ClassDef(Modifiers(FINAL), TypeName("C"), List(), Template( + List(Ident(TypeName("AnyRef"))), + emptyValDef, + List( + DefDef(Modifiers(), nme.CONSTRUCTOR, List(), List(List()), TypeTree(), + Block(List( + Apply(Select(Super(This(tpnme.EMPTY), tpnme.EMPTY), nme.CONSTRUCTOR), List())), + Literal(Constant(())))), + DefDef(Modifiers(), TermName("x"), List(), List(), TypeTree(), + Literal(Constant(2))))))), + Literal(Constant(()))) + +The method `showRaw` can also print `scala.reflect.api.Types` next to the artifacts being inspected. + + scala> import scala.tools.reflect.ToolBox // requires scala-compiler.jar + import scala.tools.reflect.ToolBox + + scala> import scala.reflect.runtime.{currentMirror => cm} + import scala.reflect.runtime.{currentMirror=>cm} + + scala> showRaw(cm.mkToolBox().typeCheck(tree), printTypes = true) + res2: String = Block[1](List( + ClassDef[2](Modifiers(FINAL), TypeName("C"), List(), Template[3]( + List(Ident[4](TypeName("AnyRef"))), + emptyValDef, + List( + DefDef[2](Modifiers(), nme.CONSTRUCTOR, List(), List(List()), TypeTree[3](), + Block[1](List( + Apply[4](Select[5](Super[6](This[3](TypeName("C")), tpnme.EMPTY), ...))), + Literal[1](Constant(())))), + DefDef[2](Modifiers(), TermName("x"), List(), List(), TypeTree[7](), + Literal[8](Constant(2))))))), + Literal[1](Constant(()))) + [1] TypeRef(ThisType(scala), scala.Unit, List()) + [2] NoType + [3] TypeRef(NoPrefix, TypeName("C"), List()) + [4] TypeRef(ThisType(java.lang), java.lang.Object, List()) + [5] MethodType(List(), TypeRef(ThisType(java.lang), java.lang.Object, List())) + [6] SuperType(ThisType(TypeName("C")), TypeRef(... java.lang.Object ...)) + [7] TypeRef(ThisType(scala), scala.Int, List()) + [8] ConstantType(Constant(2)) + +### Printing Types + +The method `show` can be used to produce a *readable* string representation of a type: + + scala> import scala.reflect.runtime.universe._ + import scala.reflect.runtime.universe._ + + scala> def tpe = typeOf[{ def x: Int; val y: List[Int] }] + tpe: scala.reflect.runtime.universe.Type + + scala> show(tpe) + res0: String = scala.AnyRef{def x: Int; val y: scala.List[Int]} + +Like the method `showRaw` for `scala.reflect.api.Trees`, `showRaw` for +`scala.reflect.api.Types` provides a visualization of the Scala AST operated +on by the Scala typechecker. + + scala> showRaw(tpe) + res1: String = RefinedType( + List(TypeRef(ThisType(scala), TypeName("AnyRef"), List())), + Scope( + TermName("x"), + TermName("y"))) + +The `showRaw` method also has named parameters `printIds` and `printKinds`, +both with default argument `false`. When passing `true` to these, `showRaw` +additionally shows the unique identifiers of symbols, as well as their kind +(package, type, method, getter, etc.). + + scala> showRaw(tpe, printIds = true, printKinds = true) + res2: String = RefinedType( + List(TypeRef(ThisType(scala#2043#PK), TypeName("AnyRef")#691#TPE, List())), + Scope( + TermName("x")#2540#METH, + TermName("y")#2541#GET)) + +## Positions + +Positions (instances of the +[Position](http://www.scala-lang.org/api/current/scala-reflect/index.html#scala.reflect.api.Position) trait) +are used to track the origin of symbols and tree nodes. They are commonly used when +displaying warnings and errors, to indicate the incorrect point in the +program. Positions indicate a column and line in a source file (the offset +from the beginning of the source file is called its "point", which is +sometimes less convenient to use). They also carry the content of the line +they refer to. Not all trees or symbols have a position; a missing position is +indicated using the `NoPosition` object. + +Positions can refer either to only a single character in a source file, or to +a *range*. In the latter case, a *range position* is used (positions that are +not range positions are also called *offset positions*). Range positions have +in addition `start` and `end` offsets. The `start` and `end` offsets can be +"focussed" on using the `focusStart` and `focusEnd` methods which return +positions (when called on a position which is not a range position, they just +return `this`). + +Positions can be compared using methods such as `precedes`, which holds if +both positions are defined (_i.e.,_ the position is not `NoPosition`) and the +end point of `this` position is not larger than the start point of the given +position. In addition, range positions can be tested for inclusion (using +method `includes`) and overlapping (using method `overlaps`). + +Range positions are either *transparent* or *opaque* (not transparent). The +fact whether a range position is opaque or not has an impact on its permitted +use, because trees containing range positions must satisfy the following +invariants: + +- A tree with an offset position never contains a child with a range position +- If the child of a tree with a range position also has a range position, then the child's range is contained in the parent's range. +- Opaque range positions of children of the same node are non-overlapping (this means their overlap is at most a single point). + +Using the `makeTransparent` method, an opaque range position can be converted +to a transparent one; all other positions are returned unchanged. diff --git a/_overviews/reflection/changelog211.md b/_overviews/reflection/changelog211.md new file mode 100644 index 0000000000..ab49dd7842 --- /dev/null +++ b/_overviews/reflection/changelog211.md @@ -0,0 +1,18 @@ +--- +layout: multipage-overview +title: Changes in Scala 2.11 + +partof: reflection +overview-name: Reflection + +num: 7 + +languages: [ja] +permalink: /overviews/reflection/:title.html +--- + +EXPERIMENTAL + +**Eugene Burmako** + +The page lives at [/overviews/macros/changelog211.html](/overviews/macros/changelog211.html). diff --git a/_overviews/reflection/environment-universes-mirrors.md b/_overviews/reflection/environment-universes-mirrors.md new file mode 100644 index 0000000000..419272694e --- /dev/null +++ b/_overviews/reflection/environment-universes-mirrors.md @@ -0,0 +1,203 @@ +--- +layout: multipage-overview +title: Environment, Universes, and Mirrors + +discourse: true + +partof: reflection +overview-name: Reflection + +num: 2 + +languages: [ja] +permalink: /overviews/reflection/:title.html +--- + +EXPERIMENTAL + +## Environment + +The reflection environment differs based on whether the reflective task is to +be done at run time or at compile time. The distinction between an environment to be used at +run time or compile time is encapsulated in a so-called *universe*. Another +important aspect of the reflective environment is the set of entities that we +have reflective access to. This set of entities is determined by a so-called +*mirror*. + +For example, the entities accessible through runtime +reflection are made available by a `ClassloaderMirror`. This mirror provides +only access to entities (packages, types, and members) loaded by a specific +classloader. + +Mirrors not only determine the set of entities that can be accessed +reflectively. They also provide reflective operations to be performed on those +entities. For example, in runtime reflection an *invoker mirror* can be used +to invoke a method or constructor of a class. + +## Universes + +There are two principal +types of universes-- since there exists both runtime and compile-time +reflection capabilities, one must use the universe that corresponds to +whatever the task is at hand. Either: + +- `scala.reflect.runtime.universe` for **runtime reflection**, or +- `scala.reflect.macros.Universe` for **compile-time reflection**. + +A universe provides an interface to all the principal concepts used in +reflection, such as `Types`, `Trees`, and `Annotations`. + +## Mirrors + +All information provided by +reflection is made accessible through *mirrors*. Depending on +the type of information to be obtained, or the reflective action to be taken, +different flavors of mirrors must be used. *Classloader mirrors* can be used to obtain representations of types and +members. From a classloader mirror, it's possible to obtain more specialized *invoker mirrors* (the most commonly-used mirrors), which implement reflective +invocations, such as method or constructor calls and field accesses. + +Summary: + +- **"Classloader" mirrors**. +These mirrors translate names to symbols (via methods `staticClass`/`staticModule`/`staticPackage`). + +- **"Invoker" mirrors**. +These mirrors implement reflective invocations (via methods `MethodMirror.apply`, `FieldMirror.get`, etc.). These "invoker" mirrors are the types of mirrors that are most commonly used. + +### Runtime Mirrors + +The entry point to mirrors for use at runtime is via `ru.runtimeMirror()`, where `ru` is `scala.reflect.runtime.universe`. + +The result of a `scala.reflect.api.JavaMirrors#runtimeMirror` call is a classloader mirror, of type `scala.reflect.api.Mirrors#ReflectiveMirror`, which can load symbols by name. + +A classloader mirror can create invoker mirrors (including `scala.reflect.api.Mirrors#InstanceMirror`, `scala.reflect.api.Mirrors#MethodMirror`, `scala.reflect.api.Mirrors#FieldMirror`, `scala.reflect.api.Mirrors#ClassMirror`, and `scala.reflect.api.Mirrors#ModuleMirror`). + +Examples of how these two types of mirrors interact are available below. + +### Types of Mirrors, Their Use Cases & Examples + +A `ReflectiveMirror` is used for loading symbols by name, and as an entry point into invoker mirrors. Entry point: `val m = ru.runtimeMirror()`. Example: + + scala> val ru = scala.reflect.runtime.universe + ru: scala.reflect.api.JavaUniverse = ... + + scala> val m = ru.runtimeMirror(getClass.getClassLoader) + m: scala.reflect.runtime.universe.Mirror = JavaMirror ... + +An `InstanceMirror` is used for creating invoker mirrors for methods and fields and for inner classes and inner objects (modules). Entry point: `val im = m.reflect()`. Example: + + scala> class C { def x = 2 } + defined class C + + scala> val im = m.reflect(new C) + im: scala.reflect.runtime.universe.InstanceMirror = instance mirror for C@3442299e + +A `MethodMirror` is used for invoking instance methods (Scala only has instance methods-- methods of objects are instance methods of object instances, obtainable via `ModuleMirror.instance`). Entry point: `val mm = im.reflectMethod()`. Example: + + scala> val methodX = ru.typeOf[C].declaration(ru.TermName("x")).asMethod + methodX: scala.reflect.runtime.universe.MethodSymbol = method x + + scala> val mm = im.reflectMethod(methodX) + mm: scala.reflect.runtime.universe.MethodMirror = method mirror for C.x: scala.Int (bound to C@3442299e) + + scala> mm() + res0: Any = 2 + +A `FieldMirror` is used for getting/setting instance fields (like methods, Scala only has instance fields, see above). Entry point: `val fm = im.reflectField()`. Example: + + scala> class C { val x = 2; var y = 3 } + defined class C + + scala> val m = ru.runtimeMirror(getClass.getClassLoader) + m: scala.reflect.runtime.universe.Mirror = JavaMirror ... + + scala> val im = m.reflect(new C) + im: scala.reflect.runtime.universe.InstanceMirror = instance mirror for C@5f0c8ac1 + + scala> val fieldX = ru.typeOf[C].declaration(ru.TermName("x")).asTerm.accessed.asTerm + fieldX: scala.reflect.runtime.universe.TermSymbol = value x + + scala> val fmX = im.reflectField(fieldX) + fmX: scala.reflect.runtime.universe.FieldMirror = field mirror for C.x (bound to C@5f0c8ac1) + + scala> fmX.get + res0: Any = 2 + + scala> fmX.set(3) + + scala> val fieldY = ru.typeOf[C].declaration(ru.TermName("y")).asTerm.accessed.asTerm + fieldY: scala.reflect.runtime.universe.TermSymbol = variable y + + scala> val fmY = im.reflectField(fieldY) + fmY: scala.reflect.runtime.universe.FieldMirror = field mirror for C.y (bound to C@5f0c8ac1) + + scala> fmY.get + res1: Any = 3 + + scala> fmY.set(4) + + scala> fmY.get + res2: Any = 4 + +A `ClassMirror` is used for creating invoker mirrors for constructors. Entry points: for static classes `val cm1 = m.reflectClass()`, for inner classes `val mm2 = im.reflectClass()`. Example: + + scala> case class C(x: Int) + defined class C + + scala> val m = ru.runtimeMirror(getClass.getClassLoader) + m: scala.reflect.runtime.universe.Mirror = JavaMirror ... + + scala> val classC = ru.typeOf[C].typeSymbol.asClass + classC: scala.reflect.runtime.universe.Symbol = class C + + scala> val cm = m.reflectClass(classC) + cm: scala.reflect.runtime.universe.ClassMirror = class mirror for C (bound to null) + + scala> val ctorC = ru.typeOf[C].declaration(ru.nme.CONSTRUCTOR).asMethod + ctorC: scala.reflect.runtime.universe.MethodSymbol = constructor C + + scala> val ctorm = cm.reflectConstructor(ctorC) + ctorm: scala.reflect.runtime.universe.MethodMirror = constructor mirror for C.(x: scala.Int): C (bound to null) + + scala> ctorm(2) + res0: Any = C(2) + +A `ModuleMirror` is used for accessing instances of singleton objects. Entry points: for static objects `val mm1 = m.reflectModule()`, for inner objects `val mm2 = im.reflectModule()`. Example: + + scala> object C { def x = 2 } + defined module C + + scala> val m = ru.runtimeMirror(getClass.getClassLoader) + m: scala.reflect.runtime.universe.Mirror = JavaMirror ... + + scala> val objectC = ru.typeOf[C.type].termSymbol.asModule + objectC: scala.reflect.runtime.universe.ModuleSymbol = object C + + scala> val mm = m.reflectModule(objectC) + mm: scala.reflect.runtime.universe.ModuleMirror = module mirror for C (bound to null) + + scala> val obj = mm.instance + obj: Any = C$@1005ec04 + +### Compile-Time Mirrors + +Compile-time mirrors make use of only classloader mirrors to load symbols by name. + +The entry point to classloader mirrors is via `scala.reflect.macros.Context#mirror`. Typical methods which use classloader mirrors include `scala.reflect.api.Mirror#staticClass`, `scala.reflect.api.Mirror#staticModule`, and `scala.reflect.api.Mirror#staticPackage`. For example: + + import scala.reflect.macros.Context + + case class Location(filename: String, line: Int, column: Int) + + object Macros { + def currentLocation: Location = macro impl + + def impl(c: Context): c.Expr[Location] = { + import c.universe._ + val pos = c.macroApplication.pos + val clsLocation = c.mirror.staticModule("Location") // get symbol of "Location" object + c.Expr(Apply(Ident(clsLocation), List(Literal(Constant(pos.source.path)), Literal(Constant(pos.line)), Literal(Constant(pos.column))))) + } + } + +*Of note:* There are several high-level alternatives that one can use to avoid having to manually lookup symbols. For example, `typeOf[Location.type].termSymbol` (or `typeOf[Location].typeSymbol` if we needed a `ClassSymbol`), which are typesafe since we don’t have to use strings to lookup the symbol. diff --git a/_overviews/reflection/overview.md b/_overviews/reflection/overview.md new file mode 100644 index 0000000000..5bdbca4f09 --- /dev/null +++ b/_overviews/reflection/overview.md @@ -0,0 +1,349 @@ +--- +layout: multipage-overview +title: Overview + +partof: reflection +overview-name: Reflection + +num: 1 + +languages: [ja] +permalink: /overviews/reflection/:title.html +--- + +EXPERIMENTAL + +**Heather Miller, Eugene Burmako, Philipp Haller** + +*Reflection* is the ability of a program to inspect, and possibly even modify +itself. It has a long history across object-oriented, functional, +and logic programming paradigms. +While some languages are built around reflection as a guiding principle, many +languages progressively evolve their reflection abilities over time. + +Reflection involves the ability to **reify** (ie. make explicit) otherwise-implicit +elements of a program. These elements can be either static program elements +like classes, methods, or expressions, or dynamic elements like the current +continuation or execution events such as method invocations and field accesses. +One usually distinguishes between compile-time and runtime reflection depending +on when the reflection process is performed. **Compile-time reflection** +is a powerful way to develop program transformers and generators, while +**runtime reflection** is typically used to adapt the language semantics +or to support very late binding between software components. + +Until 2.10, Scala has not had any reflection capabilities of its own. Instead, +one could use part of the Java reflection API, namely that dealing with providing +the ability to dynamically inspect classes and objects and access their members. +However, many Scala-specific elements are unrecoverable under standalone Java reflection, +which only exposes Java elements (no functions, no traits) +and types (no existential, higher-kinded, path-dependent and abstract types). +In addition, Java reflection is also unable to recover runtime type info of Java types +that are generic at compile-time; a restriction that carried through to runtime +reflection on generic types in Scala. + +In Scala 2.10, a new reflection library was introduced not only to address the shortcomings +of Java’s runtime reflection on Scala-specific and generic types, but to also +add a more powerful toolkit of general reflective capabilities to Scala. Along +with full-featured runtime reflection for Scala types and generics, Scala 2.10 also +ships with compile-time reflection capabilities, in the form of +[macros]({{site.baseurl }}/overviews/macros/overview.html), as well as the +ability to *reify* Scala expressions into abstract syntax trees. + +## Runtime Reflection + +What is runtime reflection? Given a type or instance of some object at **runtime**, +reflection is the ability to: + +- inspect the type of that object, including generic types, +- to instantiate new objects, +- or to access or invoke members of that object. + +Let's jump in and see how to do each of the above with a few examples. + +### Examples + +#### Inspecting a Runtime Type (Including Generic Types at Runtime) + +As with other JVM languages, Scala's types are _erased_ at compile time. This +means that if you were to inspect the runtime type of some instance, that you +might not have access to all type information that the Scala compiler has +available at compile time. + +`TypeTags` can be thought of as objects which carry along all type information +available at compile time, to runtime. Though, it's important to note that +`TypeTag`s are always generated by the compiler. This generation is triggered +whenever an implicit parameter or context bound requiring a `TypeTag` is used. +This means that, typically, one can only obtain a `TypeTag` using implicit +parameters or context bounds. + +For example, using context bounds: + + scala> import scala.reflect.runtime.{universe => ru} + import scala.reflect.runtime.{universe=>ru} + + scala> val l = List(1,2,3) + l: List[Int] = List(1, 2, 3) + + scala> def getTypeTag[T: ru.TypeTag](obj: T) = ru.typeTag[T] + getTypeTag: [T](obj: T)(implicit evidence$1: ru.TypeTag[T])ru.TypeTag[T] + + scala> val theType = getTypeTag(l).tpe + theType: ru.Type = List[Int] + +In the above, we first import `scala.reflect.runtime.universe` (it must always +be imported in order to use `TypeTag`s), and we create a `List[Int]` called +`l`. Then, we define a method `getTypeTag` which has a type parameter `T` that +has a context bound (as the REPL shows, this is equivalent to defining an +implicit "evidence" parameter, which causes the compiler to generate a +`TypeTag` for `T`). Finally, we invoke our method with `l` as its parameter, +and call `tpe` which returns the type contained in the `TypeTag`. As we can +see, we get the correct, complete type (including `List`'s concrete type +argument), `List[Int]`. + +Once we have obtained the desired `Type` instance, we can inspect it, e.g.: + + scala> val decls = theType.declarations.take(10) + decls: Iterable[ru.Symbol] = List(constructor List, method companion, method isEmpty, method head, method tail, method ::, method :::, method reverse_:::, method mapConserve, method ++) + +#### Instantiating a Type at Runtime + +Types obtained through reflection can be instantiated by invoking their +constructor using an appropriate "invoker" mirror (mirrors are expanded upon +[below]({{ site.baseurl }}/overviews/reflection/overview.html#mirrors)). Let's +walk through an example using the REPL: + + scala> case class Person(name: String) + defined class Person + + scala> val m = ru.runtimeMirror(getClass.getClassLoader) + m: scala.reflect.runtime.universe.Mirror = JavaMirror with ... + +In the first step we obtain a mirror `m` which makes all classes and types +available that are loaded by the current classloader, including class +`Person`. + + scala> val classPerson = ru.typeOf[Person].typeSymbol.asClass + classPerson: scala.reflect.runtime.universe.ClassSymbol = class Person + + scala> val cm = m.reflectClass(classPerson) + cm: scala.reflect.runtime.universe.ClassMirror = class mirror for Person (bound to null) + +The second step involves obtaining a `ClassMirror` for class `Person` using +the `reflectClass` method. The `ClassMirror` provides access to the +constructor of class `Person`. + + scala> val ctor = ru.typeOf[Person].declaration(ru.nme.CONSTRUCTOR).asMethod + ctor: scala.reflect.runtime.universe.MethodSymbol = constructor Person + +The symbol for `Person`s constructor can be obtained using only the runtime +universe `ru` by looking it up in the declarations of type `Person`. + + scala> val ctorm = cm.reflectConstructor(ctor) + ctorm: scala.reflect.runtime.universe.MethodMirror = constructor mirror for Person.(name: String): Person (bound to null) + + scala> val p = ctorm("Mike") + p: Any = Person(Mike) + +#### Accessing and Invoking Members of Runtime Types + +In general, members of runtime types are accessed using an appropriate +"invoker" mirror (mirrors are expanded upon +[below]({{ site.baseurl}}/overviews/reflection/overview.html#mirrors)). +Let's walk through an example using the REPL: + + scala> case class Purchase(name: String, orderNumber: Int, var shipped: Boolean) + defined class Purchase + + scala> val p = Purchase("Jeff Lebowski", 23819, false) + p: Purchase = Purchase(Jeff Lebowski,23819,false) + +In this example, we will attempt to get and set the `shipped` field of +`Purchase` `p`, reflectively. + + scala> import scala.reflect.runtime.{universe => ru} + import scala.reflect.runtime.{universe=>ru} + + scala> val m = ru.runtimeMirror(p.getClass.getClassLoader) + m: scala.reflect.runtime.universe.Mirror = JavaMirror with ... + +As we did in the previous example, we'll begin by obtaining a mirror `m`, +which makes all classes and types available that are loaded by the classloader +that also loaded the class of `p` (`Purchase`), which we need in order to +access member `shipped`. + + scala> val shippingTermSymb = ru.typeOf[Purchase].declaration(ru.TermName("shipped")).asTerm + shippingTermSymb: scala.reflect.runtime.universe.TermSymbol = method shipped + +We now look up the declaration of the `shipped` field, which gives us a +`TermSymbol` (a type of `Symbol`). We'll need to use this `Symbol` later to +obtain a mirror that gives us access to the value of this field (for some +instance). + + scala> val im = m.reflect(p) + im: scala.reflect.runtime.universe.InstanceMirror = instance mirror for Purchase(Jeff Lebowski,23819,false) + + scala> val shippingFieldMirror = im.reflectField(shippingTermSymb) + shippingFieldMirror: scala.reflect.runtime.universe.FieldMirror = field mirror for Purchase.shipped (bound to Purchase(Jeff Lebowski,23819,false)) + +In order to access a specific instance's `shipped` member, we need a mirror +for our specific instance, `p`'s instance mirror, `im`. Given our instance +mirror, we can obtain a `FieldMirror` for any `TermSymbol` representing a +field of `p`'s type. + +Now that we have a `FieldMirror` for our specific field, we can use methods +`get` and `set` to get/set our specific instance's `shipped` member. Let's +change the status of `shipped` to `true`. + + scala> shippingFieldMirror.get + res7: Any = false + + scala> shippingFieldMirror.set(true) + + scala> shippingFieldMirror.get + res9: Any = true + +### Runtime Classes in Java vs. Runtime Types in Scala + +Those who are comfortable using Java reflection to obtain Java _Class_ +instances at runtime might have noticed that, in Scala, we instead obtain +runtime _types_. + +The REPL-run below shows a very simple scenario where using Java reflection on +Scala classes might return surprising or incorrect results. + +First, we define a base class `E` with an abstract type member `T`, and from +it, we derive two subclasses, `C` and `D`. + + scala> class E { + | type T + | val x: Option[T] = None + | } + defined class E + + scala> class C extends E + defined class C + + scala> class D extends C + defined class D + +Then, we create an instance of both `C` and `D`, meanwhile making type member +`T` concrete (in both cases, `String`) + + scala> val c = new C { type T = String } + c: C{type T = String} = $anon$1@7113bc51 + + scala> val d = new D { type T = String } + d: D{type T = String} = $anon$1@46364879 + +Now, we use methods `getClass` and `isAssignableFrom` from Java Reflection to +obtain an instance of `java.lang.Class` representing the runtime classes of +`c` and `d`, and then we test to see that `d`'s runtime class is a subclass of +`c`'s runtime representation. + + scala> c.getClass.isAssignableFrom(d.getClass) + res6: Boolean = false + +Since above, we saw that `D` extends `C`, this result is a bit surprising. In +performing this simple runtime type check, one would expect the result of the +question "is the class of `d` a subclass of the class of `c`?" to be `true`. +However, as you might've noticed above, when `c` and `d` are instantiated, the +Scala compiler actually creates anonymous subclasses of `C` and `D`, +respectively. This is due to the fact that the Scala compiler must translate +Scala-specific (_i.e.,_ non-Java) language features into some equivalent in +Java bytecode in order to be able to run on the JVM. Thus, the Scala compiler +often creates synthetic classes (i.e. automatically-generated classes) that +are used at runtime in place of user-defined classes. This is quite +commonplace in Scala and can be observed when using Java reflection with a +number of Scala features, _e.g._ closures, type members, type refinements, +local classes, _etc_. + +In situations like these, we can instead use Scala reflection to obtain +precise runtime _types_ of these Scala objects. Scala runtime types carry +along all type info from compile-time, avoiding these types mismatches between +compile-time and run-time. + +Below, we use define a method which uses Scala reflection to get the runtime +types of its arguments, and then checks the subtyping relationship between the +two. If its first argument's type is a subtype of its second argument's type, +it returns `true`. + + scala> import scala.reflect.runtime.{universe => ru} + import scala.reflect.runtime.{universe=>ru} + + scala> def m[T: ru.TypeTag, S: ru.TypeTag](x: T, y: S): Boolean = { + | val leftTag = ru.typeTag[T] + | val rightTag = ru.typeTag[S] + | leftTag.tpe <:< rightTag.tpe + | } + m: [T, S](x: T, y: S)(implicit evidence$1: scala.reflect.runtime.universe.TypeTag[T], implicit evidence$2: scala.reflect.runtime.universe.TypeTag[S])Boolean + + scala> m(d, c) + res9: Boolean = true + +As we can see, we now get the expected result-- `d`'s runtime type is indeed a +subtype of `c`'s runtime type. + +## Compile-time Reflection + +Scala reflection enables a form of *metaprogramming* which makes it possible +for programs to modify *themselves* at compile time. This compile-time +reflection is realized in the form of macros, which provide the ability to +execute methods that manipulate abstract syntax trees at compile-time. + +A particularly interesting aspect of macros is that they are based on the same +API used also for Scala's runtime reflection, provided in package +`scala.reflect.api`. This enables the sharing of generic code between macros +and implementations that utilize runtime reflection. + +Note that +[the macros guide]({{ site.baseurl }}/overviews/macros/overview.html) +focuses on macro specifics, whereas this guide focuses on the general aspects +of the reflection API. Many concepts directly apply to macros, though, such +as abstract syntax trees which are discussed in greater detail in the section on +[Symbols, Trees, and Types]({{site.baseurl }}/overviews/reflection/symbols-trees-types.html). + +## Environment + +All reflection tasks require a proper environment to be set up. This +environment differs based on whether the reflective task is to be done at run +time or at compile time. The distinction between an environment to be used at +run time or compile time is encapsulated in a so-called *universe*. Another +important aspect of the reflective environment is the set of entities that we +have reflective access to. This set of entities is determined by a so-called +*mirror*. + +Mirrors not only determine the set of entities that can be accessed +reflectively. They also provide reflective operations to be performed on those +entities. For example, in runtime reflection an *invoker mirror* can be used +to invoke a method or constructor of a class. + +### Universes + +`Universe` is the entry point to Scala reflection. +A universe provides an interface to all the principal concepts used in +reflection, such as `Types`, `Trees`, and `Annotations`. For more details, see +the section of this guide on +[Universes]({{ site.baseurl}}/overviews/reflection/environment-universes-mirrors.html), +or the +[Universes API docs](http://www.scala-lang.org/api/current/scala-reflect/index.html#scala.reflect.api.Universe) +in package `scala.reflect.api`. + +To use most aspects of Scala reflection, including most code examples provided +in this guide, you need to make sure you import a `Universe` or the members +of a `Universe`. Typically, to use runtime reflection, one can import all +members of `scala.reflect.runtime.universe`, using a wildcard import: + + import scala.reflect.runtime.universe._ + +### Mirrors + +`Mirror`s are a central part of Scala Reflection. All information provided by +reflection is made accessible through these so-called mirrors. Depending on +the type of information to be obtained, or the reflective action to be taken, +different flavors of mirrors must be used. + +For more details, see the section of this guide on +[Mirrors]({{ site.baseurl}}/overviews/reflection/environment-universes-mirrors.html), +or the +[Mirrors API docs](http://www.scala-lang.org/api/current/scala-reflect/index.html#scala.reflect.api.Mirrors) +in package `scala.reflect.api`. diff --git a/_overviews/reflection/symbols-trees-types.md b/_overviews/reflection/symbols-trees-types.md new file mode 100644 index 0000000000..946357865b --- /dev/null +++ b/_overviews/reflection/symbols-trees-types.md @@ -0,0 +1,779 @@ +--- +layout: multipage-overview +title: Symbols, Trees, and Types + +discourse: true + +partof: reflection +overview-name: Reflection + +num: 3 + +languages: [ja] +permalink: /overviews/reflection/:title.html +--- + +EXPERIMENTAL + +## Symbols + +Symbols are used to establish bindings between a name and the entity it refers +to, such as a class or a method. Anything you define and can give a name to in +Scala has an associated symbol. + +Symbols contain all available information about the declaration of an entity +(`class`/`object`/`trait` etc.) or a member (`val`s/`var`s/`def`s etc.), and +as such are an integral abstraction central to both runtime reflection and +compile-time reflection (macros). + +A symbol can provide a wealth of information ranging from the basic `name` +method available on all symbols to other, more involved, concepts such as +getting the `baseClasses` from `ClassSymbol`. Other common use cases of +symbols include inspecting members' signatures, getting type parameters of a +class, getting the parameter type of a method or finding out the type of a +field. + +### The Symbol Owner Hierarchy + +Symbols are organized in a hierarchy. For example, a symbol that represents a +parameter of a method is *owned* by the corresponding method symbol, a method +symbol is *owned* by its enclosing class, trait, or object, a class is *owned* +by a containing package and so on. + +If a symbol does not have an owner, for example, because it refers to a top-level +entity, such as a top-level package, then its owner is the special +`NoSymbol` singleton object. Representing a missing symbol, `NoSymbol` is +commonly used in the API to denote an empty or default value. Accessing the +`owner` of `NoSymbol` throws an exception. See the API docs for the general +interface provided by type +[`Symbol`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/reflect/api/Symbols$SymbolApi.html) + +### `TypeSymbol`s + +A `TypeSymbol` represents type, class, and trait declarations, as well as type +parameters. Interesting members that do not apply to the more specific +`ClassSymbol`s, include `isAbstractType`, `isContravariant`, and +`isCovariant`. + +- `ClassSymbol`: Provides access to all information contained in a class or trait declaration, e.g., `name`, modifiers (`isFinal`, `isPrivate`, `isProtected`, `isAbstractClass`, etc.), `baseClasses`, and `typeParams`. + +### `TermSymbol`s + +The type of term symbols representing val, var, def, and object declarations +as well as packages and value parameters. + +- `MethodSymbol`: The type of method symbols representing def declarations (subclass of `TermSymbol`). It supports queries like checking whether a method is a (primary) constructor, or whether a method supports variable-length argument lists. +- `ModuleSymbol`: The type of module symbols representing object declarations. It allows looking up the class implicitly associated with the object definition via member `moduleClass`. The opposite look up is also possible. One can go back from a module class to the associated module symbol by inspecting its `selfType.termSymbol`. + +### Symbol Conversions + +There can be situations where one uses a method that returns an instance of +the general `Symbol` type. In cases like these, it's possible to convert the +more general `Symbol` type obtained to the specific, more specialized symbol +type needed. + +Symbol conversions, such as `asClass` or `asMethod`, are used to convert to a +more specific subtype of `Symbol` as appropriate (if you want to use the +`MethodSymbol` interface, for example). + +For example, + + scala> import scala.reflect.runtime.universe._ + import scala.reflect.runtime.universe._ + + scala> class C[T] { def test[U](x: T)(y: U): Int = ??? } + defined class C + + scala> val testMember = typeOf[C[Int]].member(TermName("test")) + testMember: scala.reflect.runtime.universe.Symbol = method test + +In this case, `member` returns an instance of `Symbol`, not `MethodSymbol` as +one might expect. Thus, we must use `asMethod` to ensure that we obtain a +`MethodSymbol` + + scala> testMember.asMethod + res0: scala.reflect.runtime.universe.MethodSymbol = method test + +### Free symbols + +The two symbol types `FreeTermSymbol` and `FreeTypeSymbol` have a special +status, in the sense that they refer to symbols whose available information is +not complete. These symbols are generated in some cases during reification +(see the corresponding section about reifying trees for more background). +Whenever reification cannot locate a symbol (meaning that the symbol is not +available in the corresponding class file, for example, because the symbol +refers to a local class), it reifies it as a so-called "free type", a +synthetic dummy symbol that remembers the original name and owner and has a +surrogate type signature that closely follows the original. You can check +whether a symbol is a free type by calling `sym.isFreeType`. You can also get +a list of all free types referenced by a tree and its children by calling +`tree.freeTypes`. Finally, you can get warnings when reification produces free +types by using `-Xlog-free-types`. + +## Types + +As its name suggests, instances of `Type` represent information about the type +of a corresponding symbol. This includes its members (methods, fields, type +aliases, abstract types, nested classes, traits, etc.) either declared +directly or inherited, its base types, its erasure and so on. Types also +provide operations to test for type conformance or equivalence. + +### Instantiating Types + +In general, there are three ways to instantiate a `Type`. + +1. via method `typeOf` on `scala.reflect.api.TypeTags`, which is mixed into `Universe` (simplest and most common). +2. Standard Types, such as `Int`, `Boolean`, `Any`, or `Unit` are accessible through the available universe. +3. Manual instantiation using factory methods such as `typeRef` or `polyType` on `scala.reflect.api.Types`, (not recommended). + +#### Instantiating Types With `typeOf` + +To instantiate a type, most of the time, the +`scala.reflect.api.TypeTags#typeOf` method can be used. It takes a type +argument and produces a `Type` instance which represents that argument. For +example: + + scala> import scala.reflect.runtime.universe._ + import scala.reflect.runtime.universe._ + + scala> typeOf[List[Int]] + res0: scala.reflect.runtime.universe.Type = scala.List[Int] + +In this example, a +[`scala.reflect.api.Types$TypeRef`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/reflect/api/Types$TypeRef.html) +is returned, which corresponds to the type constructor `List`, applied to +the type argument `Int`. + +Note, however, that this approach requires one to specify by hand the type +we're trying to instantiate. What if we're interested in obtaining an instance +of `Type` that corresponds to some arbitrary instance? One can simply define a +method with a context bound on the type parameter-- this generates a +specialized `TypeTag` for us, which we can use to obtain the type of our +arbitrary instance: + + scala> def getType[T: TypeTag](obj: T) = typeOf[T] + getType: [T](obj: T)(implicit evidence$1: scala.reflect.runtime.universe.TypeTag[T])scala.reflect.runtime.universe.Type + + scala> getType(List(1,2,3)) + res1: scala.reflect.runtime.universe.Type = List[Int] + + scala> class Animal; class Cat extends Animal + defined class Animal + defined class Cat + + scala> val a = new Animal + a: Animal = Animal@21c17f5a + + scala> getType(a) + res2: scala.reflect.runtime.universe.Type = Animal + + scala> val c = new Cat + c: Cat = Cat@2302d72d + + scala> getType(c) + res3: scala.reflect.runtime.universe.Type = Cat + +_Note:_ Method `typeOf` does not work for types with type parameters, such as +`typeOf[List[A]]` where `A` is a type parameter. In this case, one can use +`scala.reflect.api.TypeTags#weakTypeOf` instead. For more details, see the +[TypeTags]({{ site.baseurl }}/overviews/reflection/typetags-manifests.html) +section of this guide. + +#### Standard Types + +Standard types, such as `Int`, `Boolean`, `Any`, or `Unit`, are accessible through a universe's `definitions` member. For example: + + scala> import scala.reflect.runtime.universe + import scala.reflect.runtime.universe + + scala> val intTpe = universe.definitions.IntTpe + intTpe: scala.reflect.runtime.universe.Type = Int + +The list of standard types is specified in trait `StandardTypes` in +[`scala.reflect.api.StandardDefinitions`](http://www.scala-lang.org/api/current/index.html#scala.reflect.api.StandardDefinitions$StandardTypes). + +### Common Operations on Types + +Types are typically used for type conformance tests or are queried for members. +The three main classes of operations performed on types are: + +1. Checking the subtyping relationship between two types. +2. Checking for equality between two types. +3. Querying a given type for certain members or inner types. + +#### Subtyping Relationships + +Given two `Type` instances, one can easily test whether one is a subtype of +the other using `<:<` (and in exceptional cases, `weak_<:<`, explained below) + + scala> import scala.reflect.runtime.universe._ + import scala.reflect.runtime.universe._ + + scala> class A; class B extends A + defined class A + defined class B + + scala> typeOf[A] <:< typeOf[B] + res0: Boolean = false + + scala> typeOf[B] <:< typeOf[A] + res1: Boolean = true + +Note that method `weak_<:<` exists to check for _weak conformance_ between two +types. This is typically important when dealing with numeric types. + +Scala's numeric types abide by the following ordering (section 3.5.3 of the Scala language specification): + +> In some situations Scala uses a more general conformance relation. A type S weakly conforms to a type T, written S <:w T, if S<:T or both S and T are primitive number types and S precedes T in the following ordering: + +| Weak Conformance Relations | + --------------------------- +| `Byte` `<:w` `Short` | +| `Short` `<:w` `Int` | +| `Char` `<:w` `Int` | +| `Int` `<:w` `Long` | +| `Long` `<:w` `Float` | +| `Float` `<:w` `Double` | + +For example, weak conformance is used to determine the type of the following if-expression: + + scala> if (true) 1 else 1d + res2: Double = 1.0 + +In the if-expression shown above, the result type is defined to be the +_weak least upper bound_ of the two types (i.e., the least upper bound with +respect to weak conformance). + +Thus, since `Double` is defined to be the least upper bound with respect to +weak conformance between `Int` and `Double` (according to the spec, shown +above), `Double` is inferred as the type of our example if-expression. + +Note that method `weak_<:<` checks for _weak conformance_ (as opposed to `<:<` +which checks for conformance without taking into consideration weak +conformance relations in section 3.5.3 of the spec) and thus returns the +correct result when inspecting conformance relations between numeric types +`Int` and `Double`: + + scala> typeOf[Int] weak_<:< typeOf[Double] + res3: Boolean = true + + scala> typeOf[Double] weak_<:< typeOf[Int] + res4: Boolean = false + +Whereas using `<:<` would incorrectly report that `Int` and `Double` do not +conform to each other in any way: + + scala> typeOf[Int] <:< typeOf[Double] + res5: Boolean = false + + scala> typeOf[Double] <:< typeOf[Int] + res6: Boolean = false + +#### Type Equality + +Similar to type conformance, one can easily check the _equality_ of two types. +That is, given two arbitrary types, one can use method `=:=` to see if both +denote the exact same compile-time type. + + scala> import scala.reflect.runtime.universe._ + import scala.reflect.runtime.universe._ + + scala> def getType[T: TypeTag](obj: T) = typeOf[T] + getType: [T](obj: T)(implicit evidence$1: scala.reflect.runtime.universe.TypeTag[T])scala.reflect.runtime.universe.Type + + scala> class A + defined class A + + scala> val a1 = new A; val a2 = new A + a1: A = A@cddb2e7 + a2: A = A@2f0c624a + + scala> getType(a1) =:= getType(a2) + res0: Boolean = true + +Note that the _precise type info_ must be the same for both instances. In the +following code snippet, for example, we have two instances of `List` +with different type arguments. + + scala> getType(List(1,2,3)) =:= getType(List(1.0, 2.0, 3.0)) + res1: Boolean = false + + scala> getType(List(1,2,3)) =:= getType(List(9,8,7)) + res2: Boolean = true + +Also important to note is that `=:=` should _always_ be used to compare types +for equality. That is, never use `==`, as it can't check for type equality in +the presence of type aliases, whereas `=:=` can: + + scala> type Histogram = List[Int] + defined type alias Histogram + + scala> typeOf[Histogram] =:= getType(List(4,5,6)) + res3: Boolean = true + + scala> typeOf[Histogram] == getType(List(4,5,6)) + res4: Boolean = false + +As we can see, `==` incorrectly reports that `Histogram` and `List[Int]` have +different types. + +#### Querying Types for Members and Declarations + +Given a `Type`, one can also _query_ it for specific members or declarations. +A `Type`'s _members_ include all fields, methods, type aliases, abstract +types, nested classes/objects/traits, etc. A `Type`'s _declarations_ are only +those members that were declared (not inherited) in the class/trait/object +definition which the given `Type` represents. + +To obtain a `Symbol` for some specific member or declaration, one need only to use methods `members` or `declarations` which provide the list of definitions associated with that type. There also exists singular counterparts for each, methods `member` and `declaration` as well. The signatures of all four are shown below: + + /** The member with given name, either directly declared or inherited, an + * OverloadedSymbol if several exist, NoSymbol if none exist. */ + def member(name: Universe.Name): Universe.Symbol + + /** The defined or declared members with name name in this type; an + * OverloadedSymbol if several exist, NoSymbol if none exist. */ + def declaration(name: Universe.Name): Universe.Symbol + + /** A Scope containing all members of this type + * (directly declared or inherited). */ + def members: Universe.MemberScope // MemberScope is a type of + // Traversable, use higher-order + // functions such as map, + // filter, foreach to query! + + /** A Scope containing the members declared directly on this type. */ + def declarations: Universe.MemberScope // MemberScope is a type of + // Traversable, use higher-order + // functions such as map, + // filter, foreach to query! + +For example, to look up the `map` method of `List`, one can do: + + scala> import scala.reflect.runtime.universe._ + import scala.reflect.runtime.universe._ + + scala> typeOf[List[_]].member("map": TermName) + res0: scala.reflect.runtime.universe.Symbol = method map + +Note that we pass method `member` a `TermName`, since we're looking up a +method. If we were to look up a type member, such as `List`'s self type, `Self`, we +would pass a `TypeName`: + + scala> typeOf[List[_]].member("Self": TypeName) + res1: scala.reflect.runtime.universe.Symbol = type Self + +We can also query all members or declarations on a type in interesting ways. +We can use method `members` to obtain a `Traversable` (`MemberScopeApi` +extends `Traversable`) of `Symbol`s representing all inherited or declared +members on a given type, which means that we can use popular higher-order +functions on collections like `foreach`, `filter`, `map`, etc., to explore our +type's members. For example, to print the members of `List` which are private, +one must simply do: + + scala> typeOf[List[Int]].members.filter(_.isPrivate).foreach(println _) + method super$sameElements + method occCounts + class CombinationsItr + class PermutationsItr + method sequential + method iterateUntilEmpty + +## Trees + +Trees are the basis of Scala's abstract syntax which is used to represent +programs. They are also called abstract syntax trees and commonly abbreviated +as ASTs. + +In Scala reflection, APIs that produce or use trees are the following: + +1. Scala annotations, which use trees to represent their arguments, exposed in `Annotation.scalaArgs` (for more, see the [Annotations]({{ site.baseurl }}/overviews/reflection/annotations-names-scopes.html) section of this guide). +2. `reify`, a special method that takes an expression and returns an AST that represents this expression. +3. Compile-time reflection with macros (outlined in the [Macros guide]({{ site.baseurl }}/overviews/macros/overview.html)) and runtime compilation with toolboxes both use trees as their program representation medium. + +It's important to note that trees are immutable except for three fields-- +`pos` (`Position`), `symbol` (`Symbol`), and `tpe` (`Type`), which are +assigned when a tree is typechecked. + +### Kinds of `Tree`s + +There are three main categories of trees: + +1. **Subclasses of `TermTree`** which represent terms, _e.g.,_ method invocations are represented by `Apply` nodes, object instantiation is achieved using `New` nodes, etc. +2. **Subclasses of `TypTree`** which represent types that are explicitly specified in program source code, _e.g.,_ `List[Int]` is parsed as `AppliedTypeTree`. _Note_: `TypTree` is not misspelled, nor is it conceptually the same as `TypeTree`-- `TypeTree` is something different. That is, in situations where `Type`s are constructed by the compiler (_e.g.,_ during type inference), they can be wrapped in `TypeTree` trees and integrated into the AST of the program. +3. **Subclasses of `SymTree`** which introduce or reference definitions. Examples of the introduction of new definitions include `ClassDef`s which represent class and trait definitions, or `ValDef` which represent field and parameter definitions. Examples of the reference of existing definitions include `Ident`s which refer to an existing definition in the current scope such as a local variable or a method. + +Any other type of tree that one might encounter are typically syntactic or +short-lived constructs. For example, `CaseDef`, which wraps individual match +cases; such nodes are neither terms nor types, nor do they carry a symbol. + +### Inspecting Trees + +Scala Reflection provides a handful of ways to visualize trees, all available +through a universe. Given a tree, one can: + +- use methods `show` or `toString` which print pseudo-Scala code represented by the tree. +- use method `showRaw` to see the raw internal tree that the typechecker operates upon. + +For example, given the following tree: + + scala> import scala.reflect.runtime.universe._ + import scala.reflect.runtime.universe._ + + scala> val tree = Apply(Select(Ident(TermName("x")), TermName("$plus")), List(Literal(Constant(2)))) + tree: scala.reflect.runtime.universe.Apply = x.$plus(2) + +We can use method `show` (or `toString`, which is equivalent) to see what that +tree represents. + + scala> show(tree) + res0: String = x.$plus(2) + +As we can see, `tree` simply adds `2` to term `x`. + +We can also go in the other direction. Given some Scala expression, we can +first obtain a tree, and then use method `showRaw` to see the raw internal +tree that the compiler and typechecker operate on. For example, given the +expression: + + scala> import scala.reflect.runtime.universe._ + import scala.reflect.runtime.universe._ + + scala> val expr = reify { class Flower { def name = "Rose" } } + expr: scala.reflect.runtime.universe.Expr[Unit] = ... + +Here, `reify` simply takes the Scala expression it was passed, and returns a +Scala `Expr`, which is simply wraps a `Tree` and a `TypeTag` (see the +[Expr]({{ site.baseurl }}/overviews/reflection/annotations-names-scopes.html) +section of this guide for more information about `Expr`s). We can obtain +the tree that `expr` contains by: + + scala> val tree = expr.tree + tree: scala.reflect.runtime.universe.Tree = + { + class Flower extends AnyRef { + def () = { + super.(); + () + }; + def name = "Rose" + }; + () + } + +And we can inspect the raw tree by simply doing: + + scala> showRaw(tree) + res1: String = Block(List(ClassDef(Modifiers(), TypeName("Flower"), List(), Template(List(Ident(TypeName("AnyRef"))), emptyValDef, List(DefDef(Modifiers(), nme.CONSTRUCTOR, List(), List(List()), TypeTree(), Block(List(Apply(Select(Super(This(tpnme.EMPTY), tpnme.EMPTY), nme.CONSTRUCTOR), List())), Literal(Constant(())))), DefDef(Modifiers(), TermName("name"), List(), List(), TypeTree(), Literal(Constant("Rose"))))))), Literal(Constant(()))) + +### Traversing Trees + +After one understands the structure of a given tree, typically the next step +is to extract info from it. This is accomplished by _traversing_ the tree, and +it can be done in one of two ways: + +- Traversal via pattern matching. +- Using a subclass of `Traverser` + +#### Traversal via Pattern Matching + +Traversal via pattern matching is the simplest and most common way to traverse +a tree. Typically, one traverses a tree via pattern matching when they are +interested in the state of a given tree at a single node. For example, say we +simply want to obtain the function and the argument of the only `Apply` node +in the following tree: + + scala> import scala.reflect.runtime.universe._ + import scala.reflect.runtime.universe._ + + scala> val tree = Apply(Select(Ident(TermName("x")), TermName("$plus")), List(Literal(Constant(2)))) + tree: scala.reflect.runtime.universe.Apply = x.$plus(2) + +We can simply match on our `tree`, and in the case that we have an `Apply` +node, just return `Apply`'s function and argument: + + scala> val (fun, arg) = tree match { + | case Apply(fn, a :: Nil) => (fn, a) + | } + fun: scala.reflect.runtime.universe.Tree = x.$plus + arg: scala.reflect.runtime.universe.Tree = 2 + +We can achieve exactly the same thing a bit more concisely, by putting the +pattern match on the left-hand side: + + scala> val Apply(fun, arg :: Nil) = tree + fun: scala.reflect.runtime.universe.Tree = x.$plus + arg: scala.reflect.runtime.universe.Tree = 2 + +Note that `Tree`s can typically be quite complex, with nodes nested +arbitrarily deep within other nodes. A simple illustration would be if we were +to add a second `Apply` node to the above tree which serves to add `3` to our +sum: + + scala> val tree = Apply(Select(Apply(Select(Ident(TermName("x")), TermName("$plus")), List(Literal(Constant(2)))), TermName("$plus")), List(Literal(Constant(3)))) + tree: scala.reflect.runtime.universe.Apply = x.$plus(2).$plus(3) + +If we apply the same pattern match as above, we obtain the outer `Apply` node +which contains as its function the entire tree representing `x.$plus(2)` that +we saw above: + + scala> val Apply(fun, arg :: Nil) = tree + fun: scala.reflect.runtime.universe.Tree = x.$plus(2).$plus + arg: scala.reflect.runtime.universe.Tree = 3 + + scala> showRaw(fun) + res3: String = Select(Apply(Select(Ident(TermName("x")), TermName("$plus")), List(Literal(Constant(2)))), TermName("$plus")) + +In cases where one must do some richer task, such as traversing an entire +tree without stopping at a specific node, or collecting and inspecting all +nodes of a specific type, using `Traverser` for traversal might be more +advantageous. + +#### Traversal via `Traverser` + +In situations where it's necessary to traverse an entire tree from top to +bottom, using traversal via pattern matching would be infeasible-- to do it +this way, one must individually handle every type of node that we might come +across in the pattern match. Thus, in these situations, typically class +`Traverser` is used. + +`Traverser` makes sure to visit every node in a given tree, in a depth-first search. + +To use a `Traverser`, simply subclass `Traverser` and override method +`traverse`. In doing so, you can simply provide custom logic to handle only +the cases you're interested in. For example, if, given our +`x.$plus(2).$plus(3)` tree from the previous section, we would like to collect +all `Apply` nodes, we could do: + + scala> import scala.reflect.runtime.universe._ + import scala.reflect.runtime.universe._ + + scala> val tree = Apply(Select(Apply(Select(Ident(TermName("x")), TermName("$plus")), List(Literal(Constant(2)))), TermName("$plus")), List(Literal(Constant(3)))) + tree: scala.reflect.runtime.universe.Apply = x.$plus(2).$plus(3) + + scala> object traverser extends Traverser { + | var applies = List[Apply]() + | override def traverse(tree: Tree): Unit = tree match { + | case app @ Apply(fun, args) => + | applies = app :: applies + | super.traverse(fun) + | super.traverseTrees(args) + | case _ => super.traverse(tree) + | } + | } + defined module traverser + +In the above, we intend to construct a list of `Apply` nodes that we find in +our given tree. + +We achieve this by in effect _adding_ a special case to the already depth-first +`traverse` method defined in superclass `Traverser`, via subclass +`traverser`'s overridden `traverse` method. Our special case affects only +nodes that match the pattern `Apply(fun, args)`, where `fun` is some function +(represented by a `Tree`) and `args` is a list of arguments (represented by a +list of `Tree`s). + +When a the tree matches the pattern (_i.e.,_ when we have an `Apply` node), we +simply add it to our `List[Apply]`, `applies`, and continue our traversal. + +Note that, in our match, we call `super.traverse` on the function `fun` +wrapped in our `Apply`, and we call `super.traverseTrees` on our argument list +`args` (essentially the same as `super.traverse`, but for `List[Tree]` rather +than a single `Tree`). In both of these calls, our objective is simple-- we +want to make sure that we use the default `traverse` method in `Traverser` +because we don't know whether the `Tree` that represents fun contains our +`Apply` pattern-- that is, we want to traverse the entire sub-tree. Since the +`Traverser` superclass calls `this.traverse`, passing in every nested sub- +tree, eventually our custom `traverse` method is guaranteed to be called for +each sub-tree that matches our `Apply` pattern. + +To trigger the `traverse` and to see the resulting `List` of matching `Apply` +nodes, simply do: + + scala> traverser.traverse(tree) + + scala> traverser.applies + res0: List[scala.reflect.runtime.universe.Apply] = List(x.$plus(2), x.$plus(2).$plus(3)) + +### Creating Trees + +When working with runtime reflection, one need not construct trees manually. +However, runtime compilation with toolboxes and compile-time reflection with +macros both use trees as their program representation medium. In these cases, +there are three recommended ways to create trees: + +1. Via method `reify` (should be preferred wherever possible). +2. Via method `parse` on `ToolBox`es. +3. Manual construction (not recommended). + +#### Tree Creation via `reify` + +Method `reify` simply takes a Scala expression as an argument, and produces +that argument's typed `Tree` representation as a result. + +Tree creation via method `reify` is the recommended way of creating trees in +Scala Reflection. To see why, let's start with a small example: + + scala> import scala.reflect.runtime.universe._ + import scala.reflect.runtime.universe._ + + scala> { val tree = reify(println(2)).tree; showRaw(tree) } + res0: String = Apply(Select(Select(This(TypeName("scala")), TermName("Predef")), TermName("println")), List(Literal(Constant(2)))) + +Here, we simply `reify` the call to `println(2)`-- that is, we convert the +expression `println(2)` to its corresponding tree representation. Then we +output the raw tree. Note that the `println` method was transformed to +`scala.Predef.println`. Such transformations ensure that regardless of where +the result of `reify` is used, it will not unexpectedly change its meaning. +For example, even if this `println(2)` snippet is later inserted into a block +of code that defines its own `println`, it wouldn't affect the behavior of the +snippet. + +This way of creating trees is thus _hygenic_, in the sense that it preserves +bindings of identifiers. + +##### Splicing Trees + +Using `reify` also allows one to compose trees from smaller trees. This is +done using `Expr.splice`. + +_Note:_ `Expr` is `reify`'s return type. It can be thought of as a simple +wrapper which contains a _typed_ `Tree`, a `TypeTag` and a handful of +reification-relevant methods, such as `splice`. For more information about +`Expr`s, see +[the relevant section of this guide]({{ site.baseurl}}/overviews/reflection/annotations-names-scopes.html). + +For example, let's try to construct a tree representing `println(2)` using +`splice`: + + scala> val x = reify(2) + x: scala.reflect.runtime.universe.Expr[Int(2)] = Expr[Int(2)](2) + + scala> reify(println(x.splice)) + res1: scala.reflect.runtime.universe.Expr[Unit] = Expr[Unit](scala.this.Predef.println(2)) + +Here, we `reify` `2` and `println` separately, and simply `splice` one into +the other. + +Note, however, that there is a requirement for the argument of `reify` to be +valid and typeable Scala code. If instead of the argument to `println` we +wanted to abstract over the `println` itself, it wouldn't be possible: + + scala> val fn = reify(println) + fn: scala.reflect.runtime.universe.Expr[Unit] = Expr[Unit](scala.this.Predef.println()) + + scala> reify(fn.splice(2)) + :12: error: Unit does not take parameters + reify(fn.splice(2)) + ^ + +As we can see, the compiler assumes that we wanted to reify a call to +`println` with no arguments, when what we really wanted was to capture the +name of the function to be called. + +These types of use-cases are currently inexpressible when using `reify`. + +#### Tree Creation via `parse` on `ToolBox`es + +`Toolbox`es can be used to typecheck, compile, and execute abstract syntax +trees. A toolbox can also be used to parse a string into an AST. + +_Note:_ Using toolboxes requires `scala-compiler.jar` to be on the classpath. + +Let's see how `parse` deals with the `println` example from the previous +section: + + scala> import scala.reflect.runtime.universe._ + import scala.reflect.runtime.universe._ + + scala> import scala.tools.reflect.ToolBox + import scala.tools.reflect.ToolBox + + scala> val tb = runtimeMirror(getClass.getClassLoader).mkToolBox() + tb: scala.tools.reflect.ToolBox[scala.reflect.runtime.universe.type] = scala.tools.reflect.ToolBoxFactory$ToolBoxImpl@7bc979dd + + scala> showRaw(tb.parse("println(2)")) + res2: String = Apply(Ident(TermName("println")), List(Literal(Constant(2)))) + +It's important to note that, unlike `reify`, toolboxes aren't limited by the +typeability requirement-- although this flexibility is achieved by sacrificing +robustness. That is, here we can see that `parse`, unlike `reify`, doesn’t +reflect the fact that `println` should be bound to the standard `println` +method. + +_Note:_ when using macros, one shouldn’t use `ToolBox.parse`. This is because +there’s already a `parse` method built into the macro context. For example: + + scala> import scala.language.experimental.macros + import scala.language.experimental.macros + + scala> def impl(c: scala.reflect.macros.Context) = c.Expr[Unit](c.parse("println(2)")) + impl: (c: scala.reflect.macros.Context)c.Expr[Unit] + + scala> def test = macro impl + test: Unit + + scala> test + 2 + +##### Typechecking with ToolBoxes + +As earlier alluded to, `ToolBox`es enable one to do more than just +constructing trees from strings. They can also be used to typecheck, compile, +and execute trees. + +In addition to outlining the structure of the program, trees also hold +important information about the semantics of the program encoded in `symbol` +(a symbol assigned to trees that introduce or reference definitions), and +`tpe` (the type of the tree). By default these fields are empty, but +typechecking fills them in. + +When using the runtime reflection framework, typechecking is implemented by +`ToolBox.typeCheck`. When using macros, at compile time one can use the +`Context.typeCheck` method. + + scala> import scala.reflect.runtime.universe._ + import scala.reflect.runtime.universe._ + + scala> val tree = reify { "test".length }.tree + tree: scala.reflect.runtime.universe.Tree = "test".length() + + scala> import scala.tools.reflect.ToolBox + import scala.tools.reflect.ToolBox + + scala> val tb = runtimeMirror(getClass.getClassLoader).mkToolBox() + tb: scala.tools.reflect.ToolBox[scala.reflect.runtime.universe.type] = ... + + scala> val ttree = tb.typeCheck(tree) + ttree: tb.u.Tree = "test".length() + + scala> ttree.tpe + res5: tb.u.Type = Int + + scala> ttree.symbol + res6: tb.u.Symbol = method length + +Here, we simply create a tree that represents a call to `"test".length`, and +use `ToolBox` `tb`'s `typeCheck` method to typecheck the tree. As we can see, +`ttree` gets the correct type, `Int`, and its `Symbol` is correctly set. + +#### Tree Creation via Manual Construction + +If all else fails, one can manually construct trees. This is the most low-level +way to create trees, and it should only be attempted if no other +approach works. It sometimes offers greater flexibility when compared with +`parse`, though this flexibility is achieved at a cost of excessive verbosity +and fragility. + +Our earlier example involving `println(2)` can be manually constructed as +follows: + + scala> Apply(Ident(TermName("println")), List(Literal(Constant(2)))) + res0: scala.reflect.runtime.universe.Apply = println(2) + +The canonical use case for this technique is when the target tree needs to be +assembled from dynamically created parts, which don’t make sense in isolation +from one another. In that case, `reify` will most likely be inapplicable, +because it requires its argument to be typeable. `parse` might not work +either, since quite often, trees are assembled on sub-expression level, with +individual parts being inexpressible as Scala sources. diff --git a/_overviews/reflection/thread-safety.md b/_overviews/reflection/thread-safety.md new file mode 100644 index 0000000000..a798e13a31 --- /dev/null +++ b/_overviews/reflection/thread-safety.md @@ -0,0 +1,59 @@ +--- +layout: multipage-overview +title: Thread Safety + +discourse: true + +partof: reflection +overview-name: Reflection + +num: 6 + +languages: [ja] +permalink: /overviews/reflection/:title.html +--- + +EXPERIMENTAL + +**Eugene Burmako** + +Unfortunately, in its current state released in Scala 2.10.0, reflection is not thread safe. +There's a JIRA issue [SI-6240](https://issues.scala-lang.org/browse/SI-6240), which can be used to track our progress +and to look up technical details, and here's a concise summary of the state of the art. + +

      NEW Thread safety issues have been fixed in Scala 2.11.0-RC1, but we are going to keep this document available for now, since the problem still remains in the Scala 2.10.x series, and we currently don't have concrete plans on when the fix is going to be backported.

      + +Currently we know about two kinds of races associated with reflection. First of all, reflection initialization (the code that is called +when `scala.reflect.runtime.universe` is accessed for the first time) cannot be safely called from multiple threads. Secondly, symbol +initialization (the code that is called when symbol's flags or type signature are accessed for the first time) isn't safe as well. +Here's a typical manifestation: + + java.lang.NullPointerException: + at s.r.i.Types$TypeRef.computeHashCode(Types.scala:2332) + at s.r.i.Types$UniqueType.(Types.scala:1274) + at s.r.i.Types$TypeRef.(Types.scala:2315) + at s.r.i.Types$NoArgsTypeRef.(Types.scala:2107) + at s.r.i.Types$ModuleTypeRef.(Types.scala:2078) + at s.r.i.Types$PackageTypeRef.(Types.scala:2095) + at s.r.i.Types$TypeRef$.apply(Types.scala:2516) + at s.r.i.Types$class.typeRef(Types.scala:3577) + at s.r.i.SymbolTable.typeRef(SymbolTable.scala:13) + at s.r.i.Symbols$TypeSymbol.newTypeRef(Symbols.scala:2754) + +Good news is that compile-time reflection (the one exposed to macros via `scala.reflect.macros.Context`) is much less susceptible to +threading problems than runtime reflection (the one exposed via `scala.reflect.runtime.universe`). The first reason is that by the time +macros get chance to run, compile-time reflective universe are already initialized, which rules our the race condition #1. The second reason +is that the compiler has never been thread-safe, so there are no tools, which expect is to run in parallel. Nevertheless, if your macro +spawns multiple threads you should still be careful. + +It's much worse for runtime reflection though. Reflection init is called the first time when `scala.reflect.runtime.universe` is initialized, +and this initialization can happen in an indirect fashion. The most prominent example here is that calling methods with `TypeTag` context bounds +is potentially problematic, because to call such a method Scala typically needs to construct an autogenerated type tag, which needs to create +a type, which needs to initialize the reflective universe. A corollary is that if you don't take special measures, you can't call reliably +use `TypeTag`-based methods in tests, because a lot of tools, e.g. sbt, run tests in parallel. + +Bottom line: +* If you're writing a macro, which doesn't explicitly create threads, you're perfectly fine. +* Runtime reflection mixed with threads or actors might be dangerous. +* Multiple threads calling methods with `TypeTag` context bounds might lead to non-deterministic results. +* Check out [SI-6240](https://issues.scala-lang.org/browse/SI-6240) to see our progress with this issue. diff --git a/_overviews/reflection/typetags-manifests.md b/_overviews/reflection/typetags-manifests.md new file mode 100644 index 0000000000..17a5de00bc --- /dev/null +++ b/_overviews/reflection/typetags-manifests.md @@ -0,0 +1,165 @@ +--- +layout: multipage-overview +title: TypeTags and Manifests + +discourse: true + +partof: reflection +overview-name: Reflection + +num: 5 + +languages: [ja] +permalink: /overviews/reflection/:title.html +--- + +As with other JVM languages, Scala’s types are erased at compile time. This +means that if you were to inspect the runtime type of some instance, you +might not have access to all type information that the Scala compiler has +available at compile time. + +Like `scala.reflect.Manifest`, `TypeTags` can be thought of as objects which +carry along all type information available at compile time, to runtime. For +example, `TypeTag[T]` encapsulates the runtime type representation of some +compile-time type `T`. Note however, that `TypeTag`s should be considered to +be a richer replacement of the pre-2.10 notion of a `Manifest`, that are +additionally fully integrated with Scala reflection. + +There exist three different types of TypeTags: + +1. `scala.reflect.api.TypeTags#TypeTag`. +A full type descriptor of a Scala type. For example, a `TypeTag[List[String]]` contains all type information, in this case, of type `scala.List[String]`. + +2. `scala.reflect.ClassTag`. +A partial type descriptor of a Scala type. For example, a `ClassTag[List[String]]` contains only the erased class type information, in this case, of type `scala.collection.immutable.List`. `ClassTag`s provide access only to the runtime class of a type. Analogous to `scala.reflect.ClassManifest`. + +3. `scala.reflect.api.TypeTags#WeakTypeTag`. +A type descriptor for abstract types (see corresponding subsection below). + +## Obtaining a `TypeTag` + +Like `Manifest`s, `TypeTag`s are always generated by the compiler, and can be obtained in three ways. + +### via the Methods `typeTag`, `classTag`, or `weakTypeTag` + +One can directly obtain a `TypeTag` for a specific type by simply using +method `typeTag`, available through `Universe`. + +For example, to obtain a `TypeTag` which represents `Int`, we can do: + + import scala.reflect.runtime.universe._ + val tt = typeTag[Int] + +Or likewise, to obtain a `ClassTag` which represents `String`, we can do: + + import scala.reflect._ + val ct = classTag[String] + +Each of these methods constructs a `TypeTag[T]` or `ClassTag[T]` for the given +type argument `T`. + +### Using an Implicit Parameter of Type `TypeTag[T]`, `ClassTag[T]`, or `WeakTypeTag[T]` + +As with `Manifest`s, one can in effect _request_ that the compiler generate a +`TypeTag`. This is done by simply specifying an implicit _evidence_ parameter +of type `TypeTag[T]`. If the compiler fails to find a matching implicit value +during implicit search, it will automatically generate a `TypeTag[T]`. + +_Note_: this is typically achieved by using an implicit parameter on methods +and classes only. + +For example, we can write a method which takes some arbitrary object, and +using a `TypeTag`, prints information about that object's type arguments: + + import scala.reflect.runtime.universe._ + + def paramInfo[T](x: T)(implicit tag: TypeTag[T]): Unit = { + val targs = tag.tpe match { case TypeRef(_, _, args) => args } + println(s"type of $x has type arguments $targs") + } + +Here, we write a generic method `paramInfo` parameterized on `T`, and we +supply an implicit parameter `(implicit tag: TypeTag[T])`. We can then +directly access the type (of type `Type`) that `tag` represents using method +`tpe` of `TypeTag`. + +We can then use our method `paramInfo` as follows: + + scala> paramInfo(42) + type of 42 has type arguments List() + + scala> paramInfo(List(1, 2)) + type of List(1, 2) has type arguments List(Int) + +### Using a Context bound of a Type Parameter + +A less verbose way to achieve exactly the same as above is by using a context +bound on a type parameter. Instead of providing a separate implicit parameter, +one can simply include the `TypeTag` in the type parameter list as follows: + + def myMethod[T: TypeTag] = ... + +Given context bound `[T: TypeTag]`, the compiler will simply generate an +implicit parameter of type `TypeTag[T]` and will rewrite the method to look +like the example with the implicit parameter in the previous section. + +The above example rewritten to use context bounds is as follows: + + import scala.reflect.runtime.universe._ + + def paramInfo[T: TypeTag](x: T): Unit = { + val targs = typeOf[T] match { case TypeRef(_, _, args) => args } + println(s"type of $x has type arguments $targs") + } + + scala> paramInfo(42) + type of 42 has type arguments List() + + scala> paramInfo(List(1, 2)) + type of List(1, 2) has type arguments List(Int) + +## WeakTypeTags + +`WeakTypeTag[T]` generalizes `TypeTag[T]`. Unlike a regular `TypeTag`, +components of its type representation can be references to type parameters or +abstract types. However, `WeakTypeTag[T]` tries to be as concrete as possible, +_i.e.,_ if type tags are available for the referenced type arguments or abstract +types, they are used to embed the concrete types into the `WeakTypeTag[T]`. + +Continuing the example above: + + def weakParamInfo[T](x: T)(implicit tag: WeakTypeTag[T]): Unit = { + val targs = tag.tpe match { case TypeRef(_, _, args) => args } + println(s"type of $x has type arguments $targs") + } + + scala> def foo[T] = weakParamInfo(List[T]()) + foo: [T]=> Unit + + scala> foo[Int] + type of List() has type arguments List(T) + +## TypeTags and Manifests + +`TypeTag`s correspond loosely to the pre-2.10 notion of +`scala.reflect.Manifest`s. While `scala.reflect.ClassTag` corresponds to +`scala.reflect.ClassManifest` and `scala.reflect.api.TypeTags#TypeTag` mostly +corresponds to `scala.reflect.Manifest`, other pre-2.10 Manifest types do not +have a direct correspondence with a 2.10 "`Tag`" type. + +- **scala.reflect.OptManifest is not supported.** +This is because `Tag`s can reify arbitrary types, so they are always available. + +- **There is no equivalent for scala.reflect.AnyValManifest.** +Instead, one can compare their `Tag` with one of the base `Tag`s (defined in the corresponding companion objects) in order to find out whether or not it represents a primitive value class. Additionally, it's possible to simply use `.tpe.typeSymbol.isPrimitiveValueClass`. + +- **There are no replacement for factory methods defined in the Manifest companion objects.** +Instead, one could generate corresponding types using the reflection APIs provided by Java (for classes) and Scala (for types). + +- **Certain manifest operations(i.e., `<:<`, `>:>` and `typeArguments`) are not supported.** +Instead, one could use the reflection APIs provided by Java (for classes) and Scala (for types). + +In Scala 2.10, `scala.reflect.ClassManifest` are deprecated, and it is +planned to deprecate `scala.reflect.Manifest` in favor of `TypeTag`s and +`ClassTag`s in an upcoming point release. Thus, it is advisable to migrate any +`Manifest`-based APIs to use `Tag`s. diff --git a/overviews/repl/embedding.md b/_overviews/repl/embedding.md similarity index 95% rename from overviews/repl/embedding.md rename to _overviews/repl/embedding.md index e3bd1a25f4..25f471f628 100644 --- a/overviews/repl/embedding.md +++ b/_overviews/repl/embedding.md @@ -1,12 +1,14 @@ --- -layout: overview-large +layout: multipage-overview title: Programmatic Use of Scala REPL discourse: true partof: repl +overview-name: REPL + num: 2 -outof: 2 +permalink: /overviews/repl/:title.html --- The REPL can be embedded and invoked programmatically. @@ -58,4 +60,3 @@ as either a code interpreter or an interactive command line. val out = ILoop.run(code) println(s"Output is $out") } - diff --git a/_overviews/repl/overview.md b/_overviews/repl/overview.md new file mode 100644 index 0000000000..428cf53bdf --- /dev/null +++ b/_overviews/repl/overview.md @@ -0,0 +1,61 @@ +--- +layout: multipage-overview +title: Overview + +discourse: true + +partof: repl +overview-name: REPL + +num: 1 +permalink: /overviews/repl/:title.html +--- + +The Scala REPL is a tool (_scala_) for evaluating expressions in Scala. + +The _scala_ command will execute a source script by wrapping it in a template and +then compiling and executing the resulting program. + +In interactive mode, the REPL reads expressions at the prompt, wraps them in +an executable template, and then compiles and executes the result. + +Previous results are automatically imported into the scope of the current +expression as required. + +The REPL also provides some command facilities, described below. + +An alternative REPL is available in [the Ammonite project](https://github.com/lihaoyi/Ammonite), +which also provides a richer shell environment. + +Useful REPL features include: + + - the REPL's IMain is bound to `$intp`. + - the REPL's last exception is bound to `lastException`. + - use tab for completion. + - use `//print` to show typed desugarings. + - use `:help` for a list of commands. + - use `:paste` to enter a class and object as companions. + - use `:paste -raw` to disable code wrapping, to define a package. + - use `:javap` to inspect class artifacts. + - use `-Yrepl-outdir` to inspect class artifacts with external tools. + - use `:power` to enter power mode and import compiler components. + +Implementation notes: + + - user code can be wrapped in either an object (so that the code runs during class initialization) + or a class (so that the code runs during instance construction). The switch is `-Yrepl-class-based`. + +### Power Mode + +`:power` mode imports identifiers from the interpreter's compiler. + +That is analogous to importing from the runtime reflective context using `import reflect.runtime._, universe._`. + +Power mode also offers some utility methods as documented in the welcome banner. + +Its facilities can be witnessed using `:imports` or `-Xprint:parser`. + +### Contributing to Scala REPL + +The REPL source is part of the Scala project. Issues are tracked by the standard +mechanism for the project and pull requests are accepted at [the github repository](https://github.com/scala/scala). diff --git a/_overviews/scaladoc/basics.md b/_overviews/scaladoc/basics.md new file mode 100644 index 0000000000..4dc91dad79 --- /dev/null +++ b/_overviews/scaladoc/basics.md @@ -0,0 +1,6 @@ +--- +layout: inner-page-no-masthead +sitemap: false +permalink: /overviews/scaladoc/basics.html +redirect_to: /overviews/scaladoc/for-library-authors.html +--- diff --git a/overviews/scaladoc/for-library-authors.md b/_overviews/scaladoc/for-library-authors.md similarity index 98% rename from overviews/scaladoc/for-library-authors.md rename to _overviews/scaladoc/for-library-authors.md index 1693d7cc69..0fd4a40a08 100644 --- a/overviews/scaladoc/for-library-authors.md +++ b/_overviews/scaladoc/for-library-authors.md @@ -1,12 +1,15 @@ --- -layout: overview-large +layout: multipage-overview title: Scaladoc for Library Authors discourse: true partof: scaladoc +overview-name: Scaladoc + num: 2 -outof: 3 + +permalink: /overviews/scaladoc/:title.html --- Scaladoc is a documentation system that lives in the comments of Scala source code diff --git a/overviews/scaladoc/interface.md b/_overviews/scaladoc/interface.md similarity index 96% rename from overviews/scaladoc/interface.md rename to _overviews/scaladoc/interface.md index f95027ace5..f64417c2be 100644 --- a/overviews/scaladoc/interface.md +++ b/_overviews/scaladoc/interface.md @@ -1,12 +1,15 @@ --- -layout: overview-large +layout: multipage-overview title: Using the Scaladoc Interface discourse: true partof: scaladoc +overview-name: Scaladoc + num: 3 -outof: 3 + +permalink: /overviews/scaladoc/:title.html --- Many Scala developers, including those with a great deal of experience, are diff --git a/_overviews/scaladoc/overview.md b/_overviews/scaladoc/overview.md new file mode 100644 index 0000000000..3aa7de3d34 --- /dev/null +++ b/_overviews/scaladoc/overview.md @@ -0,0 +1,34 @@ +--- +layout: multipage-overview +title: Overview + +discourse: true + +partof: scaladoc +overview-name: Scaladoc + +num: 1 + +permalink: /overviews/scaladoc/:title.html +--- + +Scaladoc is Scala's main documentation _tool_. Scaladoc is a documentation +system that lives in the comments of Scala source code and which generates +documentation related to the code structure within which it is written. It is +based on other comment based documentation systems like Javadoc. + +There are two flavors of Scaladoc documentation: + + - **[Using the Scaladoc interface](/overviews/scaladoc/interface.html)** – how to navigate and use generated Scaladoc documentation to learn more about a library. + - **[Generating documentation for your library with Scaladoc](/overviews/scaladoc/for-library-authors.html)** – how to use Scaladoc to generate documentation for your library. + +### Contributing to Scaladoc + +If you are interested in contributing to the API documentation of the Scala +standard library (the documentation generated by Scaladoc), please read the +[Scaladoc for Library Authors](/overviews/scaladoc/basics.html) first. + +If you'd like to contribute to the actual Scaladoc documentation generation +tool itself, then please see the +[Hacker Set Up Guide](http://scala-lang.org/contribute/hacker-guide.html#2_set_up) +which covers the steps and workflow necessary work on the Scaladoc tool. diff --git a/overviews/scaladoc/usage.md b/_overviews/scaladoc/usage.md similarity index 78% rename from overviews/scaladoc/usage.md rename to _overviews/scaladoc/usage.md index cba304c11f..e1efd956f5 100644 --- a/overviews/scaladoc/usage.md +++ b/_overviews/scaladoc/usage.md @@ -1,5 +1,5 @@ --- -layout: redirected +layout: inner-page-no-masthead sitemap: false permalink: /overviews/scaladoc/usage.html redirect_to: /overviews/scaladoc/interface.html diff --git a/tutorials/scala-for-csharp-programmers.disabled.html b/_overviews/tutorials/scala-for-csharp-programmers.disabled.html similarity index 100% rename from tutorials/scala-for-csharp-programmers.disabled.html rename to _overviews/tutorials/scala-for-csharp-programmers.disabled.html diff --git a/_overviews/tutorials/scala-for-java-programmers.md b/_overviews/tutorials/scala-for-java-programmers.md new file mode 100644 index 0000000000..eb9ad8776b --- /dev/null +++ b/_overviews/tutorials/scala-for-java-programmers.md @@ -0,0 +1,725 @@ +--- +layout: singlepage-overview +title: A Scala Tutorial for Java Programmers + +partof: scala-for-java-programmers + +discourse: true + +languages: [es, ko, de, it, zh-tw] +permalink: /tutorials/:title.html +--- + +By Michel Schinz and Philipp Haller + +## Introduction + +This document gives a quick introduction to the Scala language and +compiler. It is intended for people who already have some programming +experience and want an overview of what they can do with Scala. A +basic knowledge of object-oriented programming, especially in Java, is +assumed. + +## A First Example + +As a first example, we will use the standard *Hello world* program. It +is not very fascinating but makes it easy to demonstrate the use of +the Scala tools without knowing too much about the language. Here is +how it looks: + + object HelloWorld { + def main(args: Array[String]) { + println("Hello, world!") + } + } + +The structure of this program should be familiar to Java programmers: +it consists of one method called `main` which takes the command +line arguments, an array of strings, as parameter; the body of this +method consists of a single call to the predefined method `println` +with the friendly greeting as argument. The `main` method does not +return a value (it is a procedure method). Therefore, it is not necessary +to declare a return type. + +What is less familiar to Java programmers is the `object` +declaration containing the `main` method. Such a declaration +introduces what is commonly known as a *singleton object*, that +is a class with a single instance. The declaration above thus declares +both a class called `HelloWorld` and an instance of that class, +also called `HelloWorld`. This instance is created on demand, +the first time it is used. + +The astute reader might have noticed that the `main` method is +not declared as `static` here. This is because static members +(methods or fields) do not exist in Scala. Rather than defining static +members, the Scala programmer declares these members in singleton +objects. + +### Compiling the example + +To compile the example, we use `scalac`, the Scala compiler. `scalac` +works like most compilers: it takes a source file as argument, maybe +some options, and produces one or several object files. The object +files it produces are standard Java class files. + +If we save the above program in a file called +`HelloWorld.scala`, we can compile it by issuing the following +command (the greater-than sign `>` represents the shell prompt +and should not be typed): + + > scalac HelloWorld.scala + +This will generate a few class files in the current directory. One of +them will be called `HelloWorld.class`, and contains a class +which can be directly executed using the `scala` command, as the +following section shows. + +### Running the example + +Once compiled, a Scala program can be run using the `scala` command. +Its usage is very similar to the `java` command used to run Java +programs, and accepts the same options. The above example can be +executed using the following command, which produces the expected +output: + + > scala -classpath . HelloWorld + + Hello, world! + +## Interaction with Java + +One of Scala's strengths is that it makes it very easy to interact +with Java code. All classes from the `java.lang` package are +imported by default, while others need to be imported explicitly. + +Let's look at an example that demonstrates this. We want to obtain +and format the current date according to the conventions used in a +specific country, say France. (Other regions such as the +French-speaking part of Switzerland use the same conventions.) + +Java's class libraries define powerful utility classes, such as +`Date` and `DateFormat`. Since Scala interoperates +seemlessly with Java, there is no need to implement equivalent +classes in the Scala class library--we can simply import the classes +of the corresponding Java packages: + + import java.util.{Date, Locale} + import java.text.DateFormat + import java.text.DateFormat._ + + object FrenchDate { + def main(args: Array[String]) { + val now = new Date + val df = getDateInstance(LONG, Locale.FRANCE) + println(df format now) + } + } + +Scala's import statement looks very similar to Java's equivalent, +however, it is more powerful. Multiple classes can be imported from +the same package by enclosing them in curly braces as on the first +line. Another difference is that when importing all the names of a +package or class, one uses the underscore character (`_`) instead +of the asterisk (`*`). That's because the asterisk is a valid +Scala identifier (e.g. method name), as we will see later. + +The import statement on the third line therefore imports all members +of the `DateFormat` class. This makes the static method +`getDateInstance` and the static field `LONG` directly +visible. + +Inside the `main` method we first create an instance of Java's +`Date` class which by default contains the current date. Next, we +define a date format using the static `getDateInstance` method +that we imported previously. Finally, we print the current date +formatted according to the localized `DateFormat` instance. This +last line shows an interesting property of Scala's syntax. Methods +taking one argument can be used with an infix syntax. That is, the +expression + + df format now + +is just another, slightly less verbose way of writing the expression + + df.format(now) + +This might seem like a minor syntactic detail, but it has important +consequences, one of which will be explored in the next section. + +To conclude this section about integration with Java, it should be +noted that it is also possible to inherit from Java classes and +implement Java interfaces directly in Scala. + +## Everything is an Object + +Scala is a pure object-oriented language in the sense that +*everything* is an object, including numbers or functions. It +differs from Java in that respect, since Java distinguishes +primitive types (such as `boolean` and `int`) from reference +types, and does not enable one to manipulate functions as values. + +### Numbers are objects + +Since numbers are objects, they also have methods. And in fact, an +arithmetic expression like the following: + + 1 + 2 * 3 / x + +consists exclusively of method calls, because it is equivalent to the +following expression, as we saw in the previous section: + + (1).+(((2).*(3))./(x)) + +This also means that `+`, `*`, etc. are valid identifiers +in Scala. + +The parentheses around the numbers in the second version are necessary +because Scala's lexer uses a longest match rule for tokens. +Therefore, it would break the following expression: + + 1.+(2) + +into the tokens `1.`, `+`, and `2`. The reason that +this tokenization is chosen is because `1.` is a longer valid +match than `1`. The token `1.` is interpreted as the +literal `1.0`, making it a `Double` rather than an +`Int`. Writing the expression as: + + (1).+(2) + +prevents `1` from being interpreted as a `Double`. + +### Functions are objects + +Perhaps more surprising for the Java programmer, functions are also +objects in Scala. It is therefore possible to pass functions as +arguments, to store them in variables, and to return them from other +functions. This ability to manipulate functions as values is one of +the cornerstone of a very interesting programming paradigm called +*functional programming*. + +As a very simple example of why it can be useful to use functions as +values, let's consider a timer function whose aim is to perform some +action every second. How do we pass it the action to perform? Quite +logically, as a function. This very simple kind of function passing +should be familiar to many programmers: it is often used in +user-interface code, to register call-back functions which get called +when some event occurs. + +In the following program, the timer function is called +`oncePerSecond`, and it gets a call-back function as argument. +The type of this function is written `() => Unit` and is the type +of all functions which take no arguments and return nothing (the type +`Unit` is similar to `void` in C/C++). The main function of +this program simply calls this timer function with a call-back which +prints a sentence on the terminal. In other words, this program +endlessly prints the sentence "time flies like an arrow" every +second. + + object Timer { + def oncePerSecond(callback: () => Unit) { + while (true) { callback(); Thread sleep 1000 } + } + def timeFlies() { + println("time flies like an arrow...") + } + def main(args: Array[String]) { + oncePerSecond(timeFlies) + } + } + +Note that in order to print the string, we used the predefined method +`println` instead of using the one from `System.out`. + +#### Anonymous functions + +While this program is easy to understand, it can be refined a bit. +First of all, notice that the function `timeFlies` is only +defined in order to be passed later to the `oncePerSecond` +function. Having to name that function, which is only used once, might +seem unnecessary, and it would in fact be nice to be able to construct +this function just as it is passed to `oncePerSecond`. This is +possible in Scala using *anonymous functions*, which are exactly +that: functions without a name. The revised version of our timer +program using an anonymous function instead of *timeFlies* looks +like that: + + object TimerAnonymous { + def oncePerSecond(callback: () => Unit) { + while (true) { callback(); Thread sleep 1000 } + } + def main(args: Array[String]) { + oncePerSecond(() => + println("time flies like an arrow...")) + } + } + +The presence of an anonymous function in this example is revealed by +the right arrow `=>` which separates the function's argument +list from its body. In this example, the argument list is empty, as +witnessed by the empty pair of parenthesis on the left of the arrow. +The body of the function is the same as the one of `timeFlies` +above. + +## Classes + +As we have seen above, Scala is an object-oriented language, and as +such it has a concept of class. (For the sake of completeness, + it should be noted that some object-oriented languages do not have + the concept of class, but Scala is not one of them.) +Classes in Scala are declared using a syntax which is close to +Java's syntax. One important difference is that classes in Scala can +have parameters. This is illustrated in the following definition of +complex numbers. + + class Complex(real: Double, imaginary: Double) { + def re() = real + def im() = imaginary + } + +This `Complex` class takes two arguments, which are the real and +imaginary part of the complex. These arguments must be passed when +creating an instance of class `Complex`, as follows: `new + Complex(1.5, 2.3)`. The class contains two methods, called `re` +and `im`, which give access to these two parts. + +It should be noted that the return type of these two methods is not +given explicitly. It will be inferred automatically by the compiler, +which looks at the right-hand side of these methods and deduces that +both return a value of type `Double`. + +The compiler is not always able to infer types like it does here, and +there is unfortunately no simple rule to know exactly when it will be, +and when not. In practice, this is usually not a problem since the +compiler complains when it is not able to infer a type which was not +given explicitly. As a simple rule, beginner Scala programmers should +try to omit type declarations which seem to be easy to deduce from the +context, and see if the compiler agrees. After some time, the +programmer should get a good feeling about when to omit types, and +when to specify them explicitly. + +### Methods without arguments + +A small problem of the methods `re` and `im` is that, in +order to call them, one has to put an empty pair of parenthesis after +their name, as the following example shows: + + object ComplexNumbers { + def main(args: Array[String]) { + val c = new Complex(1.2, 3.4) + println("imaginary part: " + c.im()) + } + } + +It would be nicer to be able to access the real and imaginary parts +like if they were fields, without putting the empty pair of +parenthesis. This is perfectly doable in Scala, simply by defining +them as methods *without arguments*. Such methods differ from +methods with zero arguments in that they don't have parenthesis after +their name, neither in their definition nor in their use. Our +`Complex` class can be rewritten as follows: + + class Complex(real: Double, imaginary: Double) { + def re = real + def im = imaginary + } + + +### Inheritance and overriding + +All classes in Scala inherit from a super-class. When no super-class +is specified, as in the `Complex` example of previous section, +`scala.AnyRef` is implicitly used. + +It is possible to override methods inherited from a super-class in +Scala. It is however mandatory to explicitly specify that a method +overrides another one using the `override` modifier, in order to +avoid accidental overriding. As an example, our `Complex` class +can be augmented with a redefinition of the `toString` method +inherited from `Object`. + + class Complex(real: Double, imaginary: Double) { + def re = real + def im = imaginary + override def toString() = + "" + re + (if (im < 0) "" else "+") + im + "i" + } + + +## Case Classes and Pattern Matching + +A kind of data structure that often appears in programs is the tree. +For example, interpreters and compilers usually represent programs +internally as trees; XML documents are trees; and several kinds of +containers are based on trees, like red-black trees. + +We will now examine how such trees are represented and manipulated in +Scala through a small calculator program. The aim of this program is +to manipulate very simple arithmetic expressions composed of sums, +integer constants and variables. Two examples of such expressions are +`1+2` and `(x+x)+(7+y)`. + +We first have to decide on a representation for such expressions. The +most natural one is the tree, where nodes are operations (here, the +addition) and leaves are values (here constants or variables). + +In Java, such a tree would be represented using an abstract +super-class for the trees, and one concrete sub-class per node or +leaf. In a functional programming language, one would use an algebraic +data-type for the same purpose. Scala provides the concept of +*case classes* which is somewhat in between the two. Here is how +they can be used to define the type of the trees for our example: + + abstract class Tree + case class Sum(l: Tree, r: Tree) extends Tree + case class Var(n: String) extends Tree + case class Const(v: Int) extends Tree + +The fact that classes `Sum`, `Var` and `Const` are +declared as case classes means that they differ from standard classes +in several respects: + +- the `new` keyword is not mandatory to create instances of + these classes (i.e., one can write `Const(5)` instead of + `new Const(5)`), +- getter functions are automatically defined for the constructor + parameters (i.e., it is possible to get the value of the `v` + constructor parameter of some instance `c` of class + `Const` just by writing `c.v`), +- default definitions for methods `equals` and + `hashCode` are provided, which work on the *structure* of + the instances and not on their identity, +- a default definition for method `toString` is provided, and + prints the value in a "source form" (e.g., the tree for expression + `x+1` prints as `Sum(Var(x),Const(1))`), +- instances of these classes can be decomposed through + *pattern matching* as we will see below. + +Now that we have defined the data-type to represent our arithmetic +expressions, we can start defining operations to manipulate them. We +will start with a function to evaluate an expression in some +*environment*. The aim of the environment is to give values to +variables. For example, the expression `x+1` evaluated in an +environment which associates the value `5` to variable `x`, written +`{ x -> 5 }`, gives `6` as result. + +We therefore have to find a way to represent environments. We could of +course use some associative data-structure like a hash table, but we +can also directly use functions! An environment is really nothing more +than a function which associates a value to a (variable) name. The +environment `{ x -> 5 }` given above can simply be written as +follows in Scala: + + { case "x" => 5 } + +This notation defines a function which, when given the string +`"x"` as argument, returns the integer `5`, and fails with an +exception otherwise. + +Before writing the evaluation function, let us give a name to the type +of the environments. We could of course always use the type +`String => Int` for environments, but it simplifies the program +if we introduce a name for this type, and makes future changes easier. +This is accomplished in Scala with the following notation: + + type Environment = String => Int + +From then on, the type `Environment` can be used as an alias of +the type of functions from `String` to `Int`. + +We can now give the definition of the evaluation function. +Conceptually, it is very simple: the value of a sum of two expressions +is simply the sum of the value of these expressions; the value of a +variable is obtained directly from the environment; and the value of a +constant is the constant itself. Expressing this in Scala is not more +difficult: + + def eval(t: Tree, env: Environment): Int = t match { + case Sum(l, r) => eval(l, env) + eval(r, env) + case Var(n) => env(n) + case Const(v) => v + } + +This evaluation function works by performing *pattern matching* +on the tree `t`. Intuitively, the meaning of the above definition +should be clear: + +1. it first checks if the tree `t` is a `Sum`, and if it + is, it binds the left sub-tree to a new variable called `l` and + the right sub-tree to a variable called `r`, and then proceeds + with the evaluation of the expression following the arrow; this + expression can (and does) make use of the variables bound by the + pattern appearing on the left of the arrow, i.e., `l` and + `r`, +2. if the first check does not succeed, that is, if the tree is not + a `Sum`, it goes on and checks if `t` is a `Var`; if + it is, it binds the name contained in the `Var` node to a + variable `n` and proceeds with the right-hand expression, +3. if the second check also fails, that is if `t` is neither a + `Sum` nor a `Var`, it checks if it is a `Const`, and + if it is, it binds the value contained in the `Const` node to a + variable `v` and proceeds with the right-hand side, +4. finally, if all checks fail, an exception is raised to signal + the failure of the pattern matching expression; this could happen + here only if more sub-classes of `Tree` were declared. + +We see that the basic idea of pattern matching is to attempt to match +a value to a series of patterns, and as soon as a pattern matches, +extract and name various parts of the value, to finally evaluate some +code which typically makes use of these named parts. + +A seasoned object-oriented programmer might wonder why we did not +define `eval` as a *method* of class `Tree` and its +subclasses. We could have done it actually, since Scala allows method +definitions in case classes just like in normal classes. Deciding +whether to use pattern matching or methods is therefore a matter of +taste, but it also has important implications on extensibility: + +- when using methods, it is easy to add a new kind of node as this + can be done just by defining a sub-class of `Tree` for it; on + the other hand, adding a new operation to manipulate the tree is + tedious, as it requires modifications to all sub-classes of + `Tree`, +- when using pattern matching, the situation is reversed: adding a + new kind of node requires the modification of all functions which do + pattern matching on the tree, to take the new node into account; on + the other hand, adding a new operation is easy, by just defining it + as an independent function. + +To explore pattern matching further, let us define another operation +on arithmetic expressions: symbolic derivation. The reader might +remember the following rules regarding this operation: + +1. the derivative of a sum is the sum of the derivatives, +2. the derivative of some variable `v` is one if `v` is the + variable relative to which the derivation takes place, and zero + otherwise, +3. the derivative of a constant is zero. + +These rules can be translated almost literally into Scala code, to +obtain the following definition: + + def derive(t: Tree, v: String): Tree = t match { + case Sum(l, r) => Sum(derive(l, v), derive(r, v)) + case Var(n) if (v == n) => Const(1) + case _ => Const(0) + } + +This function introduces two new concepts related to pattern matching. +First of all, the `case` expression for variables has a +*guard*, an expression following the `if` keyword. This +guard prevents pattern matching from succeeding unless its expression +is true. Here it is used to make sure that we return the constant `1` +only if the name of the variable being derived is the same as the +derivation variable `v`. The second new feature of pattern +matching used here is the *wildcard*, written `_`, which is +a pattern matching any value, without giving it a name. + +We did not explore the whole power of pattern matching yet, but we +will stop here in order to keep this document short. We still want to +see how the two functions above perform on a real example. For that +purpose, let's write a simple `main` function which performs +several operations on the expression `(x+x)+(7+y)`: it first computes +its value in the environment `{ x -> 5, y -> 7 }`, then +computes its derivative relative to `x` and then `y`. + + def main(args: Array[String]) { + val exp: Tree = Sum(Sum(Var("x"),Var("x")),Sum(Const(7),Var("y"))) + val env: Environment = { case "x" => 5 case "y" => 7 } + println("Expression: " + exp) + println("Evaluation with x=5, y=7: " + eval(exp, env)) + println("Derivative relative to x:\n " + derive(exp, "x")) + println("Derivative relative to y:\n " + derive(exp, "y")) + } + +Executing this program, we get the expected output: + + Expression: Sum(Sum(Var(x),Var(x)),Sum(Const(7),Var(y))) + Evaluation with x=5, y=7: 24 + Derivative relative to x: + Sum(Sum(Const(1),Const(1)),Sum(Const(0),Const(0))) + Derivative relative to y: + Sum(Sum(Const(0),Const(0)),Sum(Const(0),Const(1))) + +By examining the output, we see that the result of the derivative +should be simplified before being presented to the user. Defining a +basic simplification function using pattern matching is an interesting +(but surprisingly tricky) problem, left as an exercise for the reader. + +## Traits + +Apart from inheriting code from a super-class, a Scala class can also +import code from one or several *traits*. + +Maybe the easiest way for a Java programmer to understand what traits +are is to view them as interfaces which can also contain code. In +Scala, when a class inherits from a trait, it implements that trait's +interface, and inherits all the code contained in the trait. + +To see the usefulness of traits, let's look at a classical example: +ordered objects. It is often useful to be able to compare objects of a +given class among themselves, for example to sort them. In Java, +objects which are comparable implement the `Comparable` +interface. In Scala, we can do a bit better than in Java by defining +our equivalent of `Comparable` as a trait, which we will call +`Ord`. + +When comparing objects, six different predicates can be useful: +smaller, smaller or equal, equal, not equal, greater or equal, and +greater. However, defining all of them is fastidious, especially since +four out of these six can be expressed using the remaining two. That +is, given the equal and smaller predicates (for example), one can +express the other ones. In Scala, all these observations can be +nicely captured by the following trait declaration: + + trait Ord { + def < (that: Any): Boolean + def <=(that: Any): Boolean = (this < that) || (this == that) + def > (that: Any): Boolean = !(this <= that) + def >=(that: Any): Boolean = !(this < that) + } + +This definition both creates a new type called `Ord`, which +plays the same role as Java's `Comparable` interface, and +default implementations of three predicates in terms of a fourth, +abstract one. The predicates for equality and inequality do not appear +here since they are by default present in all objects. + +The type `Any` which is used above is the type which is a +super-type of all other types in Scala. It can be seen as a more +general version of Java's `Object` type, since it is also a +super-type of basic types like `Int`, `Float`, etc. + +To make objects of a class comparable, it is therefore sufficient to +define the predicates which test equality and inferiority, and mix in +the `Ord` class above. As an example, let's define a +`Date` class representing dates in the Gregorian calendar. Such +dates are composed of a day, a month and a year, which we will all +represent as integers. We therefore start the definition of the +`Date` class as follows: + + class Date(y: Int, m: Int, d: Int) extends Ord { + def year = y + def month = m + def day = d + override def toString(): String = year + "-" + month + "-" + day + +The important part here is the `extends Ord` declaration which +follows the class name and parameters. It declares that the +`Date` class inherits from the `Ord` trait. + +Then, we redefine the `equals` method, inherited from +`Object`, so that it correctly compares dates by comparing their +individual fields. The default implementation of `equals` is not +usable, because as in Java it compares objects physically. We arrive +at the following definition: + + override def equals(that: Any): Boolean = + that.isInstanceOf[Date] && { + val o = that.asInstanceOf[Date] + o.day == day && o.month == month && o.year == year + } + +This method makes use of the predefined methods `isInstanceOf` +and `asInstanceOf`. The first one, `isInstanceOf`, +corresponds to Java's `instanceof` operator, and returns true +if and only if the object on which it is applied is an instance of the +given type. The second one, `asInstanceOf`, corresponds to +Java's cast operator: if the object is an instance of the given type, +it is viewed as such, otherwise a `ClassCastException` is +thrown. + +Finally, the last method to define is the predicate which tests for +inferiority, as follows. It makes use of another predefined method, +`error`, which throws an exception with the given error message. + + def <(that: Any): Boolean = { + if (!that.isInstanceOf[Date]) + error("cannot compare " + that + " and a Date") + + val o = that.asInstanceOf[Date] + (year < o.year) || + (year == o.year && (month < o.month || + (month == o.month && day < o.day))) + } + +This completes the definition of the `Date` class. Instances of +this class can be seen either as dates or as comparable objects. +Moreover, they all define the six comparison predicates mentioned +above: `equals` and `<` because they appear directly in +the definition of the `Date` class, and the others because they +are inherited from the `Ord` trait. + +Traits are useful in other situations than the one shown here, of +course, but discussing their applications in length is outside the +scope of this document. + +## Genericity + +The last characteristic of Scala we will explore in this tutorial is +genericity. Java programmers should be well aware of the problems +posed by the lack of genericity in their language, a shortcoming which +is addressed in Java 1.5. + +Genericity is the ability to write code parametrized by types. For +example, a programmer writing a library for linked lists faces the +problem of deciding which type to give to the elements of the list. +Since this list is meant to be used in many different contexts, it is +not possible to decide that the type of the elements has to be, say, +`Int`. This would be completely arbitrary and overly +restrictive. + +Java programmers resort to using `Object`, which is the +super-type of all objects. This solution is however far from being +ideal, since it doesn't work for basic types (`int`, +`long`, `float`, etc.) and it implies that a lot of +dynamic type casts have to be inserted by the programmer. + +Scala makes it possible to define generic classes (and methods) to +solve this problem. Let us examine this with an example of the +simplest container class possible: a reference, which can either be +empty or point to an object of some type. + + class Reference[T] { + private var contents: T = _ + def set(value: T) { contents = value } + def get: T = contents + } + +The class `Reference` is parametrized by a type, called `T`, +which is the type of its element. This type is used in the body of the +class as the type of the `contents` variable, the argument of +the `set` method, and the return type of the `get` method. + +The above code sample introduces variables in Scala, which should not +require further explanations. It is however interesting to see that +the initial value given to that variable is `_`, which represents +a default value. This default value is 0 for numeric types, +`false` for the `Boolean` type, `()` for the `Unit` +type and `null` for all object types. + +To use this `Reference` class, one needs to specify which type to use +for the type parameter `T`, that is the type of the element +contained by the cell. For example, to create and use a cell holding +an integer, one could write the following: + + object IntegerReference { + def main(args: Array[String]) { + val cell = new Reference[Int] + cell.set(13) + println("Reference contains the half of " + (cell.get * 2)) + } + } + +As can be seen in that example, it is not necessary to cast the value +returned by the `get` method before using it as an integer. It +is also not possible to store anything but an integer in that +particular cell, since it was declared as holding an integer. + +## Conclusion + +This document gave a quick overview of the Scala language and +presented some basic examples. The interested reader can go on, for example, by +reading the document *Scala By Example*, which +contains much more advanced examples, and consult the *Scala + Language Specification* when needed. diff --git a/tutorials/scala-with-maven.md b/_overviews/tutorials/scala-with-maven.md similarity index 99% rename from tutorials/scala-with-maven.md rename to _overviews/tutorials/scala-with-maven.md index 2b22614c52..b0641484fd 100644 --- a/tutorials/scala-with-maven.md +++ b/_overviews/tutorials/scala-with-maven.md @@ -1,8 +1,9 @@ --- -layout: overview +layout: singlepage-overview title: Scala with Maven discourse: true +permalink: /tutorials/:title.html --- By Adrian Null diff --git a/_pl/cheatsheets/index.md b/_pl/cheatsheets/index.md new file mode 100644 index 0000000000..96b924cc4e --- /dev/null +++ b/_pl/cheatsheets/index.md @@ -0,0 +1,88 @@ +--- +layout: cheatsheet +title: Scalacheat + +partof: cheatsheet + +by: Filip Czaplicki +about: Podziękowania dla Brendan O'Connor. Ten cheatsheet ma być szybkim podsumowaniem konstrukcji składniowych Scali. Licencjonowany przez Brendan O'Connor pod licencją CC-BY-SA 3.0. + +language: pl +--- + +###### Contributed by {{ page.by }} +{{ page.about }} + +| zmienne | | +| `var x = 5` | zmienna | +| Dobrze `val x = 5`
      Źle `x=6` | stała | +| `var x: Double = 5` | zmienna z podanym typem | +| funkcje | | +| Dobrze `def f(x: Int) = { x*x }`
      Źle `def f(x: Int) { x*x }` | definicja funkcji
      ukryty błąd: bez znaku = jest to procedura zwracająca Unit; powoduje to chaos | +| Dobrze `def f(x: Any) = println(x)`
      Źle `def f(x) = println(x)` | definicja funkcji
      błąd składni: potrzebne są typy dla każdego argumentu. | +| `type R = Double` | alias typu | +| `def f(x: R)` vs.
      `def f(x: => R)` | wywołanie przez wartość
      wywołanie przez nazwę (parametry leniwe) | +| `(x:R) => x*x` | funkcja anonimowa | +| `(1 to 5).map(_*2)` vs.
      `(1 to 5).reduceLeft( _+_ )` | funkcja anonimowa: podkreślenie to argument pozycjonalny | +| `(1 to 5).map( x => x*x )` | funkcja anonimowa: aby użyć argumentu dwa razy, musisz go nazwać. | +| Dobrze `(1 to 5).map(2*)`
      Źle `(1 to 5).map(*2)` | funkcja anonimowa: związana metoda infiksowa. Możesz użyć także `2*_`. | +| `(1 to 5).map { x => val y=x*2; println(y); y }` | funkcja anonimowa: z bloku zwracane jest ostatnie wyrażenie. | +| `(1 to 5) filter {_%2 == 0} map {_*2}` | funkcja anonimowa: styl potokowy. (lub ponawiasowane). | +| `def compose(g:R=>R, h:R=>R) = (x:R) => g(h(x))`
      `val f = compose({_*2}, {_-1})` | funkcja anonimowa: aby przekazać kilka bloków musisz użyć nawiasów. | +| `val zscore = (mean:R, sd:R) => (x:R) => (x-mean)/sd` | rozwijanie funkcji, oczywista składnia. | +| `def zscore(mean:R, sd:R) = (x:R) => (x-mean)/sd` | rozwijanie funkcji, oczywista składnia. | +| `def zscore(mean:R, sd:R)(x:R) = (x-mean)/sd` | rozwijanie funkcji, lukier składniowy. ale wtedy: | +| `val normer = zscore(7, 0.4) _` | potrzeba wiodącego podkreślenia, aby wydobyć funkcję częściowo zaaplikowaną, tylko dla wersji z lukrem składniowym. | +| `def mapmake[T](g:T=>T)(seq: List[T]) = seq.map(g)` | typ generyczny. | +| `5.+(3); 5 + 3`
      `(1 to 5) map (_*2)` | lukier składniowy dla operatorów infiksowych. | +| `def sum(args: Int*) = args.reduceLeft(_+_)` | zmienna liczba argumentów funkcji. | +| pakiety | | +| `import scala.collection._` | import wszystkiego z danego pakietu. | +| `import scala.collection.Vector`
      `import scala.collection.{Vector, Sequence}` | import selektywny. | +| `import scala.collection.{Vector => Vec28}` | import ze zmianą nazwy. | +| `import java.util.{Date => _, _}` | importowanie wszystkiego z java.util poza Date. | +| `package pkg` _na początku pliku_
      `package pkg { ... }` | deklaracja pakietu. | +| struktury danych | | +| `(1,2,3)` | literał krotki. (`Tuple3`) | +| `var (x,y,z) = (1,2,3)` | przypisanie z podziałem: rozpakowywanie krotki przy pomocy dopasowywania wzorca. | +| Źle`var x,y,z = (1,2,3)` | ukryty błąd: do każdego przypisana cała krotka. | +| `var xs = List(1,2,3)` | lista (niezmienna). | +| `xs(2)` | indeksowanie za pomocą nawiasów. ([slajdy](http://www.slideshare.net/Odersky/fosdem-2009-1013261/27)) | +| `1 :: List(2,3)` | operator dołożenia elementu na początek listy. | +| `1 to 5` _to samo co_ `1 until 6`
      `1 to 10 by 2` | składnia dla przedziałów. | +| `()` _(puste nawiasy)_ | jedyny obiekt typu Unit (podobnie jak void w C/Java). | +| konstrukcje kontrolne | | +| `if (check) happy else sad` | warunek. | +| `if (check) happy` _to samo co_
      `if (check) happy else ()` | lukier składniowy dla warunku. | +| `while (x < 5) { println(x); x += 1}` | pętla while. | +| `do { println(x); x += 1} while (x < 5)` | pętla do while. | +| `import scala.util.control.Breaks._`
      `breakable {`
      ` for (x <- xs) {`
      ` if (Math.random < 0.1) break`
      ` }`
      `}`| instrukcja przerwania pętli (break). ([slides](http://www.slideshare.net/Odersky/fosdem-2009-1013261/21)) | +| `for (x <- xs if x%2 == 0) yield x*10` _to samo co_
      `xs.filter(_%2 == 0).map(_*10)` | instrukcja for: filtrowanie / mapowanie | +| `for ((x,y) <- xs zip ys) yield x*y` _to samo co_
      `(xs zip ys) map { case (x,y) => x*y }` | instrukcja for: przypisanie z podziałem | +| `for (x <- xs; y <- ys) yield x*y` _to samo co_
      `xs flatMap {x => ys map {y => x*y}}` | instrukcja for: iloczyn kartezjański | +| `for (x <- xs; y <- ys) {`
      `println("%d/%d = %.1f".format(x, y, x/y.toFloat))`
      `}` | instrukcja for: imperatywnie
      [sprintf-style](http://java.sun.com/javase/6/docs/api/java/util/Formatter.html#syntax) | +| `for (i <- 1 to 5) {`
      `println(i)`
      `}` | instrukcja for: iterowanie aż do górnej granicy | +| `for (i <- 1 until 5) {`
      `println(i)`
      `}` | instrukcja for: iterowanie poniżej górnej granicy | +| pattern matching (dopasowywanie wzorca) | | +| Dobrze `(xs zip ys) map { case (x,y) => x*y }`
      Źle `(xs zip ys) map( (x,y) => x*y )` | używaj słowa kluczowego case w funkcjach w celu dopasowywania wzorca (pattern matching). | +| Źle
      `val v42 = 42`
      `Some(3) match {`
      ` case Some(v42) => println("42")`
      ` case _ => println("Not 42")`
      `}` | "v42" jest interpretowane jako nazwa pasująca do każdej wartości typu Int, więc "42" zostaje wypisywane. | +| Dobrze
      `val v42 = 42`
      `Some(3) match {`
      `` case Some(`v42`) => println("42")``
      `case _ => println("Not 42")`
      `}` | "\`v42\`" z grawisami jest interpretowane jako istniejąca wartość `v42`, więc "Not 42" zostaje wypisywane. | +| Dobrze
      `val UppercaseVal = 42`
      `Some(3) match {`
      ` case Some(UppercaseVal) => println("42")`
      ` case _ => println("Not 42")`
      `}` | `UppercaseVal` jest traktowane jako istniejąca wartość, nie jako zmienna wzorca, bo zaczyna się z wielkiej litery. W takim razie wartość przechowywana w `UppercaseVal` jest porównywana z `3`, więc "Not 42" zostaje wypisywane. | +| obiektowość | | +| `class C(x: R)` _to samo co_
      `class C(private val x: R)`
      `var c = new C(4)` | parametry konstruktora - prywatne | +| `class C(val x: R)`
      `var c = new C(4)`
      `c.x` | parametry konstruktora - publiczne | +| `class C(var x: R) {`
      `assert(x > 0, "positive please")`
      `var y = x`
      `val readonly = 5`
      `private var secret = 1`
      `def this = this(42)`
      `}`|

      konstruktor jest ciałem klasy
      deklaracja publicznego pola
      deklaracja publicznej stałej
      deklaracja pola prywatnego
      alternatywny konstruktor| +| `new{ ... }` | klasa anonimowa | +| `abstract class D { ... }` | definicja klasy abstrakcyjnej. (nie da się stworzyć obiektu tej klasy) | +| `class C extends D { ... }` | definicja klasy pochodnej. | +| `class D(var x: R)`
      `class C(x: R) extends D(x)` | dziedziczenie i parametry konstruktora. (wishlist: domyślne, automatyczne przekazywanie parametrów) +| `object O extends D { ... }` | definicja singletona. (w stylu modułu) | +| `trait T { ... }`
      `class C extends T { ... }`
      `class C extends D with T { ... }` | cechy.
      interface'y z implementacją. bez parametrów konstruktora. [możliwość mixin'ów]({{ site.baseurl }}/tutorials/tour/mixin-class-composition.html). +| `trait T1; trait T2`
      `class C extends T1 with T2`
      `class C extends D with T1 with T2` | wiele cech. | +| `class C extends D { override def f = ...}` | w przeciążeniach funkcji wymagane jest słowo kluczowe override. | +| `new java.io.File("f")` | tworzenie obiektu. | +| Źle `new List[Int]`
      Dobrze `List(1,2,3)` | błąd typu: typ abstrakcyjny
      zamiast tego konwencja: wywoływalna fabryka przysłaniająca typ | +| `classOf[String]` | literał klasy. | +| `x.isInstanceOf[String]` | sprawdzenie typu (w czasie wykonania) | +| `x.asInstanceOf[String]` | rzutowanie typu (w czasie wykonania) | +| `x: String` | oznaczenie typu (w czasie kompilacji) | diff --git a/_pl/tour/abstract-types.md b/_pl/tour/abstract-types.md new file mode 100644 index 0000000000..4d152a4940 --- /dev/null +++ b/_pl/tour/abstract-types.md @@ -0,0 +1,79 @@ +--- +layout: tour +title: Typy abstrakcyjne + +discourse: false + +partof: scala-tour + +num: 22 +language: pl +next-page: compound-types +previous-page: 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ę, ż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/tour/annotations.md b/_pl/tour/annotations.md new file mode 100644 index 0000000000..65921a156f --- /dev/null +++ b/_pl/tour/annotations.md @@ -0,0 +1,145 @@ +--- +layout: tour +title: Adnotacje + +discourse: false + +partof: scala-tour + +num: 31 +next-page: default-parameter-values +previous-page: 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`](https://www.scala-lang.org/api/current/scala/SerialVersionUID.html) | [`serialVersionUID`](http://java.sun.com/j2se/1.5.0/docs/api/java/io/Serializable.html#navbar_bottom) (pole) | +| [`scala.deprecated`](https://www.scala-lang.org/api/current/scala/deprecated.html) | [`java.lang.Deprecated`](http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Deprecated.html) | +| [`scala.inline`](https://www.scala-lang.org/api/current/scala/inline.html) (since 2.6.0) | brak odpowiednika | +| [`scala.native`](https://www.scala-lang.org/api/current/scala/native.html) (since 2.6.0) | [`native`](http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html) (słowo kluczowe) | +| [`scala.remote`](https://www.scala-lang.org/api/current/scala/remote.html) | [`java.rmi.Remote`](http://java.sun.com/j2se/1.5.0/docs/api/java/rmi/Remote.html) | +| [`scala.throws`](https://www.scala-lang.org/api/current/scala/throws.html) | [`throws`](http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html) (słowo kluczowe) | +| [`scala.transient`](https://www.scala-lang.org/api/current/scala/transient.html) | [`transient`](http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html) (słowo kluczowe) | +| [`scala.unchecked`](https://www.scala-lang.org/api/current/scala/unchecked.html) (od 2.4.0) | brak odpowiednika | +| [`scala.volatile`](https://www.scala-lang.org/api/current/scala/volatile.html) | [`volatile`](http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html) (słowo kluczowe) | +| [`scala.beans.BeanProperty`](https://www.scala-lang.org/api/current/scala/beans/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ących klasy rzucanych wyjątków. + +``` +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/tour/anonymous-function-syntax.md b/_pl/tour/anonymous-function-syntax.md new file mode 100644 index 0000000000..60a428f3b8 --- /dev/null +++ b/_pl/tour/anonymous-function-syntax.md @@ -0,0 +1,55 @@ +--- +layout: tour +title: Funkcje anonimowe + +discourse: false + +partof: scala-tour + +num: 6 +language: pl +next-page: higher-order-functions +previous-page: 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/tour/automatic-closures.md b/_pl/tour/automatic-closures.md new file mode 100644 index 0000000000..ca6c14eabf --- /dev/null +++ b/_pl/tour/automatic-closures.md @@ -0,0 +1,74 @@ +--- +layout: tour +title: Automatyczna konstrukcja domknięć + +discourse: false + +partof: scala-tour + +num: 30 +language: pl +next-page: annotations +previous-page: 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/tour/case-classes.md b/_pl/tour/case-classes.md new file mode 100644 index 0000000000..831277f07b --- /dev/null +++ b/_pl/tour/case-classes.md @@ -0,0 +1,144 @@ +--- +layout: tour +title: Klasy przypadków + +discourse: false + +partof: scala-tour + +num: 10 +language: pl +next-page: pattern-matching +previous-page: 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` implementującą 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/tour/classes.md b/_pl/tour/classes.md new file mode 100644 index 0000000000..24a69c466c --- /dev/null +++ b/_pl/tour/classes.md @@ -0,0 +1,54 @@ +--- +layout: tour +title: Klasy + +discourse: false + +partof: scala-tour + +num: 3 +language: pl +next-page: traits +previous-page: 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/tour/compound-types.md b/_pl/tour/compound-types.md new file mode 100644 index 0000000000..84ebd0d963 --- /dev/null +++ b/_pl/tour/compound-types.md @@ -0,0 +1,53 @@ +--- +layout: tour +title: Typy złożone + +discourse: false + +partof: scala-tour + +num: 23 +language: pl +next-page: explicitly-typed-self-references +previous-page: 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/tour/currying.md b/_pl/tour/currying.md new file mode 100644 index 0000000000..29735548cc --- /dev/null +++ b/_pl/tour/currying.md @@ -0,0 +1,42 @@ +--- +layout: tour +title: Rozwijanie funkcji (Currying) + +discourse: false + +partof: scala-tour + +num: 9 +language: pl +next-page: case-classes +previous-page: 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, zwraca 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/tour/default-parameter-values.md b/_pl/tour/default-parameter-values.md new file mode 100644 index 0000000000..5b32053260 --- /dev/null +++ b/_pl/tour/default-parameter-values.md @@ -0,0 +1,73 @@ +--- +layout: tour +title: Domyślne wartości parametrów + +discourse: false + +partof: scala-tour + +num: 32 +language: pl +next-page: named-parameters +previous-page: 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ę, w jaki sposób możemy wykorzystać *dowolną* domyślną wartość poprzez użycie [parametrów nazwanych](named-parameters.html). diff --git a/_pl/tour/explicitly-typed-self-references.md b/_pl/tour/explicitly-typed-self-references.md new file mode 100644 index 0000000000..2be5e78d67 --- /dev/null +++ b/_pl/tour/explicitly-typed-self-references.md @@ -0,0 +1,125 @@ +--- +layout: tour +title: Jawnie typowane samoreferencje + +discourse: false + +partof: scala-tour + +num: 24 +language: pl +next-page: implicit-parameters +previous-page: 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/tour/extractor-objects.md b/_pl/tour/extractor-objects.md new file mode 100644 index 0000000000..5b3e20f5e6 --- /dev/null +++ b/_pl/tour/extractor-objects.md @@ -0,0 +1,43 @@ +--- +layout: tour +title: Obiekty ekstraktorów + +discourse: false + +partof: scala-tour + +num: 15 +language: pl +next-page: sequence-comprehensions +previous-page: 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/tour/generic-classes.md b/_pl/tour/generic-classes.md new file mode 100644 index 0000000000..c822d2087d --- /dev/null +++ b/_pl/tour/generic-classes.md @@ -0,0 +1,50 @@ +--- +layout: tour +title: Klasy generyczne + +discourse: false + +partof: scala-tour + +num: 17 +language: pl +next-page: variances +previous-page: 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/tour/higher-order-functions.md b/_pl/tour/higher-order-functions.md new file mode 100644 index 0000000000..c93ea72c71 --- /dev/null +++ b/_pl/tour/higher-order-functions.md @@ -0,0 +1,43 @@ +--- +layout: tour +title: Funkcje wyższego rzędu + +discourse: false + +partof: scala-tour + +num: 7 +language: pl +next-page: nested-functions +previous-page: 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óra 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/tour/implicit-conversions.md b/_pl/tour/implicit-conversions.md new file mode 100644 index 0000000000..aea3a9df82 --- /dev/null +++ b/_pl/tour/implicit-conversions.md @@ -0,0 +1,53 @@ +--- +layout: tour +title: Konwersje niejawne + +discourse: false + +partof: scala-tour + +num: 26 +language: pl +next-page: polymorphic-methods +previous-page: 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/tour/implicit-parameters.md b/_pl/tour/implicit-parameters.md new file mode 100644 index 0000000000..4bac40487a --- /dev/null +++ b/_pl/tour/implicit-parameters.md @@ -0,0 +1,59 @@ +--- +layout: tour +title: Parametry domniemane + +discourse: false + +partof: scala-tour + +num: 25 +language: pl +next-page: implicit-conversions +previous-page: 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/tour/inner-classes.md b/_pl/tour/inner-classes.md new file mode 100644 index 0000000000..b4a8b66561 --- /dev/null +++ b/_pl/tour/inner-classes.md @@ -0,0 +1,99 @@ +--- +layout: tour +title: Klasy wewnętrzne + +discourse: false + +partof: scala-tour + +num: 21 +language: pl +next-page: abstract-types +previous-page: 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 chcielibyśmy znieść to ograniczenie, należy zmienić typ zmiennej `nodes` na `Graph#Node`. diff --git a/_pl/tour/local-type-inference.md b/_pl/tour/local-type-inference.md new file mode 100644 index 0000000000..c3d057bab9 --- /dev/null +++ b/_pl/tour/local-type-inference.md @@ -0,0 +1,64 @@ +--- +layout: tour +title: Lokalna inferencja typów + +discourse: false + +partof: scala-tour + +num: 28 +language: pl +next-page: operators +previous-page: 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 ilustrujący to przykład: + +``` +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/tour/lower-type-bounds.md b/_pl/tour/lower-type-bounds.md new file mode 100644 index 0000000000..dd22954df8 --- /dev/null +++ b/_pl/tour/lower-type-bounds.md @@ -0,0 +1,57 @@ +--- +layout: tour +title: Dolne ograniczenia typów + +discourse: false + +partof: scala-tour + +num: 20 +language: pl +next-page: inner-classes +previous-page: 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/tour/mixin-class-composition.md b/_pl/tour/mixin-class-composition.md new file mode 100644 index 0000000000..ed358ac8ca --- /dev/null +++ b/_pl/tour/mixin-class-composition.md @@ -0,0 +1,58 @@ +--- +layout: tour +title: Kompozycja domieszek + +discourse: false + +partof: scala-tour + +num: 5 +language: pl +next-page: anonymous-function-syntax +previous-page: 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 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 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/tour/named-parameters.md b/_pl/tour/named-parameters.md new file mode 100644 index 0000000000..29b8bc8c36 --- /dev/null +++ b/_pl/tour/named-parameters.md @@ -0,0 +1,38 @@ +--- +layout: tour +title: Parametry nazwane + +discourse: false + +partof: scala-tour + +num: 33 +language: pl +previous-page: 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/tour/nested-functions.md b/_pl/tour/nested-functions.md new file mode 100644 index 0000000000..0f905aff11 --- /dev/null +++ b/_pl/tour/nested-functions.md @@ -0,0 +1,36 @@ +--- +layout: tour +title: Funkcje zagnieżdżone + +discourse: false + +partof: scala-tour + +num: 8 +language: pl +next-page: currying +previous-page: 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/tour/operators.md b/_pl/tour/operators.md new file mode 100644 index 0000000000..a6a1c9a057 --- /dev/null +++ b/_pl/tour/operators.md @@ -0,0 +1,39 @@ +--- +layout: tour +title: Operatory + +discourse: false + +partof: scala-tour + +num: 29 +language: pl +next-page: automatic-closures +previous-page: 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/tour/pattern-matching.md b/_pl/tour/pattern-matching.md new file mode 100644 index 0000000000..f36648439a --- /dev/null +++ b/_pl/tour/pattern-matching.md @@ -0,0 +1,46 @@ +--- +layout: tour +title: Dopasowanie wzorców (Pattern matching) + +discourse: false + +partof: scala-tour + +num: 11 +language: pl +next-page: singleton-objects +previous-page: case-classes +--- + +Scala posiada wbudowany mechanizm dopasowania wzorców. Umożliwia on dopasowanie dowolnego rodzaju danych, przy czym zawsze zwracamy 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 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/tour/polymorphic-methods.md b/_pl/tour/polymorphic-methods.md new file mode 100644 index 0000000000..c40f48dbac --- /dev/null +++ b/_pl/tour/polymorphic-methods.md @@ -0,0 +1,31 @@ +--- +layout: tour +title: Metody polimorficzne + +discourse: false + +partof: scala-tour + +num: 27 +language: pl +next-page: local-type-inference +previous-page: 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/tour/regular-expression-patterns.md b/_pl/tour/regular-expression-patterns.md new file mode 100644 index 0000000000..33c42cbad7 --- /dev/null +++ b/_pl/tour/regular-expression-patterns.md @@ -0,0 +1,40 @@ +--- +layout: tour +title: Wzorce wyrażeń regularnych + +discourse: false + +partof: scala-tour + +num: 14 +language: pl + +next-page: extractor-objects +previous-page: singleton-objects +--- + +## 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/tour/sequence-comprehensions.md b/_pl/tour/sequence-comprehensions.md new file mode 100644 index 0000000000..c477ee25e0 --- /dev/null +++ b/_pl/tour/sequence-comprehensions.md @@ -0,0 +1,69 @@ +--- +layout: tour +title: Instrukcje for (For comprehension) + +discourse: false + +partof: scala-tour + +num: 16 +language: pl +next-page: generic-classes +previous-page: 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/tour/singleton-objects.md b/_pl/tour/singleton-objects.md new file mode 100644 index 0000000000..653c10ac88 --- /dev/null +++ b/_pl/tour/singleton-objects.md @@ -0,0 +1,71 @@ +--- +layout: tour +title: Obiekty singleton + +discourse: false + +partof: scala-tour + +num: 12 +language: pl + +next-page: regular-expression-patterns +previous-page: 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 towarzyszą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) takimi jak `ipord` powyżej, zdefiniowanymi 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, pomocnicze 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 *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/tour/tour-of-scala.md b/_pl/tour/tour-of-scala.md new file mode 100644 index 0000000000..936a18277f --- /dev/null +++ b/_pl/tour/tour-of-scala.md @@ -0,0 +1,47 @@ +--- +layout: tour +title: Wprowadzenie + +discourse: false + +partof: scala-tour + +num: 1 +outof: 33 +language: pl +next-page: 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), a także 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 zapewniający, ż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 tak, 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/tour/traits.md b/_pl/tour/traits.md new file mode 100644 index 0000000000..9d11ab5213 --- /dev/null +++ b/_pl/tour/traits.md @@ -0,0 +1,57 @@ +--- +layout: tour +title: Cechy + +discourse: false + +partof: scala-tour + +num: 4 +language: pl +next-page: mixin-class-composition +previous-page: 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/tour/unified-types.md b/_pl/tour/unified-types.md new file mode 100644 index 0000000000..103647d8e7 --- /dev/null +++ b/_pl/tour/unified-types.md @@ -0,0 +1,49 @@ +--- +layout: tour +title: Hierarchia typów + +discourse: false + +partof: scala-tour + +num: 2 +language: pl +next-page: classes +previous-page: 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/tour/upper-type-bounds.md b/_pl/tour/upper-type-bounds.md new file mode 100644 index 0000000000..000cd344c0 --- /dev/null +++ b/_pl/tour/upper-type-bounds.md @@ -0,0 +1,52 @@ +--- +layout: tour +title: Górne ograniczenia typów + +discourse: false + +partof: scala-tour + +num: 19 +language: pl +next-page: lower-type-bounds +previous-page: 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/tour/variances.md b/_pl/tour/variances.md new file mode 100644 index 0000000000..2faedf1cdd --- /dev/null +++ b/_pl/tour/variances.md @@ -0,0 +1,42 @@ +--- +layout: tour +title: Wariancje + +discourse: false + +partof: scala-tour + +num: 18 +language: pl +next-page: upper-type-bounds +previous-page: 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ę, ż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[+T] { + def push[S >: T](elem: S): Stack[S] = new Stack[S] { + override def top: S = elem + override def pop: Stack[S] = Stack.this + override def toString: String = + elem.toString + " " + Stack.this.toString + } + def top: T = sys.error("no element on stack") + def pop: Stack[T] = sys.error("no element on stack") + override def toString: String = "" +} + +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/_plugins/tut_replace.rb b/_plugins/tut_replace.rb new file mode 100644 index 0000000000..bb932932a2 --- /dev/null +++ b/_plugins/tut_replace.rb @@ -0,0 +1,18 @@ +module Jekyll + class TutConverter < Converter + safe false + priority :high + + def matches(ext) + ext =~ /^\.(md|markdown)$/i + end + + def output_ext(ext) + ".html" + end + + def convert(content) + content.gsub("```tut\n", "```scala\n") + end + end +end diff --git a/_pt-br/cheatsheets/index.md b/_pt-br/cheatsheets/index.md new file mode 100644 index 0000000000..5d3176bda0 --- /dev/null +++ b/_pt-br/cheatsheets/index.md @@ -0,0 +1,88 @@ +--- +layout: cheatsheet +title: Scalacheat + +partof: cheatsheet + +by: Reginaldo Russinholi +about: Agradecimentos a Brendan O'Connor, este 'cheatsheet' se destina a ser uma referência rápida às construções sintáticas de Scala. Licenciado por Brendan O'Connor sobre a licença CC-BY-SA 3.0. + +language: pt-br +--- + +###### Contribuição de {{ page.by }} +{{ page.about }} + +| variáveis | | +| `var x = 5` | variável | +| Bom `val x = 5`
      Ruim `x=6` | constante | +| `var x: Double = 5` | tipo explícito | +| funções | | +| Bom `def f(x: Int) = { x*x }`
      Ruim `def f(x: Int) { x*x }` | define uma função
      erro omitido: sem = é uma procedure que retorna Unit; causa dano | +| Bom `def f(x: Any) = println(x)`
      Ruim `def f(x) = println(x)` | define uma função
      erro de sintaxe: necessita tipos para todos os argumentos. | +| `type R = Double` | alias de tipo | +| `def f(x: R)` vs.
      `def f(x: => R)` | chamada-por-valor
      chamada-por-nome (parâmetros 'lazy') | +| `(x:R) => x*x` | função anônima | +| `(1 to 5).map(_*2)` vs.
      `(1 to 5).reduceLeft( _+_ )` | função anônima: 'underscore' está associado a posição do argumento. | +| `(1 to 5).map( x => x*x )` | função anônima: para usar um argumento duas vezes, tem que dar nome a ele. | +| Bom `(1 to 5).map(2*)`
      Ruim `(1 to 5).map(*2)` | função anônima: método infixo encadeado. Use `2*_` para ficar mais claro. | +| `(1 to 5).map { val x=_*2; println(x); x }` | função anônima: estilo em bloco retorna a última expressão. | +| `(1 to 5) filter {_%2 == 0} map {_*2}` | função anônima: estilo 'pipeline'. (ou também parênteses). | +| `def compose(g:R=>R, h:R=>R) = (x:R) => g(h(x))`
      `val f = compose({_*2}, {_-1})` | função anônima: para passar múltiplos blocos é necessário colocar entre parênteses. | +| `val zscore = (mean:R, sd:R) => (x:R) => (x-mean)/sd` | currying, sintáxe óbvia. | +| `def zscore(mean:R, sd:R) = (x:R) => (x-mean)/sd` | currying, sintáxe óbvia | +| `def zscore(mean:R, sd:R)(x:R) = (x-mean)/sd` | currying, sintáxe 'sugar'. mas então: | +| `val normer = zscore(7, 0.4)_` | precisa de 'underscore' no final para obter o parcial, apenas para a versão 'sugar'. | +| `def mapmake[T](g:T=>T)(seq: List[T]) = seq.map(g)` | tipo genérico. | +| `5.+(3); 5 + 3`
      `(1 to 5) map (_*2)` | sintáxe 'sugar' para operadores infixos. | +| `def sum(args: Int*) = args.reduceLeft(_+_)` | varargs. | +| pacotes | | +| `import scala.collection._` | caracter coringa para importar tudo de um pacote. | +| `import scala.collection.Vector`
      `import scala.collection.{Vector, Sequence}` | importação seletiva. | +| `import scala.collection.{Vector => Vec28}` | renomear uma importação. | +| `import java.util.{Date => _, _}` | importar tudo de java.util exceto Date. | +| `package pkg` _no início do arquivo_
      `package pkg { ... }` | declara um pacote. | +| estruturas de dados | | +| `(1,2,3)` | literal de tupla. (`Tuple3`) | +| `var (x,y,z) = (1,2,3)` | atribuição desestruturada: desempacotando uma tupla através de "pattern matching". | +| Ruim`var x,y,z = (1,2,3)` | erro oculto: cada variável é associada a tupla inteira. | +| `var xs = List(1,2,3)` | lista (imutável). | +| `xs(2)` | indexação por parênteses. ([slides](http://www.slideshare.net/Odersky/fosdem-2009-1013261/27)) | +| `1 :: List(2,3)` | concatenação. | +| `1 to 5` _o mesmo que_ `1 until 6`
      `1 to 10 by 2` | sintáxe 'sugar' para intervalo. | +| `()` _(parênteses vazio)_ | um membro do tipo Unit (igual ao void de C/Java). | +| estruturas de controle | | +| `if (check) happy else sad` | condicional. | +| `if (check) happy` _o mesmo que_
      `if (check) happy else ()` | condicional 'sugar'. | +| `while (x < 5) { println(x); x += 1}` | while. | +| `do { println(x); x += 1} while (x < 5)` | do while. | +| `import scala.util.control.Breaks._`
      `breakable {`
      ` for (x <- xs) {`
      ` if (Math.random < 0.1) break`
      ` }`
      `}`| break. ([slides](http://www.slideshare.net/Odersky/fosdem-2009-1013261/21)) | +| `for (x <- xs if x%2 == 0) yield x*10` _o mesmo que_
      `xs.filter(_%2 == 0).map(_*10)` | for: filter/map | +| `for ((x,y) <- xs zip ys) yield x*y` _o mesmo que_
      `(xs zip ys) map { case (x,y) => x*y }` | for: associação desestruturada | +| `for (x <- xs; y <- ys) yield x*y` _o mesmo que_
      `xs flatMap {x => ys map {y => x*y}}` | for: produto cruzado | +| `for (x <- xs; y <- ys) {`
      `println("%d/%d = %.1f".format(x, y, x/y.toFloat))`
      `}` | for: estilo imperativo
      [sprintf-style](http://java.sun.com/javase/6/docs/api/java/util/Formatter.html#syntax) | +| `for (i <- 1 to 5) {`
      `println(i)`
      `}` | for: itera incluindo o limite superior | +| `for (i <- 1 until 5) {`
      `println(i)`
      `}` | for: itera omitindo o limite superior | +| pattern matching | | +| Bom `(xs zip ys) map { case (x,y) => x*y }`
      Ruim `(xs zip ys) map( (x,y) => x*y )` | use 'case' nos argumentos de funções para fazer a associação via 'pattern matching'. | +| Ruim
      `val v42 = 42`
      `Some(3) match {`
      ` case Some(v42) => println("42")`
      ` case _ => println("Not 42")`
      `}` | "v42" é interpretado como um nome que será comparado com qualquer valor Int, e "42" é impresso. | +| Bom
      `val v42 = 42`
      `Some(3) match {`
      `` case Some(`v42`) => println("42")``
      `case _ => println("Not 42")`
      `}` | "\`v42\`" entre crases é interpretado como existindo o valor `v42`, e "Not 42" é impresso. | +| Bom
      `val UppercaseVal = 42`
      `Some(3) match {`
      ` case Some(UppercaseVal) => println("42")`
      ` case _ => println("Not 42")`
      `}` | `UppercaseVal` é tratado como um valor existente, mais do que uma nova variável de padrão, porque ele inicia com uma letra maiúscula. Assim, o valor contido em `UppercaseVal` é checado contra `3`, e "Not 42" é impresso. | +| orientação a objetos | | +| `class C(x: R)` _o mesmo que_
      `class C(private val x: R)`
      `var c = new C(4)` | parâmetros do construtor - privado | +| `class C(val x: R)`
      `var c = new C(4)`
      `c.x` | parâmetros do construtor - público | +| `class C(var x: R) {`
      `assert(x > 0, "positive please")`
      `var y = x`
      `val readonly = 5`
      `private var secret = 1`
      `def this = this(42)`
      `}`|
      o construtor é o corpo da classe
      declara um membro público
      declara um membro que pode ser obtido mas não alterado
      declara um membro privado
      construtor alternativo| +| `new{ ... }` | classe anônima | +| `abstract class D { ... }` | define uma classe abstrata. (que não pode ser instanciada) | +| `class C extends D { ... }` | define uma classe com herança. | +| `class D(var x: R)`
      `class C(x: R) extends D(x)` | herança e parâmetros do construtor. (lista de desejos: automaticamente passar parâmetros por 'default') +| `object O extends D { ... }` | define um 'singleton'. (module-like) | +| `trait T { ... }`
      `class C extends T { ... }`
      `class C extends D with T { ... }` | traits.
      interfaces-com-implementação. sem parâmetros de construtor. [mixin-able]({{ site.baseurl }}/tutorials/tour/mixin-class-composition.html). +| `trait T1; trait T2`
      `class C extends T1 with T2`
      `class C extends D with T1 with T2` | multiplos traits. | +| `class C extends D { override def f = ...}` | é necessário declarar a sobrecarga de métodos. | +| `new java.io.File("f")` | cria/instancia um objeto. | +| Ruim `new List[Int]`
      Bom `List(1,2,3)` | erro de tipo: tipo abstrato
      ao invés, por convenção: a 'factory' chamada já aplica o tipo implicitamente | +| `classOf[String]` | literal de classe. | +| `x.isInstanceOf[String]` | checagem de tipo (em tempo de execução) | +| `x.asInstanceOf[String]` | conversão/'cast' de tipo (em tempo de execução) | +| `x: String` | atribuição de tipo (em tempo de compilação) | diff --git a/_pt-br/tour/abstract-types.md b/_pt-br/tour/abstract-types.md new file mode 100644 index 0000000000..5eda5a34b9 --- /dev/null +++ b/_pt-br/tour/abstract-types.md @@ -0,0 +1,79 @@ +--- +layout: tour +title: Tipos Abstratos + +discourse: false + +partof: scala-tour + +num: 22 +next-page: compound-types +previous-page: inner-classes +language: pt-br +--- + +Em Scala, as classes são parametrizadas com valores (os parâmetros de construtor) e com tipos (se as [classes genéricas](generic-classes.html)). Por razões de regularidade, só não é possível ter valores como membros de um objeto; tipos juntamente com valores são membros de objetos. Além disso, ambas as formas de membros podem ser concretas e abstratas. + +Aqui está um exemplo que mostra uma definição de valor diferido e uma definição de tipo abstrato como membros de uma [trait](traits.html) chamada `Buffer`. + +```tut +trait Buffer { + type T + val element: T +} +``` + +*Tipos Abstratos* são tipos cuja identidade não é precisamente conhecida. No exemplo acima, só sabemos que cada objeto da classe `Buffer` tem um membro de tipo `T`, mas a definição de classe `Buffer` não revela a qual tipo concreto o membro do tipo `T` corresponde. Como definições de valores, podemos sobrescrever definições de tipos em subclasses. Isso nos permite revelar mais informações sobre um tipo abstrato ao limitar o tipo associado (o qual descreve as possíveis instâncias concretas do tipo abstrato). + +No seguinte programa temos uma classe `SeqBuffer` que nos permite armazenar apenas as sequências no buffer ao definir que o tipo `T` precisa ser um subtipo de `Seq[U]` para um novo tipo abstrato `U`: + +```tut +abstract class SeqBuffer extends Buffer { + type U + type T <: Seq[U] + def length = element.length +} +``` + +[Traits](traits.html) ou [classes](classes.html) com membros de tipo abstratos são frequentemente utilizadas em combinação com instâncias de classe anônimas. Para ilustrar isso, agora analisamos um programa que lida com um buffer de sequência que se refere a uma lista de inteiros: + +```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) +} +``` + +O tipo de retorno do método `newIntSeqBuf` refere-se a uma especialização da trait `Buffer` no qual o tipo `U` é agora equivalente a `Int`. Declaramos um tipo *alias* semelhante ao que temos na instanciação da classe anônima dentro do corpo do método `newIntSeqBuf`. Criamos uma nova instância de `IntSeqBuffer` na qual o tipo `T` refere-se a `List[Int]`. + +Observe que muitas vezes é possível transformar os membros de tipo abstrato em parâmetros de tipo de classes e vice-versa. Aqui está uma versão do código acima que usa apenas parâmetros de tipo: + +```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) +} +``` + +Note que temos que usar [anotação de variância](variances.html) aqui; Caso contrário, não seríamos capazes de ocultar o tipo implementado pela sequência concreta do objeto retornado pelo método `newIntSeqBuf`. Além disso, há casos em que não é possível substituir tipos abstratos com parâmetros de tipo. diff --git a/_pt-br/tour/annotations.md b/_pt-br/tour/annotations.md new file mode 100644 index 0000000000..56032eaecb --- /dev/null +++ b/_pt-br/tour/annotations.md @@ -0,0 +1,150 @@ +--- +layout: tour +title: Anotações + +discourse: false + +partof: scala-tour + +num: 31 +next-page: default-parameter-values +previous-page: automatic-closures +language: pt-br +--- + +Anotações associam meta-informação com definições. + +Uma cláusula de anotação simples tem a forma `@C` ou `@C(a1,..., an)`. Aqui, `C` é um construtor de uma classe `C`, que deve estar em conformidade com a classe `scala.Annotation`. Todos os argumentos de construtor fornecidos `a1, .., an` devem ser expressões constantes (isto é, expressões em literais numéricos, strings, literais de classes, enumerações Java e matrizes uni-dimensionais). + +Uma cláusula de anotação se aplica à primeira definição ou declaração que a segue. Mais de uma cláusula de anotação pode preceder uma definição e uma declaração. Não importa a ordem em que essas cláusulas são declaradas. + +O significado das cláusulas de anotação é _dependente da implementação_. Na plataforma Java, as seguintes anotações Scala têm um significado padrão. + +| Scala | Java | +| ------ | ------ | +| [`scala.SerialVersionUID`](https://www.scala-lang.org/api/current/scala/SerialVersionUID.html) | [`serialVersionUID`](http://java.sun.com/j2se/1.5.0/docs/api/java/io/Serializable.html#navbar_bottom) (field) | +| [`scala.deprecated`](https://www.scala-lang.org/api/current/scala/deprecated.html) | [`java.lang.Deprecated`](http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Deprecated.html) | +| [`scala.inline`](https://www.scala-lang.org/api/current/scala/inline.html) (desde 2.6.0) | não há equivalente | +| [`scala.native`](https://www.scala-lang.org/api/current/scala/native.html) (desde 2.6.0) | [`native`](http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html) (keyword) | +| [`scala.remote`](https://www.scala-lang.org/api/current/scala/remote.html) | [`java.rmi.Remote`](http://java.sun.com/j2se/1.5.0/docs/api/java/rmi/Remote.html) | +| [`scala.throws`](https://www.scala-lang.org/api/current/scala/throws.html) | [`throws`](http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html) (keyword) | +| [`scala.transient`](https://www.scala-lang.org/api/current/scala/transient.html) | [`transient`](http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html) (keyword) | +| [`scala.unchecked`](https://www.scala-lang.org/api/current/scala/unchecked.html) (since 2.4.0) | não há equivalente | +| [`scala.volatile`](https://www.scala-lang.org/api/current/scala/volatile.html) | [`volatile`](http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html) (keyword) | +| [`scala.beans.BeanProperty`](https://www.scala-lang.org/api/current/scala/beans/BeanProperty.html) | [`Design pattern`](http://docs.oracle.com/javase/tutorial/javabeans/writing/properties.html) | + +No exemplo a seguir, adicionamos a anotação `throws` à definição do método `read` para capturar a exceção lançada no código Java. + +> Um compilador Java verifica se um programa contém manipuladores para [exceções verificadas](http://docs.oracle.com/javase/specs/jls/se5.0/html/exceptions.html) analisando quais exceções verificadas podem resultar da execução de um método ou construtor. Para cada exceção verificada que é um resultado possível, a cláusula **throws** para o método ou construtor _deve_ mencionar a classe dessa exceção ou uma das superclasses da classe dessa exceção. +> Como Scala não tem exceções verificadas, os métodos Scala _devem_ ser anotados com uma ou mais anotações `throws`, de forma que o código Java possa capturar exceções lançadas por um método Scala. + + +Exemplo de classe Scala que lança uma exceção do tipo `IOException`: + +``` +package examples +import java.io._ +class Reader(fname: String) { + private val in = new BufferedReader(new FileReader(fname)) + @throws(classOf[IOException]) + def read() = in.read() +} +``` + +O programa Java a seguir imprime o conteúdo do arquivo cujo nome é passado como o primeiro argumento para o método `main`. + +``` +package test; +import examples.Reader; // Classe Scala acima declarada!! +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()); + } + } +} +``` + +Comentando-se a anotação `throws` na classe `Reader` o compilador produz a seguinte mensagem de erro ao compilar o programa principal Java: + +``` +Main.java:11: exception java.io.IOException is never thrown in body of +corresponding try statement + } catch (java.io.IOException e) { + ^ +1 error +``` + +### Anotações Java ### + +**Nota:** Certifique-se de usar a opção `-target: jvm-1.5` com anotações Java. + +Java 1.5 introduziu metadados definidos pelo usuário na forma de [anotações](http://java.sun.com/j2se/1.5.0/docs/guide/language/annotations.html). Uma característica chave das anotações é que elas dependem da especificação de pares no formato nome-valor para inicializar seus elementos. Por exemplo, se precisamos de uma anotação para rastrear a origem de alguma classe, podemos defini-la como: + +``` +@interface Source { + public String URL(); + public String mail(); +} +``` + +O uso da anotação Source fica da seguinte forma + +``` +@Source(URL = "http://coders.com/", + mail = "support@coders.com") +public class MyClass extends HisClass ... +``` + +A uso de anotações em Scala parece uma invocação de construtor, para instanciar uma anotação Java é preciso usar argumentos nomeados: + +``` +@Source(URL = "http://coders.com/", + mail = "support@coders.com") +class MyScalaClass ... +``` + +Esta sintaxe é bastante tediosa, se a anotação contiver apenas um parâmetro (sem valor padrão), por convenção, se o nome for especificado como `value`, ele pode ser aplicado em Java usando uma sintaxe semelhante a Scala, ou seja parecido com a invocação de um construtor: + +``` +@interface SourceURL { + public String value(); + public String mail() default ""; +} +``` + +O uso da anotação SourceURL fica da seguinte forma + +``` +@SourceURL("http://coders.com/") +public class MyClass extends HisClass ... +``` + +Neste caso, a Scala oferece a mesma possibilidade + +``` +@SourceURL("http://coders.com/") +class MyScalaClass ... +``` + +O elemento `mail` foi especificado com um valor padrão, portanto não precisamos fornecer explicitamente um valor para ele. No entanto, se precisarmos fazer isso, não podemos misturar e combinar os dois estilos em Java: + +``` +@SourceURL(value = "http://coders.com/", + mail = "support@coders.com") +public class MyClass extends HisClass ... +``` + +Scala proporciona mais flexibilidade a respeito disso: + +``` +@SourceURL("http://coders.com/", + mail = "support@coders.com") + class MyScalaClass ... +``` diff --git a/_pt-br/tour/anonymous-function-syntax.md b/_pt-br/tour/anonymous-function-syntax.md new file mode 100644 index 0000000000..94120e366b --- /dev/null +++ b/_pt-br/tour/anonymous-function-syntax.md @@ -0,0 +1,55 @@ +--- +layout: tour +title: Sintaxe de Função Anônima + +discourse: false + +partof: scala-tour + +num: 6 +next-page: higher-order-functions +previous-page: mixin-class-composition +language: pt-br +--- + +Scala fornece uma sintaxe relativamente leve para definir funções anônimas. A expressão a seguir cria uma função sucessor para inteiros: + +```tut +(x: Int) => x + 1 +``` + +Isso é uma abreviação para a definição de classe anônima a seguir: + +```tut +new Function1[Int, Int] { + def apply(x: Int): Int = x + 1 +} +``` + +Também é possível definir funções com múltiplos parâmetros: + +```tut +(x: Int, y: Int) => "(" + x + ", " + y + ")" +``` + +ou sem parâmetros: + +```tut +() => { System.getProperty("user.dir") } +``` + +Há também uma forma muito simples de escrever tipos de funções. Aqui estão os tipos da três funções acima definidas: + +``` +Int => Int +(Int, Int) => String +() => String +``` + +Essa sintaxe é uma abreviação para os seguintes tipos: + +``` +Function1[Int, Int] +Function2[Int, Int, String] +Function0[String] +``` diff --git a/_pt-br/tour/automatic-closures.md b/_pt-br/tour/automatic-closures.md new file mode 100644 index 0000000000..3d919a9ec5 --- /dev/null +++ b/_pt-br/tour/automatic-closures.md @@ -0,0 +1,75 @@ +--- +layout: tour +title: Construção Automática de Closures de Tipo-Dependente + +discourse: false + +partof: scala-tour + +num: 30 +next-page: annotations +previous-page: operators +language: pt-br +--- + +_Nota de tradução: A palavra `closure` em pode ser traduzida como encerramento/fechamento, porém é preferível utilizar a notação original_ + +Scala permite funções sem parâmetros como parâmetros de métodos. Quando um tal método é chamado, os parâmetros reais para nomes de função sem parâmetros não são avaliados e uma função nula é passada em vez disso, tal função encapsula a computação do parâmetro correspondente (isso é conhecido por avaliação *call-by-name*). + +O código a seguir demonstra esse mecanismo: + +```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 + } +} +``` + +A função `whileLoop` recebe dois parâmetros: `cond` e `body`. Quando a função é aplicada, os parâmetros reais não são avaliados. Mas sempre que os parâmetros formais são usados no corpo de `whileLoop`, as funções nulas criadas implicitamente serão avaliadas em seu lugar. Assim, o nosso método `whileLoop` implementa um while-loop Java-like com um esquema de implementação recursiva. + +Podemos combinar o uso de [operadores infix/postfix](operators.html) com este mecanismo para criar declarações mais complexas (com uma sintaxe agradável). + +Aqui está a implementação de uma instrução que executa loop a menos que uma condição seja satisfeita: + +```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) +} +``` + +A função `loop` aceita apenas um corpo e retorna uma instância da classe` LoopUnlessCond` (que encapsula este objeto de corpo). Note que o corpo ainda não foi avaliado. A classe `LoopUnlessCond` tem um método `unless` que podemos usar como um *operador infix*. Dessa forma, obtemos uma sintaxe bastante natural para nosso novo loop: `loop { } unless ( )`. + +Aqui está a saída de quando o `TargetTest2` é executado: + +``` +i = 10 +i = 9 +i = 8 +i = 7 +i = 6 +i = 5 +i = 4 +i = 3 +i = 2 +i = 1 +``` diff --git a/_pt-br/tour/case-classes.md b/_pt-br/tour/case-classes.md new file mode 100644 index 0000000000..e2eb9fa655 --- /dev/null +++ b/_pt-br/tour/case-classes.md @@ -0,0 +1,142 @@ +--- +layout: tour +title: Classes Case + +discourse: false + +partof: scala-tour + +num: 10 +next-page: pattern-matching +previous-page: currying +language: pt-br +--- + +Scala suporta o conceito de _classes case_. Classes case são classes regulares que são: + +* Imutáveis por padrão +* Decompostas por meio de [correspondência de padrões](pattern-matching.html) +* Comparadas por igualdade estrutural ao invés de referência +* Sucintas para instanciar e operar + +Aqui temos um exemplo de hierarquia de tipos para *Notification* que consiste em uma super classe abstrata `Notification` e três tipos concretos de notificação implementados com classes case `Email`, `SMS`, e `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 +``` + +Instânciar uma classe case é fácil: (Perceba que nós não precisamos da palavra-chave `new`) + +```tut +val emailDeJohn = Email("john.doe@mail.com", "Saudações do John!", "Olá Mundo") +``` + +Os parâmetros do construtor de uma classe case são tratados como valores públicos e podem ser acessados diretamente. + +```tut +val titulo = emailDeJohn.title +println(titulo) // prints "Saudações do John!" +``` + +Com classes case, você não pode alterar seus campos diretamente. (ao menos que você declare `var` antes de um campo, mas fazê-lo geralmente é desencorajado). + +```tut:fail +emailDeJohn.title = "Adeus do John!" // Erro the compilação. Não podemos atribuir outro valor para um campo que foi declarado como val, lembrando que todos os campos de classes case são val por padrão. +``` + +Ao invés disso, faça uma cópia utilizando o método `copy`. Como descrito abaixo, então você poderá substituir alguns dos campos: + +```tut +val emailEditado = emailDeJohn.copy(title = "Estou aprendendo Scala!", body = "É muito legal!") + +println(emailDeJohn) // prints "Email(john.doe@mail.com,Saudações do John!,Hello World!)" +println(emailEditado) // prints "Email(john.doe@mail.com,Estou aprendendo Scala,É muito legal!)" +``` + +Para cada classe case em Scala o compilador gera um método `equals` que implementa a igualdade estrutural e um método `toString`. Por exemplo: + +```tut +val primeiroSMS = SMS("12345", "Hello!") +val segundoSMS = SMS("12345", "Hello!") + +if (primeiroSMS == segundoSMS) { + println("Somos iguais!") +} + +println("SMS é: " + primeiroSMS) +``` + +Irá gerar como saída: + +``` +Somos iguais! +SMS é: SMS(12345, Hello!) +``` + +Com classes case, você pode utilizar **correspondência de padrões** para manipular seus dados. Aqui temos um exemplo de uma função que escreve como saída diferente mensagens dependendo do tipo de notificação recebida: + +```tut +def mostrarNotificacao(notificacao: Notification): String = { + notificacao match { + case Email(email, title, _) => + "Você recebeu um email de " + email + " com o título: " + title + case SMS(number, message) => + "Você recebeu um SMS de" + number + "! Mensagem: " + message + case VoiceRecording(name, link) => + "Você recebeu uma Mensagem de Voz de " + name + "! Clique no link para ouvir: " + link + } +} + +val algumSMS = SMS("12345", "Você está aí?") +val algumaMsgVoz = VoiceRecording("Tom", "voicerecording.org/id/123") + +println(mostrarNotificacao(algumSMS)) +println(mostrarNotificacao(algumaMsgVoz)) + +// Saída: +// Você recebeu um SMS de 12345! Mensagem: Você está aí? +// Você recebeu uma Mensagem de Voz de Tom! Clique no link para ouvir: voicerecording.org/id/123 +``` + +Aqui um exemplo mais elaborado utilizando a proteção `if`. Com a proteção `if`, o correspondência de padrão irá falhar se a condição de proteção retorna falso. + +```tut +def mostrarNotificacaoEspecial(notificacao: Notification, emailEspecial: String, numeroEspecial: String): String = { + notificacao match { + case Email(email, _, _) if email == emailEspecial => + "Você recebeu um email de alguém especial!" + case SMS(numero, _) if numero == numeroEspecial => + "Você recebeu um SMS de alguém especial!" + case outro => + mostrarNotificacao(outro) // Nada especial para mostrar, então delega para nossa função original mostrarNotificacao + } +} + +val NumeroEspecial = "55555" +val EmailEspecial = "jane@mail.com" +val algumSMS = SMS("12345", "Você está aí?") +val algumaMsgVoz = VoiceRecording("Tom", "voicerecording.org/id/123") +val emailEspecial = Email("jane@mail.com", "Beber hoje a noite?", "Estou livre depois das 5!") +val smsEspecial = SMS("55555", "Estou aqui! Onde está você?") + +println(mostrarNotificacaoEspecial(algumSMS, EmailEspecial, NumeroEspecial)) +println(mostrarNotificacaoEspecial(algumaMsgVoz, EmailEspecial, NumeroEspecial)) +println(mostrarNotificacaoEspecial(smsEspecial, EmailEspecial, NumeroEspecial)) +println(mostrarNotificacaoEspecial(smsEspecial, EmailEspecial, NumeroEspecial)) + +// Saída: +// Você recebeu um SMS de 12345! Mensagem: Você está aí? +// Você recebeu uma Mensagem de Voz de Tom! Clique no link para ouvir: voicerecording.org/id/123 +// Você recebeu um email de alguém especial! +// Você recebeu um SMS de alguém especial! + +``` + +Ao programar em Scala, recomenda-se que você use classes case de forma pervasiva para modelar / agrupar dados, pois elas ajudam você a escrever código mais expressivo e passível de manutenção: + +* Imutabilidade libera você de precisar acompanhar onde e quando as coisas são mutadas +* Comparação por valor permite comparar instâncias como se fossem valores primitivos - não há mais incerteza sobre se as instâncias de uma classe é comparada por valor ou referência +* Correspondência de padrões simplifica a lógica de ramificação, o que leva a menos bugs e códigos mais legíveis. \ No newline at end of file diff --git a/_pt-br/tour/classes.md b/_pt-br/tour/classes.md new file mode 100644 index 0000000000..1b9f46df7a --- /dev/null +++ b/_pt-br/tour/classes.md @@ -0,0 +1,55 @@ +--- +layout: tour +title: Classes + +discourse: false + +partof: scala-tour + +num: 3 +next-page: traits +previous-page: unified-types +language: pt-br +--- + +Classes em Scala são templates estáticos que podem ser instanciados como vários objetos em tempo de execução. +Aqui uma definição de classe que define a classe `Ponto`: + +```tut +class Ponto(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 + ")" +} +``` + +Classes em Scala são parametrizadas com argumentos de construtor. O código acima define dois argumentos de construtor, `x` e `y`; ambos são acessíveis por todo o corpo da classe. + +A classe também inclui dois métodos, `move` and `toString`. `move` recebe dois parâmetros inteiros mas não retorna um valor (o tipo de retorno `Unit` equivale ao `void` em linguagens como Java). `toString`, por outro lado, não recebe parâmetro algum mas retorna um valor `String`. Dado que `toString` sobrescreve o método pré-definido `toString`, o mesmo é marcado com a palavra-chave `override`. + +Perceba que em Scala, não é necessário declarar `return` para então retornar um valor. O valor retornado em um método é simplesmente o último valor no corpo do método. No caso do método `toString` acima, a expressão após o sinal de igual é avaliada e retornada para quem chamou a função. + +Classes são instânciadas com a primitiva `new`, por exemplo: + +```tut +object Classes { + def main(args: Array[String]) { + val pt = new Ponto(1, 2) + println(pt) + pt.move(10, 10) + println(pt) + } +} +``` + +O programa define uma aplicação executável chamada Classes como um [Objeto Singleton](singleton-objects) dentro do método `main`. O método `main` cria um novo `Ponto` e armazena o valor em `pt`. Perceba que valores definidos com o construtor `val` são diferentes das variáveis definidas com o construtor `var` (veja acima a classe `Ponto`), `val` não permite atualização do valor, ou seja, o valor é uma constante. + +Aqui está a saída do programa: + +``` +(1, 2) +(11, 12) +``` diff --git a/_pt-br/tour/compound-types.md b/_pt-br/tour/compound-types.md new file mode 100644 index 0000000000..0b06a4b1ba --- /dev/null +++ b/_pt-br/tour/compound-types.md @@ -0,0 +1,54 @@ +--- +layout: tour +title: Tipos Compostos + +discourse: false + +partof: scala-tour + +num: 23 +next-page: explicitly-typed-self-references +previous-page: abstract-types +language: pt-br +--- + +Às vezes é necessário expressar que o tipo de um objeto é um subtipo de vários outros tipos. Em Scala isso pode ser expresso com a ajuda de *tipos compostos*, que são interseções de tipos de objetos. + +Suponha que temos duas traits `Cloneable` and `Resetable`: + +```tut +trait Cloneable extends java.lang.Cloneable { + override def clone(): Cloneable = { + super.clone().asInstanceOf[Cloneable] + } +} +trait Resetable { + def reset: Unit +} +``` + +Agora supondo que queremos escrever uma função `cloneAndReset` que recebe um objeto, clona e reseta o objeto original: + +``` +def cloneAndReset(obj: ?): Cloneable = { + val cloned = obj.clone() + obj.reset + cloned +} +``` + +A questão é: qual é o tipo do parâmetro `obj`? Se for `Cloneable` então o objeto pode ser clonado, mas não resetado; Se for `Resetable` nós podemos resetar, mas não há nenhuma operação para clonar. Para evitar conversão de tipos em tal situação, podemos especificar o tipo de `obj` para ser tanto `Cloneable` como `Resetable`. Este tipo composto pode ser escrito da seguinte forma em Scala: `Cloneable with Resetable`. + +Aqui está a função atualizada: + +``` +def cloneAndReset(obj: Cloneable with Resetable): Cloneable = { + //... +} +``` + +Os tipos de compostos podem consistir em vários tipos de objeto e eles podem ter um único refinamento que pode ser usado para restrigir a assinatura de membros de objetos existentes. + +A forma geral é: `A with B with C ... { refinamento }` + +Um exemplo para o uso de refinamentos é dado na página sobre [tipos abstratos](abstract-types.html). diff --git a/_pt-br/tour/currying.md b/_pt-br/tour/currying.md new file mode 100644 index 0000000000..505c806734 --- /dev/null +++ b/_pt-br/tour/currying.md @@ -0,0 +1,44 @@ +--- +layout: tour +title: Currying + +discourse: false + +partof: scala-tour + +num: 9 +next-page: case-classes +previous-page: nested-functions +language: pt-br +--- + +_Nota de tradução: Currying é uma técnica de programação Funcional nomeada em honra ao matemático e lógico Haskell Curry. Por essa razão a palavra Currying não será traduzida. Entende-se que é uma ação, uma técnica básica de Programação Funcional._ + +Métodos podem definir múltiplas listas de parâmetros. Quando um método é chamado com uma lista menor de parâmetros, então será retornada uma função que recebe a lista que parâmetros que falta como argumentos. + +Aqui um exemplo: + +```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))) +} +``` + +_Nota: o método `modN` é parcialmente aplicado em duas chamadas de `filter`; por exemplo: somente o primeiro argumento é realmente aplicado. O termo `modN(2)` retorna uma função do tipo `Int => Boolean` e esta se torna uma possível candidata a segundo argumento da função `filter`._ + +A saída do programa acima produz: + +``` +List(2,4,6,8) +List(3,6) +``` diff --git a/_pt-br/tour/default-parameter-values.md b/_pt-br/tour/default-parameter-values.md new file mode 100644 index 0000000000..dedcc4e702 --- /dev/null +++ b/_pt-br/tour/default-parameter-values.md @@ -0,0 +1,75 @@ +--- +layout: tour +title: Parâmetro com Valor Padrão + +discourse: false + +partof: scala-tour + +num: 32 +next-page: named-parameters +previous-page: annotations +language: pt-br +--- + +Scala provê a capacidade de fornecer parâmetros com valores padrão que podem ser usados para permitir que um usuário possa omitir tais parâmetros se preciso. + +Em Java, é comum ver um monte de métodos sobrecarregados que servem apenas para fornecer valores padrão para determinados parâmetros de um método maior. Isso é especialmente verdadeiro com os construtores: + +```java +public class HashMap { + public HashMap(Map m); + /** Cria um novo HashMap com a capacidade padrão (16) + * and loadFactor (0.75) + */ + public HashMap(); + /** Cria um novo HashMap com um fator de carga padrão (0.75) */ + public HashMap(int initialCapacity); + public HashMap(int initialCapacity, float loadFactor); +} +``` + +Há realmente apenas dois construtores aqui; Um que recebe um map e outro que tem uma capacidade e um fator de carga. O terceiro e o quarto construtores estão lá para permitir que os usuários do HashMap criem instâncias com os valores padrões de fator de carga e capacidade, que provavelmente são bons para a maioria dos casos. + +O maior problema é que os valores usados como padrões estão declarados no Javadoc *e* no código. Manter isso atualizado é complicado, pois pode ser esquecido facilmente. Um abordagem típica nesses casos seria adicionar constantes públicas cujos valores aparecerão no 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); + /** Cria um novo HashMap com capacidade padrão (16) + * e fator de carga padrão (0.75) + */ + public HashMap(); + /** Cria um novo HashMap com um fator de carga padrão (0.75) */ + public HashMap(int initialCapacity); + public HashMap(int initialCapacity, float loadFactor); +} +``` + +Enquanto isso nos impede de nos repetir, é menos do que expressivo. + +Scala adiciona suporte direto para isso: + +```tut +class HashMap[K,V](initialCapacity:Int = 16, loadFactor:Float = 0.75f) { +} + +// Utiliza os valores padrões (16, 0.75f) +val m1 = new HashMap[String,Int] + +// Inicial com capacidade 20, e fator de carga padrão +val m2= new HashMap[String,Int](20) + +// Sobreescreve ambos os valores +val m3 = new HashMap[String,Int](20,0.8f) + +// Sobreescreve somente o fator de carga +// parâmetro nomeado +val m4 = new HashMap[String,Int](loadFactor = 0.8f) +``` + +Observe como podemos tirar proveito de *qualquer* valor padrão usando [parâmetros nomeados](named-parameters.html). + diff --git a/_pt-br/tour/explicitly-typed-self-references.md b/_pt-br/tour/explicitly-typed-self-references.md new file mode 100644 index 0000000000..432e8061f9 --- /dev/null +++ b/_pt-br/tour/explicitly-typed-self-references.md @@ -0,0 +1,123 @@ +--- +layout: tour +title: Auto Referências Explicitamente Tipadas + +discourse: false + +partof: scala-tour + +num: 24 +next-page: implicit-parameters +previous-page: compound-types +language: pt-br +--- + +Ao desenvolver um software extensível, às vezes é útil declarar explicitamente o tipo do valor `this`. Para ilustrar isso, criaremos uma pequena representação extensível de uma estrutura de dados de grafo em Scala. + +Aqui está uma definição que descreve um grafo: + +```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 +} +``` + +Um grafo consiste em uma lista de nós e arestas onde o nó e o tipo de aresta são declarados como abstratos. O uso de [tipos abstratos](abstract-types.html) permite que a implementação da trait `Graph` forneça suas próprias classes concretas para nós e arestas. Além disso, existe um método `addNode` para adicionar novos nós a um grafo. Os nós são conectados usando o método `connectWith`. + +Uma possível implementação de `Graph` é ilustrada na classe a seguir: + +```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 + } +} +``` + +A classe `DirectedGraph` estende a classe `Graph` fornecendo uma implementação parcial. A implementação é apenas parcial porque gostaríamos de poder ampliar o `DirectedGraph`. Portanto, esta classe deixa todos os detalhes de implementação abertos e assim, tanto as arestas quanto os nós são definidos como abstratos. No entanto, a classe `DirectedGraph` revela alguns detalhes adicionais sobre a implementação do tipo das arestas ao restringir o limite de tipo para a classe `EdgeImpl`. Além disso, temos algumas implementações preliminares de arestas e nós representados pelas classes `EdgeImpl` e `NodeImpl`. Uma vez que é necessário criar novos objetos nó e aresta dentro da nossa implementação de grafo, também temos que adicionar os métodos de construção `newNode` e `newEdge`. Os métodos `addNode` e `connectWith` são ambos definidos em termos destes métodos de construção. Uma análise mais detalhada da implementação do método `connectWith` revela que, para criar uma aresta, temos que passar a auto-referência `this` para o método de construção `newEdge`. Mas a `this` é atribuído o tipo `NodeImpl`, por isso não é compatível com o tipo `Node` que é exigido pelo método de construção correspondente. Como consequência, o programa acima não é bem-formado e o compilador Scala irá emitir uma mensagem de erro. + +Em Scala é possível vincular uma classe a outro tipo (que será implementado no futuro) ao fornecer a auto referência `this` ao outro tipo explicitamente. Podemos usar esse mecanismo para corrigir nosso código acima. O tipo explícito de `this` é especificado dentro do corpo da classe `DirectedGraph`. + +Aqui está o programa já corrigido: + +```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 => // nova linha adicionada + def connectWith(node: Node): Edge = { + val edge = newEdge(this, node) // agora válido + 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 + } +} +``` + +Nesta nova definição de classe `NodeImpl`, `this` tem o tipo `Node`. Como o tipo `Node` é abstrato e, portanto, ainda não sabemos se `NodeImpl` é realmente um subtipo de `Node`, o sistema de tipo Scala não nos permitirá instanciar esta classe. No entanto declaramos com a anotação de tipo explícito que, em algum ponto, (uma subclasse de) `NodeImpl` precisa denotar um subtipo de tipo `Node` para ser instantiável. + +Aqui está uma especialização concreta de `DirectedGraph` onde todos os membros da classe abstrata são definidos: + +```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) +} +``` + +Observe que nesta classe, podemos instanciar `NodeImpl` porque agora sabemos que `NodeImpl` representa um subtipo de tipo `Node` (que é simplesmente um *alias* para `NodeImpl`). + +Aqui está um exemplo de uso da classe `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) +} +``` \ No newline at end of file diff --git a/_pt-br/tour/extractor-objects.md b/_pt-br/tour/extractor-objects.md new file mode 100644 index 0000000000..d1e71d4330 --- /dev/null +++ b/_pt-br/tour/extractor-objects.md @@ -0,0 +1,43 @@ +--- +layout: tour +title: Objetos Extratores + +discourse: false + +partof: scala-tour + +num: 15 +next-page: sequence-comprehensions +previous-page: regular-expression-patterns +language: pt-br +--- + +Em Scala, padrões podem ser definidos independentemente de classes case. Para este fim, um método chamado `unapply` é definido para retornar um extrator. Um extrator pode ser pensado como um método especial que inverte o efeito da aplicação de um determinado objeto em algumas entradas. Seu objetivo é "extrair" as entradas que estavam presentes antes da operação `apply`. Por exemplo, o código a seguir define um [objeto](singleton-objects.html) extrator chamado 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) } // prints 21 +} +``` + +Existem duas convenções sintáticas em ação aqui: + +O padrão `case Twice (n)` causa a invocação do método `Twice.unapply`, que é usado para fazer a comparação de qualquer número par; O valor de retorno de `unapply` indica se a comparação falhou ou não, e quaisquer sub-valores que possam ser utilizados para uma seguinte comparação. Aqui, o sub-valor é `z/2`. + +O método `apply` não é necessário na correspondência de padrões. É utilizado somente para simular um construtor. `val x = Twice(21)` é expandido para `val x = Twice.apply(21)`. + +O tipo de retorno de uma chamada `unapply` deveria ser escolhido da seguinta forma: + +* Se é somente um teste, retorne `Boolean`. Por exemplo `case even()` +* Se retorna um único subvalor to tipo `T`, retorne `Option[T]` +* Se você quer retornar vários subvalores `T1,...,Tn`, agrupe todos em uma tupla opcional como `Option[(T1,...,Tn)]`. + +Algumas vezes, o número de subvalores é fixo e você precisa retornar uma sequência. Para isso, você pode definir padrões através da chamada `unapplySeq`. O último subvalor do tipo `Tn` precisa ser `Seq[S]`. Tal mecanismo é utilizado como exemplo no padrão `case List(x1, ..., xn)`. + +Extratores podem tornar o código mais fácil de manter. Para mais detalhes, leia o artigo ["Matching Objects with Patterns"](https://infoscience.epfl.ch/record/98468/files/MatchingObjectsWithPatterns-TR.pdf) (veja a seção 4) by Emir, Odersky and Williams (January 2007). diff --git a/_pt-br/tour/generic-classes.md b/_pt-br/tour/generic-classes.md new file mode 100644 index 0000000000..b9e8fe31bc --- /dev/null +++ b/_pt-br/tour/generic-classes.md @@ -0,0 +1,48 @@ +--- +layout: tour +title: Classes Genéricas + +discourse: false + +partof: scala-tour + +num: 17 +next-page: variances +previous-page: sequence-comprehensions +language: pt-br +--- + +Semelhante ao Java 5 (aka. [JDK 1.5](http://java.sun.com/j2se/1.5/)), Scala tem suporte nativo para classes parametrizadas com tipos. Essas classes genéricas são particularmente úteis para o desenvolvimento de classes que representam coleções de dados. +Aqui temos um exemplo que demonstra isso: + +```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 } +} +``` + +A classe `Stack` modela uma pilha mutável que contém elementos de um tipo arbitrário `T`. Os parâmetros de tipo garantem que somente os elementos legais (que são do tipo `T`) são inseridos na pilha. Da mesma forma, com os parâmetros de tipo podemos expressar que o método `top` retorna somente elementos de um único tipo de dado, no caso, `T`. + +Aqui temos mais alguns exemplos de uso: + +```tut +object GenericsTest extends App { + val stack = new Stack[Int] + stack.push(1) + stack.push('a') + println(stack.top) + stack.pop() + println(stack.top) +} +``` + +A saída do programa é: + +``` +97 +1 +``` +_Nota: subtipos de tipos genéricos são *invariantes*. Isto significa que se tivermos uma pilha de caracteres do tipo `Stack[Char]` então ela não pode ser usada como uma pilha de inteiros do tipo `Stack[Int]`. Isso seria incorreto porque isso nos permitiria inserir inteiros verdadeiros na pilha de caracteres. Para concluir, `Stack[T]` é um subtipo de de `Stack[S]` se e somente se `S = T`. Como isso pode ser bastante restritivo, Scala oferece um [mecanismo de anotação de parâmetro de tipo](variances.html) para controlar o comportamento de subtipo de tipos genéricos._ \ No newline at end of file diff --git a/_pt-br/tour/higher-order-functions.md b/_pt-br/tour/higher-order-functions.md new file mode 100644 index 0000000000..8cb5f3acc6 --- /dev/null +++ b/_pt-br/tour/higher-order-functions.md @@ -0,0 +1,43 @@ +--- +layout: tour +title: Funções de ordem superior + +discourse: false + +partof: scala-tour + +num: 7 +next-page: nested-functions +previous-page: anonymous-function-syntax +language: pt-br +--- + +Scala permite definir funções de ordem superior. Tais funções _recebem outras funções como parâmetros_, ou _resultam em uma função_. Por exemplo, a função `apply` recebe outra função `f` e um valor `v` então aplica a função `f` em`v`: + +```tut +def apply(f: Int => String, v: Int) = f(v) +``` + +_Nota: métodos são automaticamente convertidos em funções se o contexto demandar.**_ + +Outro exemplo: + +```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)) +} +``` + +A execução produz a saída: + +``` +[7] +``` + +Nesse exemplo, o método `decorator.layout` é automaticamente convertido em um valor do tipo `Int => String` conforme o método `apply` demanda. Note que o método `decorator.layout` é um _método polimórfico_ (por exemplo: ele abstrai alguns tipos de sua assinatura) e o compilador Scala precisa primeiro instanciar corretamento o tipo do método. diff --git a/_pt-br/tour/implicit-conversions.md b/_pt-br/tour/implicit-conversions.md new file mode 100644 index 0000000000..f4686c9001 --- /dev/null +++ b/_pt-br/tour/implicit-conversions.md @@ -0,0 +1,54 @@ +--- +layout: tour +title: Conversões Implícitas + +discourse: false + +partof: scala-tour + +num: 26 +next-page: polymorphic-methods +previous-page: implicit-parameters +language: pt-br +--- + +Uma conversão implícita do tipo `S` para o tipo `T` é definida por um valor implícito que tem o tipo de função `S => T`, ou por um método implícito convertível em um valor de tal tipo. + +As conversões implícitas são aplicadas em duas situações: + +* Se uma expressão `e` for do tipo `S` e `S` não estiver em conformidade com o tipo esperado `T` da expressão. +* Em uma seleção `e.m` com `e` do tipo `T`, se o seletor `m` não representar um membro de `T`. + +No primeiro caso, é procurada uma conversão `c` que seja aplicável a `e` e cujo tipo de resultado esteja em conformidade com `T`. + +No segundo caso, é procurada uma conversão `c` que seja aplicável a `e` e cujo resultado contém um membro chamado `m`. + +A seguinte operação nas duas listas xs e ys do tipo `List[Int]` é válida: + +``` +xs <= ys +``` + +Assuma que os métodos implícitos `list2ordered` e` int2ordered` definidos abaixo estão no mesmo escopo: + +``` +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] { /* .. */ } +``` + +O objeto implicitamente importado `scala.Predef` declara vários tipos predefinidos (por exemplo, `Pair`) e métodos (por exemplo, `assert`), mas também várias conversões implícitas. + +Por exemplo, ao chamar um método Java que espera um `java.lang.Integer`, você está livre para passar um `scala.Int` em vez disso. Isso ocorre porque `Predef` inclui as seguintes conversões implícitas: + +```tut +import scala.language.implicitConversions + +implicit def int2Integer(x: Int) = + java.lang.Integer.valueOf(x) +``` + +Para definir suas próprias conversões implícitas, primeiro você deve importar `scala.language.implicitConversions` (ou invocar o compilador com a opção `-language: implicitConversions`). Tal recurso deve ser explicitamente habilitado porque pode se tornar complexo se usado indiscriminadamente. diff --git a/_pt-br/tour/implicit-parameters.md b/_pt-br/tour/implicit-parameters.md new file mode 100644 index 0000000000..60177c17cb --- /dev/null +++ b/_pt-br/tour/implicit-parameters.md @@ -0,0 +1,60 @@ +--- +layout: tour +title: Parâmetros Implícitos + +discourse: false + +partof: scala-tour + +num: 25 +next-page: implicit-conversions +previous-page: explicitly-typed-self-references +language: pt-br +--- + +Um método com _parâmetros implícitos_ pode ser aplicado a argumentos como um método normal. Neste caso, o rótulo implícito não tem efeito. No entanto, se faltarem argumentos para os parâmetros implícitos declarados, tais argumentos serão automaticamente fornecidos. + +Os argumentos reais que são elegíveis para serem passados para um parâmetro implícito se dividem em duas categorias: + +* Primeira, são elegíveis todos os identificadores x que podem ser acessados no ponto da chamada do método sem um prefixo e que denotam uma definição implícita ou um parâmetro implícito. + +* Segunda, são elegíveis também todos os membros dos módulos acompanhantes do tipo do parâmetro implícito que são rotulados como `implicit`. + +No exemplo a seguir, definimos um método `sum` que calcula a soma de uma lista de elementos usando as operações `add` e `unit` do monoide. Observe que valores implícitos não podem ser *top-level*, eles precisam ser membros de um modelo. + +```tut +/** Este exemplo usa uma estrutura da álgebra abstrata para mostrar como funcionam os parâmetros implícitos. Um semigrupo é uma estrutura algébrica em um conjunto A com uma operação (associativa), chamada add, que combina um par de A's e retorna um outro A. */ +abstract class SemiGroup[A] { + def add(x: A, y: A): A +} +/** Um monóide é um semigrupo com um elemento distinto de A, chamado unit, que quando combinado com qualquer outro elemento de A retorna esse outro elemento novamente. */ +abstract class Monoid[A] extends SemiGroup[A] { + def unit: A +} +object ImplicitTest extends App { + /** Para mostrar como os parâmetros implícitos funcionam, primeiro definimos os monóides para strings e inteiros. A palavra-chave implicit indica que o objeto correspondente pode ser usado implicitamente, dentro deste escopo, como um parâmetro de uma função definia como implícita. */ + 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 + } + /** Este método recebe uma List[A] retorna um A que representa o valor da combinação resultante ao aplicar a operação monóide sucessivamente em toda a lista. Tornar o parâmetro m implícito aqui significa que só temos de fornecer o parâmetro xs no local de chamada, pois se temos uma List[A] sabemos qual é realmente o tipo A e, portanto, o qual o tipo do Monoid[A] é necessário. Podemos então encontrar implicitamente qualquer val ou objeto no escopo atual que também tem esse tipo e usá-lo sem precisar especificá-lo explicitamente. */ + def sum[A](xs: List[A])(implicit m: Monoid[A]): A = + if (xs.isEmpty) m.unit + else m.add(xs.head, sum(xs.tail)) + + /** Aqui chamamos a função sum duas vezes, com apenas um parâmetro cada vez. Como o segundo parâmetro de soma, m, está implícito, seu valor é procurado no escopo atual, com base no tipo de monóide exigido em cada caso, o que significa que ambas as expressões podem ser totalmente avaliadas. */ + println(sum(List(1, 2, 3))) // uses IntMonoid implicitly + println(sum(List("a", "b", "c"))) // uses StringMonoid implicitly +} +``` + +Aqui está a saída do programa: + +``` +6 +abc +``` diff --git a/_pt-br/tour/inner-classes.md b/_pt-br/tour/inner-classes.md new file mode 100644 index 0000000000..266b8faad3 --- /dev/null +++ b/_pt-br/tour/inner-classes.md @@ -0,0 +1,98 @@ +--- +layout: tour +title: Classes Internas + +discourse: false + +partof: scala-tour + +num: 21 +next-page: abstract-types +previous-page: lower-type-bounds +language: pt-br +--- + +Em Scala é possível declarar classes que tenham outras classes como membros. Em contraste com a linguagenm Java, onde classes internas são membros da classe em que foram declaradas, em Scala as classes internas são ligadas ao objeto exterior. Para ilustrar essa diferença, rapidamente esboçamos a implementação de grafo como um tipo de dados: + +```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 + } +} +``` + +Em nosso programa, os grafos são representados por uma lista de nós. Os nós são objetos da classe interna `Node`. Cada nó tem uma lista de vizinhos, que são armazenados na lista `connectedNodes`. Agora podemos configurar um grafo com alguns nós e conectar os nós de forma incremental: + +```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) +} +``` + +Agora melhoramos o exemplo acima com tipos, para assim declarar explicitamente qual o tipo das várias entidades definidas: + +```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) +} +``` + +Este código mostra claramente que o tipo nó é prefixado com sua instância externa (em nosso exemplo é o objeto `g`). Se agora temos dois grafos, o sistema de tipos de Scala não nos permite misturar nós definidos dentro de um grafo com os nós de outro, já que os nós do outro grafo têm um tipo diferente. +Aqui está um programa inválido: + +```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) // legal + val h: Graph = new Graph + val n3: h.Node = h.newNode + n1.connectTo(n3) // illegal! +} +``` + +Observe que em Java a última linha no programa do exemplo anterior é válida. Para nós de ambos os grafos, Java atribuiria o mesmo tipo `Graph.Node`; isto é, `Node` é prefixado com a classe `Graph`. Em Scala, esse tipo também pode ser expresso, e é escrito `Graph#Node`. Se quisermos ser capazes de conectar nós de diferentes grafos, temos que mudar a definição inicial da nossa implementação do grafo da seguinte maneira: + +```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 + } +} +``` + +> Note que este programa não nos permite anexar um nó a dois grafos diferentes. Se quisermos também remover esta restrição, temos de mudar o tipo da variável `nodes` para `Graph#Node`. diff --git a/_pt-br/tour/local-type-inference.md b/_pt-br/tour/local-type-inference.md new file mode 100644 index 0000000000..a9f5de8f54 --- /dev/null +++ b/_pt-br/tour/local-type-inference.md @@ -0,0 +1,67 @@ +--- +layout: tour +title: Inferência de Tipo Local + +discourse: false + +partof: scala-tour + +num: 28 +next-page: operators +previous-page: polymorphic-methods +language: pt-br +--- + +Scala tem um mecanismo nativo de inferência de tipos que permite ao programador omitir certas anotações. Por exemplo, muitas vezes não é necessário especificar o tipo de uma variável, uma vez que o compilador pode deduzir o tipo a partir da expressão de inicialização da variável. Os tipos de retorno de métodos também podem muitas vezes ser omitidos, uma vez que correspondem ao tipo do corpo do método, que é inferido pelo compilador. + +Por exemplo: + +```tut +object InferenceTest1 extends App { + val x = 1 + 2 * 3 // o tipo de x é Int + val y = x.toString() // o tipo de y é String + def succ(x: Int) = x + 1 // o método succ retorna um valor Int +} +``` + +Para métodos recursivos, o compilador não é capaz de inferir o tipo de retorno. + +Exemplo de um método que não irá compilar por este motivo: + +```tut:fail +object InferenceTest2 { + def fac(n: Int) = if (n == 0) 1 else n * fac(n - 1) +} +``` + +Também não é obrigatório especificar os tipos dos parâmetros quando [métodos polimórficos](polymorphic-methods.html) são invocados ou são criadas instâncias de [classes genéricas](generic-classes.html). O compilador Scala irá inferir tais parâmetros que não estão presentes a partir do contexto das chamadas e dos tipos dos parâmetros reais do método/construtor. + +Por exemplo: + +``` +case class MyPair[A, B](x: A, y: B); +object InferenceTest3 extends App { + def id[T](x: T) = x + val p = MyPair(1, "scala") // type: MyPair[Int, String] + val q = id(1) // type: Int +} +``` + +As duas últimas linhas deste programa são equivalentes ao seguinte código onde todos os tipos inferidos são declarados explicitamente: + +``` +val x: MyPair[Int, String] = MyPair[Int, String](1, "scala") +val y: Int = id[Int](1) +``` + +Em algumas situações, pode ser muito perigoso confiar no mecanismo de inferência de tipos de Scala como mostra o seguinte programa: + + +```tut:fail +object InferenceTest4 { + var obj = null + obj = new Object() +} +``` + +Este programa não compila porque o tipo inferido para a variável `obj` é `Null`. Como o único valor desse tipo é `null`, é impossível fazer essa variável se referir a outro valor. diff --git a/_pt-br/tour/lower-type-bounds.md b/_pt-br/tour/lower-type-bounds.md new file mode 100644 index 0000000000..544609d6d7 --- /dev/null +++ b/_pt-br/tour/lower-type-bounds.md @@ -0,0 +1,59 @@ +--- +layout: tour +title: Limitante Inferior de Tipos + +discourse: false + +partof: scala-tour + +num: 20 +next-page: inner-classes +previous-page: upper-type-bounds +language: pt-br +--- + +Enquanto o [limitante superior de tipos](upper-type-bounds.html) limita um tipo a um subtipo de outro tipo, o *limitante inferior de tipos* declara um tipo para ser supertipo de outro tipo. O termo `T>: A` expressa que o parâmetro de tipo `T` ou tipo abstracto `T` refere-se a um supertipo do tipo `A`. + +Aqui está um exemplo onde isso é útil: + +```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) +} +``` + +O programa acima implementa uma linked list com uma operação de pré-inserção. Infelizmente, esse tipo é invariante no parâmetro de tipo da classe `ListNode`; Ou seja, `ListNode [String]` não é um subtipo de `ListNode [Any]`. Com a ajuda de [anotações de variância](variances.html) podemos expressar tal semântica de subtipo: + +``` +case class ListNode[+T](h: T, t: ListNode[T]) { ... } +``` + +Infelizmente, este programa não compila, porque uma anotação de covariância só é possível se a variável de tipo é usada somente em posições covariantes. Como a variável de tipo `T` aparece como um parâmetro de tipo do método `prepend`, tal regra é violada. Porém com a ajuda de um *limitante inferior de tipo*, podemos implementar um método de pré-inserção onde `T` só aparece em posições covariantes. + +Aqui está o código correspondente: + +```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) +} +``` + +_Nota:_ o novo método `prepend` tem um tipo ligeiramente menos restritivo. Permite, por exemplo, inserir um objeto de um supertipo a uma lista existente. A lista resultante será uma lista deste supertipo. + +Aqui está o código que ilustra isso: + +```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/_pt-br/tour/mixin-class-composition.md b/_pt-br/tour/mixin-class-composition.md new file mode 100644 index 0000000000..fea1eca336 --- /dev/null +++ b/_pt-br/tour/mixin-class-composition.md @@ -0,0 +1,57 @@ +--- +layout: tour +title: Composição de Classes Mixin + +discourse: false + +partof: scala-tour + +num: 5 +next-page: anonymous-function-syntax +previous-page: traits +language: pt-br +--- +_Nota de tradução: A palavra `mixin` pode ser traduzida como mescla, porém é preferível utilizar a notação original_ + +Ao contrário de linguagens que suportam somente _herança simples_, Scala tem uma noção mais abrangente sobre a reutilização de classes. Scala torna possível reutilizar a _nova definição de membros de uma classe_ (por exemplo: o relacionamento delta para com a superclasse) na definição de uma nova classe. Isso é expressado como uma _composição de classe mixin ou mixin-class composition_. Considere a seguinte abstração para iterators. + +```tut +abstract class AbsIterator { + type T + def hasNext: Boolean + def next: T +} +``` + +A seguir, considere a classe mixin que estende `AbsIterator` com um método `foreach` que aplica uma dada função para cada elemento retornado pelo iterator. Para definir tal classe que será utilizada como um mixin a palavra-chave `trait` deve ser declarada. + +```tut +trait RichIterator extends AbsIterator { + def foreach(f: T => Unit) { while (hasNext) f(next) } +} +``` + +Aqui uma classes iterator concreta a qual retorna sucessivos caracteres de uma dada string: + +```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 } +} +``` + +Poderíamos combinar a funcionalidade de `StringIterator` e `RichIterator` em uma só classe. Com herança simples e interfaces isso é impossível, pois ambas as classes contém implementações para seus membros. Scala nos ajuda com a sua _composição de classes mixin_. Isso permite que programadores reutilizem o delta de uma definição de uma classe, por exemplo: todas as novas definições não são herdadas. Esse mecanismo torna possível combinar `StringIterator` com `RichIterator`, como pode ser visto no programa teste a seguir, que imprime uma coluna de todos os caracteres de uma dada string. + +```tut +object StringIteratorTest { + def main(args: Array[String]) { + class Iter extends StringIterator(args(0)) with RichIterator + val iter = new Iter + iter foreach println + } +} +``` + +A classe `Iter` na função `main` é construída a partir de uma composição dos pais `StringIterator` e `RichIterator` com a palavra-chave `with`. O primeiro pai é chamado de _superclass_ de `Iter`, já o segundo pai (e qualquer outro que venha após) é chamado de _mixin_ ou mescla. \ No newline at end of file diff --git a/_pt-br/tour/named-parameters.md b/_pt-br/tour/named-parameters.md new file mode 100644 index 0000000000..a481e2babe --- /dev/null +++ b/_pt-br/tour/named-parameters.md @@ -0,0 +1,40 @@ +--- +layout: tour +title: Parâmetros Nomeados + +discourse: false + +partof: scala-tour + +num: 33 +previous-page: default-parameter-values +language: pt-br +--- + +Ao chamar métodos e funções, você pode utilizar explicitamente o nome das variáveis nas chamadas, por exemplo: + +```tut + def imprimeNome(nome:String, sobrenome:String) = { + println(nome + " " + sobrenome) + } + + imprimeNome("John","Smith") + // Imprime "John Smith" + imprimeNome(nome = "John",sobrenome = "Smith") + // Imprime "John Smith" + imprimeNome(sobrenome = "Smith",nome = "John") + // Imprime "John Smith" +``` + +Perceba que a ordem não importa quando você utiliza parâmetros nomeados nas chamadas de métodos e funções, desde que todos os parâmetros sejam declarados. Essa funcionalidade pode ser combinada com [parâmetros com valor padrão](default-parameter-values.html): + +```tut + def imprimeNome(nome:String = "John", sobrenome:String = "Smith") = { + println(nome + " " + sobrenome) + } + + imprimeNome(sobrenome = "Forbeck") + // Imprime "John Forbeck" +``` + +Dado que é permitido declarar os parâmetros em qualquer ordem, você pode utilizar o valor padrão para parâmetros que aparecem primeiro na lista de parâmetros da função. diff --git a/_pt-br/tour/nested-functions.md b/_pt-br/tour/nested-functions.md new file mode 100644 index 0000000000..6783c3795e --- /dev/null +++ b/_pt-br/tour/nested-functions.md @@ -0,0 +1,36 @@ +--- +layout: tour +title: Funções Aninhadas + +discourse: false + +partof: scala-tour + +num: 8 +next-page: currying +previous-page: higher-order-functions +language: pt-br +--- + +Em scala é possível aninhar definições de funções. O objeto a seguir fornece uma função `filter` para extrair valores de uma lista de inteiros que são abaixo de um determinado valor: + +```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)) +} +``` + +_Nota: a função aninhada `process` refere-se a variável `threshold` definida em um escopo externo como um parâmetro da função `filter`._ + +A saída gerada pelo programa é: + +``` +List(1,2,3,4) +``` diff --git a/_pt-br/tour/operators.md b/_pt-br/tour/operators.md new file mode 100644 index 0000000000..8e97ec3912 --- /dev/null +++ b/_pt-br/tour/operators.md @@ -0,0 +1,39 @@ +--- +layout: tour +title: Operadores + +discourse: false + +partof: scala-tour + +num: 29 +next-page: automatic-closures +previous-page: local-type-inference +language: pt-br +--- + +Qualquer método que tenha um único parâmetro pode ser usado como um *operador infix* em Scala. Aqui está a definição da classe `MyBool` que inclui os métodos `add` e `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) +} +``` + +Agora é possível utilizar as funções `and` and `or` como operadores infix: + +```tut +def not(x: MyBool) = x.negate +def xor(x: MyBool, y: MyBool) = (x or y) and not(x and y) +``` + +Isso ajuda a tornar a definição de `xor` mais legível. + +Aqui está o código correspondente em uma sintaxe de linguagem de programação orientada a objetos mais tradicional: + +```tut +def not(x: MyBool) = x.negate +def xor(x: MyBool, y: MyBool) = x.or(y).and(x.and(y).negate) +``` diff --git a/_pt-br/tour/pattern-matching.md b/_pt-br/tour/pattern-matching.md new file mode 100644 index 0000000000..9dcd9e4d3c --- /dev/null +++ b/_pt-br/tour/pattern-matching.md @@ -0,0 +1,50 @@ +--- +layout: tour +title: Correspondência de Padrões + +discourse: false + +partof: scala-tour + +num: 11 + +next-page: singleton-objects +previous-page: case-classes +language: pt-br +--- + +_Nota de tradução: A palavra cujo o significado melhor corresponde a palavra `match` em inglês seria `correspondência`. Também podemos entender que `match` é como "coincidir" ou "concordar" com algo._ + +Scala possui mecanismo de correspondência de padrão embutido. Isso permite realizar o match de qualquer tipo de dados com a política de primeiro match. +Aqui um pequeno exemplo que mostrar como realizar o match de um número inteiro: + +```tut +object MatchTest1 extends App { + def matchTest(x: Int): String = x match { + case 1 => "um" + case 2 => "dois" + case _ => "muitos" + } + println(matchTest(3)) +} +``` + +O bloco com a declaração `case` define a função que mapeia inteiros para strings. A palavra-chave `match` fornece uma maneira conveniente de aplicar uma função (como a função de correspondência de padrões acima) em um objeto. + +Aqui um segundo exemplo no qual o match é realizado em valores de diferentes tipos: + +```tut +object MatchTest2 extends App { + def matchTest(x: Any): Any = x match { + case 1 => "um" + case "dois" => 2 + case y: Int => "scala.Int" + } + println(matchTest("dois")) +} +``` + +O primeiro `case` realiza o match se `x` refere-se a um valor inteiro `1`. O segundo `case` realiza o match se `x` é igual a string `"dois"`. O terceiro `case` é padrão tipado; realiza o match de qualquer valor que seja um inteiro e associa o valor do match de `x` a uma variável `y` do tipo `Int`. + +A correspondência de padrões de Scala é mais útil para realizar os matches de tipos algébricos expressados com [classes case](case-classes.html). +Scala também permite a definição de padrões independentemente de classes case, basta utilizar o método `unapply` em um [objeto extrator](extractor-objects.html). diff --git a/_pt-br/tour/polymorphic-methods.md b/_pt-br/tour/polymorphic-methods.md new file mode 100644 index 0000000000..6d6c9150b7 --- /dev/null +++ b/_pt-br/tour/polymorphic-methods.md @@ -0,0 +1,32 @@ +--- +layout: tour +title: Métodos Polimórficos + +discourse: false + +partof: scala-tour + +num: 27 + +next-page: local-type-inference +previous-page: implicit-conversions +language: pt-br +--- + +Os métodos em Scala podem ser parametrizados com valores e tipos. Como no nível de classe, os parâmetros de valor são declarados entre parênteses, enquanto os parâmetros de tipo são declarados entre colchetes. + +Por exemplo: + +```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)) // primeira chamada +println(dup("three", 3)) // segunda chamada +``` + +O método `dup` é parametrizado com o tipo `T` e com os parâmetros de valor `x: T` e `n: Int`. Na primeira chamada de `dup`, o programador fornece os parâmetros necessários, mas como mostra a seguinte linha, o programador não é obrigado a fornecer explicitamente os parâmetros de tipos. O sistema de tipos de Scala pode inferir tais tipos sem problemas. Isso é feito observando-se os tipos dos parâmetros de valor fornecidos ao método e qual o contexto que o mesmo é chamado. diff --git a/_pt-br/tour/regular-expression-patterns.md b/_pt-br/tour/regular-expression-patterns.md new file mode 100644 index 0000000000..2fd77422e6 --- /dev/null +++ b/_pt-br/tour/regular-expression-patterns.md @@ -0,0 +1,48 @@ +--- +layout: tour +title: Padrões de Expressões Regulares + +discourse: false + +partof: scala-tour + +num: 14 + +next-page: extractor-objects +previous-page: singleton-objects +language: pt-br +--- + +## Padrões de sequência que ignoram a direita ## + +Padrões de sequência que ignoram a direita são uma funcionalidade útil para decompor qualquer dado sendo ele um subtipo de `Seq[A]` ou uma classe case com um parâmetro iterador formal, como por exemplo: + +``` +Elem(prefix:String, label:String, attrs:MetaData, scp:NamespaceBinding, children:Node*) +``` +Em tais casos, Scala permite que padrões tenham o curinga `_*` na posição mais à direita para ter lugar para sequências arbitrariamente longas. +O exemplo a seguir demonstra um padrão que faz o match de um prefixo de uma sequência e vincula o resto à variável `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 + } + } +} +``` + +Em contraste com versões anteriores Scala, não é mais permitido ter expressões regulares arbitrárias, pelas razões descritas abaixo. + +###Padrões genéricos de expressões regulares `RegExp` temporariamente retirados de Scala### + +Desde que descobrimos um problema de precisão, esse recurso está temporariamente removido da linguagem Scala. Se houver solicitação da comunidade de usuários, poderemos reativá-la de forma aprimorada. + +De acordo com nossa opinião, os padrões de expressões regulares não eram tão úteis para o processamento XML como estimamos. Em aplicações de processamento de XML de vida real, XPath parece uma opção muito melhor. Quando descobrimos que nossos padrões de expressões regulares ou de tradução tinham alguns bugs para padrões raros que são incomuns e difíceis de excluir, escolhemos que seria hora de simplificar a linguagem. + diff --git a/_pt-br/tour/sequence-comprehensions.md b/_pt-br/tour/sequence-comprehensions.md new file mode 100644 index 0000000000..6bd993b926 --- /dev/null +++ b/_pt-br/tour/sequence-comprehensions.md @@ -0,0 +1,70 @@ +--- +layout: tour +title: Sequence Comprehensions + +discourse: false + +partof: scala-tour + +num: 16 +next-page: generic-classes +previous-page: extractor-objects +language: pt-br +--- + +Scala oferece uma notação simples para expressar *compreensões de sequência*. As compreensões têm a forma `for (enumerators) yield e`, onde` enumerators` se refere a uma lista de enumeradores separados por ponto-e-vírgula. Um *enumerator* é um gerador que introduz novas variáveis ou é um filtro. A compreensão avalia o corpo `e` para cada associação gerada pelos enumeradores e retorna uma sequência desses valores. + +Por exemplo: + +```tut +object ComprehensionTest1 extends App { + def par(de: Int, ate: Int): List[Int] = + for (i <- List.range(de, ate) if i % 2 == 0) yield i + Console.println(par(0, 20)) +} +``` + +A função for-expression introduz uma nova variável `i` do tipo `Int`, que é subsequentemente associada a todos os valores da lista `List (de, de + 1, ..., ate - 1)`. A restrição `if i% 2 == 0` ignora todos os números ímpares para que o corpo (que só consiste na expressão i) seja avaliado somente para números pares. Consequentemente, toda a expressão `for` retorna uma lista de números pares. + +O programa produz a seguinte saída: + +``` +List(0, 2, 4, 6, 8, 10, 12, 14, 16, 18) +``` + +Agora um exemplo mais complicado que calcula todos os pares de números entre `0` e` n-1` cuja soma é igual a um dado valor `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)") + } +} +``` + +Este exemplo mostra que as compreensões não estão restritas às listas. Pois o programa anterior usa iteradores. Todo tipo de dados que suporta as operações `withFilter`, `map`, e `flatMap` (com os tipos apropriados) pode ser usado em compreensões de sequência. + +Aqui está a saída do programa: + +``` +(13, 19) +(14, 18) +(15, 17) +(16, 16) +``` + +Há também uma forma especial de compreensão de sequência que retorna `Unit`. Aqui as associações que são criadas a partir da lista de geradores e filtros são utilizadas para gerar efeitos colaterais. O programador precisa omitir a palavra-chave `yield` para fazer uso de tal compreensão de sequência. +Aqui está um programa que é equivalente ao anterior, mas usa uma forma especial de for-expression que retorna `Unit`: + +``` +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/_pt-br/tour/singleton-objects.md b/_pt-br/tour/singleton-objects.md new file mode 100644 index 0000000000..808285b761 --- /dev/null +++ b/_pt-br/tour/singleton-objects.md @@ -0,0 +1,74 @@ +--- +layout: tour +title: Objetos Singleton + +discourse: false + +partof: scala-tour + +num: 12 + +next-page: regular-expression-patterns +previous-page: pattern-matching +language: pt-br +--- + +Métodos e valores que não são associados com instâncias individuais de uma [classe](classes.html) são considerados *objetos singleton*, denotados através da palavra-chave `object` ao invés de `class`. + +``` +package test + +object Blah { + def sum(l: List[Int]): Int = l.sum +} +``` + +O método `sum` é disponível globalmente, e pode ser referenciado ou importado como `test.Blah.sum`. + +Objetos Singleton são um tipo de mescla e abreviação para definir uma classe de uso único, a qual não pode ser diretamente instanciada, e um membro `val` durante a definição do `object`. De fato, como `val`, objetos singleton podem ser definidos como membros de uma [trait](traits.html) ou [classe](classes.html), porém isso não é comum. + +Um objeto singleton pode estender classes e traits. Já uma [classe clase](case-classes.html) sem [parâmetros com tipo](generic-classes.html) por padrão irá criar um objeto singleton como o mesmo nome e com uma [`Função*`](http://www.scala-lang.org/api/current/scala/Function1.html) trait implementada. + +## Acompanhantes ## + +A maioria dos objetos singleton não estão sozinhos, mas sim associados com uma classe de mesmo nome. O “objeto singleton de mesmo nome” que uma classe case, acima mencionado, é um exemplo disso. Quando isso acontece, o objeto singleton é chamado de *objeto acompanhante* de uma classe e, a classe é chamada de *classe acompanhante* de um objeto. + +[Scaladoc](https://wiki.scala-lang.org/display/SW/Introduction) possui um recurso especial para navegar entre classes e seus acompanhantes: se o grande círculo contendo “C” ou “O” possui sua extremidade superior dobrada para baixo, você pode clicar no círculo para acessar o acompanhante. + +Se houver um objeto acompanhante para uma classe, ambos devem ser definidos no mesmo aquivo fonte. Por exemplo: + +```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)) +} +``` + +É comum ver instâncias de classes de tipo como [valores implícitos](implicit-parameters.html), como `ipord` acima, definido no objeto acompanhante quando se segue o padrão da *typeclass*. Isso ocorre porque os membros do objeto acompanhante são incluídos por padrão na busca de valores implícitos. + +## Nota para programadores Java ## + +`static` não é uma palavra-chave em Scala. Ao invés disso, todos os membros que devem ser estáticos, incluindo classes, devem ser declarados no objeto singleton. Eles podem ser referenciados com a mesma sintaxe, importados gradativamente ou como um grupo, e assim por diante. + +Frequentemente, os programadores Java definem membros estáticos, talvez `private`, como auxiliares de implementação para seus membros de instância. Estes são movidos para o acompanhante também; Um padrão comum é importar os membros do objeto acompanhante na classe, da seguinte forma: +``` +class X { + import X._ + + def blah = foo +} + +object X { + private def foo = 42 +} +``` + +Isso demonstra outra característica: no contexto `private`, as classes e seus acompanhantes são amigos. `object X` pode acessar membro privados da `class X`, e vice versa. Para fazer com que um membro seja *realmente* para um ou outro, utilize `private[this]`. + +Por uma melhor interoperabilidade com Java, métodos, incluindo `var`s e `val`s, definidos diretamente em um objeto singleton também têm um método estático definido na classe acompanhante, chamado *encaminhadores estáticos*. Outros membros são acessíveis por meio de campos estáticos `X$.MODULE$` para o `object X`. + +Se você mover tudo para um objeto acompanhante e descobrir que tudo o que resta é uma classe que você não deseja que seja instanciada, simplesmente exclua a classe. Encaminhadores estáticos ainda serão criados. \ No newline at end of file diff --git a/_pt-br/tour/tour-of-scala.md b/_pt-br/tour/tour-of-scala.md new file mode 100644 index 0000000000..8e0bdb9d14 --- /dev/null +++ b/_pt-br/tour/tour-of-scala.md @@ -0,0 +1,50 @@ +--- +layout: tour +title: Introdução + +discourse: false + +partof: scala-tour + +num: 1 + +next-page: unified-types +language: pt-br +--- + +Scala é uma linguagem de programação moderna e multi-paradigma desenvolvida para expressar padrões de programação comuns em uma forma concisa, elegante e com tipagem segura. Integra facilmente características de linguagens orientadas a objetos e funcional. + +## Scala é orientada a objetos ## +Scala é uma linguagem puramente orientada a objetos no sentido que [todo valor é um objeto](unified-types.html). Tipos e comportamentos de objetos são descritos por [classes](classes.html) e [traits](traits.html). Classes são estendidas por subclasses e por um flexível mecanismo [de composição mesclada](mixin-class-composition.html) como uma alternativa para herança múltipla. + +## Scala é funcional ## +Scala é também uma linguagem funcional no sentido que [toda função é um valor](unified-types.html). Scala fornece uma [sintaxe leve](anonymous-function-syntax.html) para definir funções anônimas, suporta [funções de primeira ordem](higher-order-functions.html), permite funções [aninhadas](nested-functions.html), e suporta [currying](currying.html). As [case classes](case-classes.html) da linguagem Scala e o suporte embutido para [correspondência de padrões](pattern-matching.html) modelam tipos algébricos utilizados em muitas linguagens de programação funcional. [Objetos Singleton](singleton-objects.html) fornecem uma alternativa conveniente para agrupar funções que não são membros de uma classe. + +Além disso, a noção de correspondência de padrões em Scala se estende naturalmente ao [processamento de dados de um XML](xml-processing.html) com a ajuda de [expressões regulares](regular-expression-patterns.html), por meio de uma extensão via [objetos extratores](extractor-objects.html). Nesse contexto, [compreensões de sequência](sequence-comprehensions.html) são úteis para formular consultas. Essas funcionalidades tornam Scala ideal para desenvolver aplicações como serviços web. + +## Scala é estaticamente tipada ## +Scala é equipada com um expressivo sistema de tipos que reforça estaticamente que abstrações são utilizadas de uma forma segura e coerente. Particularmente, o sistema de tipos suporta: + +* [Classes genéricas](generic-classes.html) +* [Anotações variáveis](variances.html) +* [Limites de tipos superiores](upper-type-bounds.html) e [limites de tipos inferiores](lower-type-bounds.html), +* [Classes internas](inner-classes.html) e [tipos abstratos](abstract-types.html) como membros de um objeto +* [Tipos compostos](compound-types.html) +* [Auto referências explicitamente tipadas](explicitly-typed-self-references.html) +* [parâmetros implícitos](implicit-parameters.html) e [conversões implícitas](implicit-conversions.html) +* [métodos polimórficos](polymorphic-methods.html) + +Um [mecanismo de inferência de tipo local](local-type-inference.html) se encarrega para que o usuário não seja obrigado a anotar o programa com informações reduntante de tipos. Combinados, esses recursos fornecem uma base poderosa para a reutilização segura de abstrações de programação e para a extensão de tipos seguro do software. + +## Scala é extensível ## + +Na prática, o desenvolvimento de aplicações de um determinado domínio geralmente requer uma linguagem de domínio específico. Scala fornece uma combinação única de mecanismos de linguagem que facilitam a adição suave de novas construções de linguagem na forma de bibliotecas: + +* qualquer método pode ser utilizado como um [operador infix ou postfix](operators.html) +* [closures são construídas automaticamente dependendo do tipo esperado](automatic-closures.html) (tipo alvo). + +Uma utilização conjunta de ambos os recursos facilita a definição de novas instruções sem estender a sintaxe e sem usar meta-programação como macros. + +Scala é projetada para interoperar bem com o popular Java 2 Runtime Environment (JRE). Em particular, a interação com a linguagem de programação orientada a objetos Java é o mais suave possível. Funcionalidades novas do Java como [annotations](annotations.html) e Java generics têm correspondentes diretos em Scala. Esses recursos Scala sem correspondentes Java, como [valor default de parâmetros](default-parameter-values.html) e [parâmetros nomeados](named-parameters.html), compilam de forma semelhante ao Java. Scala tem o mesmo modelo de compilação que Java (compilação separada, carregamento de classe dinâmica) e permite o acesso a milhares de bibliotecas de alta qualidade existentes. + +Continue na próxima página para ler mais. diff --git a/_pt-br/tour/traits.md b/_pt-br/tour/traits.md new file mode 100644 index 0000000000..a7a750961f --- /dev/null +++ b/_pt-br/tour/traits.md @@ -0,0 +1,55 @@ +--- +layout: tour +title: Traits + +discourse: false + +partof: scala-tour + +num: 4 +next-page: mixin-class-composition +previous-page: classes +language: pt-br +--- + +Similar a interfaces em Java, traits são utilizadas para definir tipos de objetos apenas especificando as assinaturas dos métodos suportados. Como em Java 8, Scala permite que traits sejam parcialmente implementadas; ex. é possível definir uma implementação padrão para alguns métodos. Diferentemente de classes, traits não precisam ter construtores com parâmetros. +Veja o exemplo a seguir: + +```tut +trait Similaridade { + def eSemelhante(x: Any): Boolean + def naoESemelhante(x: Any): Boolean = !eSemelhante(x) +} +``` + +Tal trait consiste em dois métodos `eSemelhante` e `naoESemelhante`. Equanto `eSemelhante` não fornece um método com implementação concreta (que é semelhante ao abstract na linguagem Java), o método `naoESemelhante` define um implementação concreta. Consequentemente, classes que integram essa trait só precisam fornecer uma implementação concreta para o método `eSemelhante`. O comportamento para `naoESemelhante` é herdado diretamente da trait. Traits são tipicamente integradas a uma [classe](classes.html) (ou outras traits) utilizando a [composição mesclada de classes](mixin-class-composition.html): + +```tut +class Point(xc: Int, yc: Int) extends Similaridade { + var x: Int = xc + var y: Int = yc + def eSemelhante(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.eSemelhante(p2)) + println(p1.eSemelhante(p3)) + // Ponto.naoESemelhante foi definido na classe Similaridade + println(p1.naoESemelhante(2)) + println(p1.naoESemelhante(p4)) +} +``` + +Aqui a saída do programa: + +``` +true +false +true +false +``` diff --git a/_pt-br/tour/unified-types.md b/_pt-br/tour/unified-types.md new file mode 100644 index 0000000000..d0e240261b --- /dev/null +++ b/_pt-br/tour/unified-types.md @@ -0,0 +1,50 @@ +--- +layout: tour +title: Tipos Unificados + +discourse: false + +partof: scala-tour + +num: 2 +next-page: classes +previous-page: tour-of-scala +language: pt-br +--- + +Diferente de Java, todos os valores em Scala são objetos (incluindo valores numéricos e funções). Dado que Scala é baseada em classes, todos os valores são instâncias de uma classe. O diagrama a seguir ilustra a hierarquia de classes. + +![Hierarquia de Tipos Scala]({{ site.baseurl }}/resources/images/classhierarchy.img_assist_custom.png) + +## Hierarquia de Tipos Scala ## + +A superclass de todas as classes `scala.Any` tem duas subclasses diretas `scala.AnyVal` e `scala.AnyRef` representando dois mundos de classes distintos: classes de valor e classes de referência. Todas as classes de valor são predefinidas; elas correspondem aos tipos primitivos em linguagens semelhante a Java. Todas as outras classes definem tipos de referência. Classes definidas pelo usuário definem tipos de referência por padrão; por exemplo, tais classes sempre (indiretamente) são subclasses de `scala.AnyRef`. Toda classes definida pelo usuário em Scala implicitamente estende a trait `scala.ScalaObject`. Classes de infraestrutura nas quais Scala está sendo executado (ex. ambiente de execução do Java) não estendem `scala.ScalaObject`. Se utilizar Scala no contexto do ambiente de execução do Java, então `scala.AnyRef` corresponde à `java.lang.Object`. +Observe que o diagrama acima mostra implicitamente as conversões entre as classes de valores. +Este exemplo demonstra que números numbers, caracteres, valores booleanos, e funções são objetos como qualquer outro objeto: + +```tut +object TiposUnificados extends App { + val set = new scala.collection.mutable.LinkedHashSet[Any] + set += "Sou uma string" // adiciona uma string ao set + set += 732 // adiciona um número + set += 'c' // adiciona um caractere + set += true // adiciona um valor booleano + set += main _ // adiciona a função main + val iter: Iterator[Any] = set.iterator + while (iter.hasNext) { + println(iter.next.toString()) + } +} +``` + +O programa declara uma aplicação chamada `TiposUnificados` em forma de um [objeto Singleton](singleton-objects.html) que estende `App`. A aplicação define uma variável local `set` que se refere a uma instância da classe `LinkedHashSet[Any]`. As demais linhas adicionam vários elementos à variável set. Tais elementos devem estar em conformidade com o tipo `Any` que foi declarado para o set. Por fim, são escritas as representações em string de todos os elementos adicionados ao set. + + +Escrita de saída do programa: +``` +Sou uma string +732 +c +true + +``` diff --git a/_pt-br/tour/upper-type-bounds.md b/_pt-br/tour/upper-type-bounds.md new file mode 100644 index 0000000000..478b729886 --- /dev/null +++ b/_pt-br/tour/upper-type-bounds.md @@ -0,0 +1,51 @@ +--- +layout: tour +title: Limitante Superior de Tipos + +discourse: false + +partof: scala-tour + +num: 19 +next-page: lower-type-bounds +previous-page: variances +language: pt-br +--- + +Em Scala, [parâmetros de tipos](generic-classes.html) e [tipos abstratos](abstract-types.html) podem ser restringidos por um limitante de tipo. Tal limitante de tipo limita os valores concretos de uma variável de tipo e possivelmente revela mais informações sobre os membros de determinados tipos. Um _limitante superiror de tipos_ `T <: A` declare que a variável tipo `T` refere-se a um subtipo do tipo `A`. +Aqui um exemplo que demonstra um limitante superior de tipo para um parâmetro de tipo da classe `Cage`: + +```tut +abstract class Animal { + def name: String +} + +abstract class Pet extends Animal {} + +class Gato extends Pet { + override def name: String = "Gato" +} + +class Cachorro extends Pet { + override def name: String = "Cachorro" +} + +class Leao extends Animal { + override def name: String = "Leao" +} + +class Jaula[P <: Pet](p: P) { + def pet: P = p +} + +object Main extends App { + var jaulaCachorro = new Jaula[Cachorro](new Cachorro) + var jaulaGato = new Jaula[Gato](new Gato) + /* Não é possível colocar Leao em Jaula pois Leao não estende Pet. */ +// var jaulaLeao = new Jaula[Leao](new Leao) +} +``` + +Um instância da classe `Jaula` pode conter um animal, porém com um limite superior do tipo `Pet`. Um animal to tipo `Leao` não é um pet, pois não estende `Pet`, então não pode ser colocado em uma Jaula. + +O uso de limitantes inferiores de tipo é discutido [aqui](lower-type-bounds.html). diff --git a/_pt-br/tour/variances.md b/_pt-br/tour/variances.md new file mode 100644 index 0000000000..7f10c95004 --- /dev/null +++ b/_pt-br/tour/variances.md @@ -0,0 +1,42 @@ +--- +layout: tour +title: Variâncias + +discourse: false + +partof: scala-tour + +num: 18 +next-page: upper-type-bounds +previous-page: generic-classes +language: pt-br +--- + +Scala suporta anotações de variância de parâmetros de tipo de [classes genéricas](generic-classes.html). Em contraste com o Java 5, as anotações de variância podem ser adicionadas quando uma abstração de classe é definida, enquanto que em Java 5, as anotações de variância são fornecidas por clientes quando uma abstração de classe é usada. + +Na página sobre [classes genéricas](generic-classes.html) foi dado um exemplo de uma pilha de estado mutável. Explicamos que o tipo definido pela classe `Stack [T]` está sujeito a subtipos invariantes em relação ao parâmetro de tipo. Isso pode restringir a reutilização da abstração de classe. Derivamos agora uma implementação funcional (isto é, imutável) para pilhas que não tem esta restrição. Por favor, note que este é um exemplo avançado que combina o uso de [métodos polimórficos](polymorphic-methods.html), [limites de tipo inferiores](lower-type-bounds.html) e anotações de parâmetros de tipos covariantes em um estilo não trivial. Além disso, fazemos uso de [classes internas](inner-classes.html) para encadear os elementos da pilha sem links explícitos. + +```tut +class Stack[+T] { + def push[S >: T](elem: S): Stack[S] = new Stack[S] { + override def top: S = elem + override def pop: Stack[S] = Stack.this + override def toString: String = + elem.toString + " " + Stack.this.toString + } + def top: T = sys.error("no element on stack") + def pop: Stack[T] = sys.error("no element on stack") + override def toString: String = "" +} + +object VariancesTest extends App { + var s: Stack[Any] = new Stack().push("hello") + s = s.push(new Object()) + s = s.push(7) + println(s) +} +``` + +A anotação `+T` declara o tipo `T` para ser usado somente em posições covariantes. Da mesma forma, `-T` declara que `T` pode ser usado somente em posições contravariantes. Para os parâmetros de tipo covariante obtemos uma relação de sub-tipo covariante em relação ao parâmetro de tipo. Em nosso exemplo, isso significa que `Stack [T]` é um subtipo de `Stack [S]` se `T` for um subtipo de `S`. O oposto é válido para parâmetros de tipo que são marcados com um `-`. + +No exemplo da pilha teríamos que usar o parâmetro de tipo covariante `T` em uma posição contravariante para podermos definir o método `push`. Uma vez que queremos sub-tipagem covariante para pilhas, usamos um truque e abstraímos o tipo de parâmetro do método `push`. Então temos um método polimórfico no qual usamos o tipo de elemento `T` como um limite inferior da variável de tipo da função `push`. Isso faz com que a variância de `T` fique em acordo com sua declaração, como um parâmetro de tipo covariante. Agora as pilhas são covariantes, mas a nossa solução permite que, por exemplo, seja possível inserir uma string em uma pilha de inteiros. O resultado será uma pilha do tipo `Stack [Any]`; Assim detectamos o error somente se o resultado for usado em um contexto onde esperamos uma pilha de números inteiros. Caso contrário, nós apenas criamos uma pilha com um tipo de elemento mais abrangente. diff --git a/_ru/overviews/parallel-collections/architecture.md b/_ru/overviews/parallel-collections/architecture.md new file mode 100644 index 0000000000..d7652bb896 --- /dev/null +++ b/_ru/overviews/parallel-collections/architecture.md @@ -0,0 +1,70 @@ +--- +layout: multipage-overview +title: Архитектура библиотеки параллельных коллекций + +discourse: false + +partof: parallel-collections +overview-name: Parallel Collections + +language: ru +num: 5 +--- + +Как и обычная библиотека коллекций Scala, библиотека параллельных коллекций содержит большое количество операций, для которых, в свою очередь, существует множество различных реализаций. И так же, как последовательная, параллельная библиотека избегает повторений кода путем реализации большинства операций посредством собственных "шаблонов", которые достаточно объявить один раз, а потом наследовать в различных реализациях параллельных коллекций. + +Преимущества этого подхода сильно облегчают **поддержку** и **расширяемость**. Поддержка станет простой и надежной, когда одна реализация операции над параллельной коллекцией унаследуется всеми параллельными коллекциями; исправления ошибок в этом случае сами распространятся вниз по иерархии классов, а не потребуют дублировать реализации. По тем же причинам всю библиотеку проще расширять-- новые классы коллекций наследуют большинство имеющихся операций. + + +## Ключевые абстракции + +Упомянутые выше "шаблонные" трейты реализуют большинство параллельных операций в терминах двух ключевых абстракций -- разделителей (`Splitter`) и компоновщиков (`Combiner`). + +### Разделители + +Задача разделителя `Splitter`, как и предполагает имя, заключается в том, чтобы разбить параллельную коллекцию на непустые разделы. А основная идея-- в том, чтобы разбивать коллекцию на более мелкие части, пока их размер не станет подходящим для последовательной обработки. + + trait Splitter[T] extends Iterator[T] { + def split: Seq[Splitter[T]] + } + +Что интересно, разделители `Splitter` реализованы через итераторы-- `Iterator`, а это подразумевает, что помимо разделения, они позволяют перебирать элементы параллельной коллекции (то есть, наследуют стандартные методы трейта `Iterator`, такие, как `next` и `hasNext`.) Уникальность этого "разделяющего итератора" в том, что его метод `split` разбивает текущий объект `this` (мы помним, что `Splitter`, это подтип `Iterator`а) на другие разделители `Splitter`, каждый из которых перебирает свой, **отделенный** набор элементов когда-то целой параллельной коллекции. И так же, как любой нормальный `Iterator`, `Splitter` становится недействительным после того, как вызван его метод `split`. + +Как правило, коллекции разделяются `Splitter`ами на части примерно одинакового размера. В случаях, когда требуются разделы произвольного размера, особенно в параллельных последовательностях, используется `PreciseSplitter`, который является наследником `Splitter` и дополнительно реализует точный метод разделения, `psplit`. + +### Компоновщики + +Компоновщик `Combiner` можно представить себе как обобщенный `Builder` из библиотеки последовательных коллекций Scala. У каждой параллельной коллекции есть свой отдельный `Combiner`, так же, как у каждой последовательной есть свой `Builder`. + +Если в случае с последовательными коллекциями элементы можно добавлять в `Builder`, а потом получить коллекцию, вызвав метод `result`, то при работе с параллельными требуется вызвать у компоновщика метод `combine`, который берет аргументом другой компоновщик и делает новый `Combiner`. Результатом будет компоновщик, содержащий объединенный набор. После вызова метода `combine` оба компоновщика становятся недействительными. + + trait Combiner[Elem, To] extends Builder[Elem, To] { + def combine(other: Combiner[Elem, To]): Combiner[Elem, To] + } + +Два параметра-типа в примере выше, `Elem` и `To`, просто обозначают тип элемента и тип результирующей коллекции соответственно. + +_Примечание:_ Если есть два `Combiner`а, `c1` и `c2` где `c1 eq c2` равняется `true` (то есть, они являются одним и тем же `Combiner`ом), вызов `c1.combine(c2)` всегда ничего не делает, а просто возвращает исходный `Combiner`, то есть `c1`. + + +## Иерархия + +Параллельные коллекции Scala во многом созданы под влиянием дизайна библиотеки (последовательных) коллекций Scala. На рисунке ниже показано, что их дизайн фактически отражает соответствующие трейты фреймворка обычных коллекций. + +[]({{ site.baseurl }}/resources/images/parallel-collections-hierarchy.png) + +
      Иерархия библиотеки Scala: коллекции и параллельные коллекции
      +
      + +Цель, конечно же, в том, чтобы интегрировать параллельные коллекции с последовательными настолько тесно, насколько это возможно, так, чтобы можно было без дополнительных усилий заменять последовательные коллекции параллельными (и наоборот). + +Чтобы можно было получить ссылку на коллекцию, которая может быть либо последовательной, либо параллельной (так, чтобы было возможно "переключаться" между параллельной и последовательной коллекции вызовами `par` и `seq` соответственно), у обоих типов коллекций должен быть общий предок. Этим источником "обобщенных" трейтов, как показано выше, являются `GenTraversable`, `GenIterable`, `GenSeq`, `GenMap` и `GenSet`, которые не гарантируют того, что элементы будут обрабатываться по-порядку или по-одному. Отсюда наследуются соответствующие последовательные и параллельные трейты; например, `ParSeq` и `Seq` являются подтипами общей последовательности `GenSeq`, а не унаследованы друг от друга. + +Более подробное обсуждение иерархии, разделяемой последовательными и параллельными коллекциями, можно найти в техническом отчете. \[[1][1]\] + + +## Ссылки + +1. [On a Generic Parallel Collection Framework, Aleksandar Prokopec, Phil Bawgell, Tiark Rompf, Martin Odersky, June 2011][1] + +[1]: http://infoscience.epfl.ch/record/165523/files/techrep.pdf "flawed-benchmark" diff --git a/_ru/overviews/parallel-collections/concrete-parallel-collections.md b/_ru/overviews/parallel-collections/concrete-parallel-collections.md new file mode 100644 index 0000000000..81c7807c89 --- /dev/null +++ b/_ru/overviews/parallel-collections/concrete-parallel-collections.md @@ -0,0 +1,166 @@ +--- +layout: multipage-overview +title: Конкретные классы параллельных коллекций + +discourse: false + +partof: parallel-collections +overview-name: Parallel Collections + +language: ru +num: 2 +--- + +## Параллельный Массив + +Последовательность [ParArray](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/parallel/mutable/ParArray.html) хранит линейный массив смежно хранимых элементов. Это означает, что получение доступа и обновление элементов эффективно, так как происходит путем изменения массива, лежащего в основе. По этой причине наиболее эффективна последовательная обработка элементов одного за другим. Параллельные массивы похожи на обычные в том отношении, что их размер постоянен. + + scala> val pa = scala.collection.parallel.mutable.ParArray.tabulate(1000)(x => 2 * x + 1) + pa: scala.collection.parallel.mutable.ParArray[Int] = ParArray(1, 3, 5, 7, 9, 11, 13,... + + scala> pa reduce (_ + _) + res0: Int = 1000000 + + scala> pa map (x => (x - 1) / 2) + res1: scala.collection.parallel.mutable.ParArray[Int] = ParArray(0, 1, 2, 3, 4, 5, 6, 7,... + +Реализация разбивки параллельного массива [разделителями]({{ site.baseurl }}/ru/overviews/parallel-collections/architecture.html#core_abstractions) сводится к созданию двух новых разделителей с последующим обновлением их итерационных индексов. [Компоновщики]({{ site.baseurl }}/ru/overviews/parallel-collections/architecture.html#core_abstractions) играют более заметную роль. Так как мы не знаем заранее количество элементов (и следовательно, размер массива) при выполнении большинства методов трансформации (например, `flatMap`, `filter`, `takeWhile`, и т.д.), каждый компоновщик, в сущности, является массивом-буфером, у которого операция `+=` требует для выполнения амортизированное постоянное время. Разные процессоры добавляют элементы к отдельным компоновщикам параллельного массива, которые потом по цепочке объединяют свои внутренние массивы. Лежащий в основе массив размещается и параллельно заполняется только после того, как становится известным общее число элементов. По этой причине методы трансформации требуют больше ресурсов, чем методы получения доступа. Также стоит заметить, что финальное размещение массива выполняется JVM последовательно, поэтому этот момент может стать узким местом, если даже сама операция отображения весьма нересурсоемкая. + +Вызов метода `seq` приводит к преобразованию параллельного массива в коллекцию `ArraySeq`, которая является его последовательным аналогом. Такое преобразование эффективно, и в основе `ArraySeq` остается тот же массив, что и был у исходного параллельного. + +## Параллельный вектор + +[ParVector](http://www.scala-lang.org/api/{{ site.scala-version}}/scala/collection/parallel/immutable/ParVector.html) является неизменяемой последовательностью, временная сложность доступа и обновления которой является логарифмической с низкой константой-множителем. + + scala> val pv = scala.collection.parallel.immutable.ParVector.tabulate(1000)(x => x) + pv: scala.collection.parallel.immutable.ParVector[Int] = ParVector(0, 1, 2, 3, 4, 5, 6, 7, 8, 9,... + + scala> pv filter (_ % 2 == 0) + res0: scala.collection.parallel.immutable.ParVector[Int] = ParVector(0, 2, 4, 6, 8, 10, 12, 14, 16, 18,... + +Неизменяемые векторы представлены 32-ичными деревьями (32-way trees), поэтому [разделители]({{ site.baseurl }}/ru/overviews/parallel-collections/architecture.html#core_abstractions) разбивают их, назначая по поддереву каждому новому разделителю. +[Компоновщики]({{ site.baseurl }}/ru/overviews/parallel-collections/architecture.html#core_abstractions) в настоящий момент хранят вектор из элементов и компонуют путем отложенного копирования. По этой причине методы трансформации менее масштабируемы по сравнению с теми же методами параллельного массива. Как только в будущем релизе Scala станет доступной операция конкатенации векторов, компоновщики станут образовываться путем конкатенации, и от этого методы трансформации станут гораздо более эффективными. + +Параллельный вектор является параллельным аналогом последовательной коллекции [Vector](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Vector.html), и преобразования одного в другое занимают постоянное время. + +## Параллельный диапазон + +[ParRange](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/parallel/immutable/ParRange.html) представляет собой упорядоченную последовательность элементов, отстоящих друг от друга на одинаковые промежутки. Параллельный диапазон создается подобно последовательному [Range](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Range.html): + + scala> 1 to 3 par + res0: scala.collection.parallel.immutable.ParRange = ParRange(1, 2, 3) + + scala> 15 to 5 by -2 par + res1: scala.collection.parallel.immutable.ParRange = ParRange(15, 13, 11, 9, 7, 5) + +Подобно тому, как последовательные диапазоны не имеют строителей, параллельные диапазоны не имеют [компоновщиков]({{ site.baseurl }}/ru/overviews/parallel-collections/architecture.html#core_abstractions). При создании отображения (mapping) элементов параллельного диапазона получается параллельный вектор. Последовательные и параллельные диапазоны могут эффективно преобразовываться друг в друга вызовами методов `seq` и `par`. + +## Параллельные хэш-таблицы + +В основе параллельной хэш-таблицы лежит массив, причем место элемента таблицы в этом массиве определяется хэш-кодом элемента. На хэш-таблицах основаны параллельные изменяемые хэш-множества ([mutable.ParHashSet](http://www.scala-lang.org/api/{{ site.scala-version}}/scala/collection/parallel/mutable/ParHashSet.html)) и параллельные изменяемые ассоциативные хэш-массивы (хэш-отображения) ([mutable.ParHashMap](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/parallel/mutable/ParHashMap.html)). + + scala> val phs = scala.collection.parallel.mutable.ParHashSet(1 until 2000: _*) + phs: scala.collection.parallel.mutable.ParHashSet[Int] = ParHashSet(18, 327, 736, 1045, 773, 1082,... + + scala> phs map (x => x * x) + res0: scala.collection.parallel.mutable.ParHashSet[Int] = ParHashSet(2181529, 2446096, 99225, 2585664,... + +Компоновщики параллельных хэш-таблиц распределяют элементы по блокам в соответствии с префиксом их хэш-кода. Компонуют же они простой конкатенацией таких блоков. Когда хэш-таблица окажется окончательно сформированной (то есть, когда будет вызван метод `result` компоновщика), размещается лежащий в основе массив и элементы из различных блоков параллельно копируются в различные смежнолежащие сегменты этого массива. + +Последовательные хэш-отображения и хэш-множества могут преобразовываться в свои параллельные аналоги с помощью метода `par`. Внутри параллельной хэш-таблицы требуется поддерживать карту размеров, которая отслеживает количество элементов в различных ее частях. Это значит, что при первом преобразовании последовательной хэш-таблицы в параллельную, вся она просматривается с целью создания карты размеров - по этой причине первый вызов метода `par` требует линейного по отношению к числу элементов времени выполнения. При дальнейших изменениях хэш-таблицы ее карта размеров поддерживается в актуальном состоянии, поэтому последующие преобразования вызовами `par` и `seq` имеют постоянную сложность. Впрочем, поддержку карты размеров можно и отключить, используя метод `useSizeMap` хэш-таблицы. Важный момент: изменения, сделанные в последовательной хэш-таблице, видны в параллельной, и наоборот. + +## Параллельные префиксные хэш-деревья (Hash Tries) + +Параллельные префиксные хэш-деревья являются параллельным аналогом неизменяемых префиксных хэш-деревьев, которые используются для эффективного представления неизменяемых множеств и ассоциативных массивов. Последние представлены классами [immutable.ParHashSet](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/parallel/immutable/ParHashSet.html) и [immutable.ParHashMap](http://www.scala-lang.org/api/{{ site.scala-version}}/scala/collection/parallel/immutable/ParHashMap.html). + + scala> val phs = scala.collection.parallel.immutable.ParHashSet(1 until 1000: _*) + phs: scala.collection.parallel.immutable.ParHashSet[Int] = ParSet(645, 892, 69, 809, 629, 365, 138, 760, 101, 479,... + + scala> phs map { x => x * x } sum + res0: Int = 332833500 + +[Компоновщики]({{ site.baseurl }}/overviews/parallel-collections/architecture.html#core_abstractions) параллельных хэш-деревьев действуют аналогично компоновщикам хэш-таблиц, а именно предварительно распределяют элементы по блокам, а после этого параллельно составляют результирующее хэш-дерево, назначая обработку различных блоков разным процессорам, каждый из которых независимо собирает свое поддерево. + +Параллельные хэш-деревья могут за постоянное время преобразовываться вызовами методов `seq` и `par` в последовательные хэш-деревья и обратно. + +## Параллельные многопоточные префиксные деревья (Concurrent Tries) + +Параллельным аналогом коллекции [concurrent.TrieMap](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/concurrent/TrieMap.html), представляющей собой многопоточный и потокозащищеный ассоциативный массив, является коллекция [mutable.ParTrieMap](http://www.scala-lang.org/api/{{ site.scala-version}}/scala/collection/parallel/mutable/ParTrieMap.html). В то время, как большинство многопоточных структур данных не гарантируют правильного перебора элементов в случае, если эта структура данных была изменена во время ее прохождения, многопоточные деревья `Ctries` гарантируют, что обновленные данные станут видны только при следующем прохождении. Это означает, что можно изменять многопоточное дерево прямо во время прохождения, как в следующем примере, в котором выводятся квадратные корни от 1 до 99: + + scala> val numbers = scala.collection.parallel.mutable.ParTrieMap((1 until 100) zip (1 until 100): _*) map { case (k, v) => (k.toDouble, v.toDouble) } + numbers: scala.collection.parallel.mutable.ParTrieMap[Double,Double] = ParTrieMap(0.0 -> 0.0, 42.0 -> 42.0, 70.0 -> 70.0, 2.0 -> 2.0,... + + scala> while (numbers.nonEmpty) { + | numbers foreach { case (num, sqrt) => + | val nsqrt = 0.5 * (sqrt + num / sqrt) + | numbers(num) = nsqrt + | if (math.abs(nsqrt - sqrt) < 0.01) { + | println(num, nsqrt) + | numbers.remove(num) + | } + | } + | } + (1.0,1.0) + (2.0,1.4142156862745097) + (7.0,2.64576704419029) + (4.0,2.0000000929222947) + ... + +[Компоновщики]({{ site.baseurl }}/ru/overviews/parallel-collections/architecture.html#core_abstractions) реализованы как `TrieMap`-- так как эта структура является многопоточной, при вызове метода трансформации создается только один компоновщик, разделяемый всеми процессорами. + +Как и в случае с другими параллельными изменяемыми коллекциями, экземпляры `TrieMap` и параллельных `ParTrieMap`, полученные вызовом методов `seq` или `par`, хранят данные в одном и том же хранилище, поэтому модификации одной коллекции видны в другой. Такие преобразования занимают постоянное время. + +## Характеристики производительности + +Характеристики производительности последовательных типов (sequence types): + +| | head | tail | apply | update| prepend | append | insert | +| -------- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | +| `ParArray` | C | L | C | C | L | L | L | +| `ParVector` | eC | eC | eC | eC | eC | eC | - | +| `ParRange` | C | C | C | - | - | - | - | + +Характеристики производительности множеств (set) и ассоциативных массивов (map): + +| | lookup | add | remove | +| -------- | ---- | ---- | ---- | +| **неизменяемые** | | | | +| `ParHashSet`/`ParHashMap`| eC | eC | eC | +| **изменяемые** | | | | +| `ParHashSet`/`ParHashMap`| C | C | C | +| `ParTrieMap` | eC | eC | eC | + + +### Расшифровка + +Обозначения в двух представленных выше таблицах означают следующее: + +| | | +| --- | ---- | +| **C** | Операция (быстрая) выполняется за постоянное время. | +| **eC** | Операция выполняется за фактически постоянное время, но только при соблюдении некоторых предположений, например о максимальной длине вектора или распределении хэш-кодов.| +| **aC** | Операция выполняется за амортизированное постоянное время. Некоторые вызовы операции могут выполняться медленнее, но при подсчете времени выполнения большого количества операций выходит, что в среднем на операцию требуется постоянное время. | +| **Log** | Операция занимает время, пропорциональное логарифму размера коллекции. | +| **L** | Операция линейна, то есть занимает время, пропорциональное размеру коллекции. | +| **-** | Операция не поддерживается. | + +Первая таблица трактует последовательные типы-- изменяемые и неизменяемые-- в контексте выполнения следующих операций: + +| | | +| --- | ---- | +| **head** | Получение первого элемента последовательности. | +| **tail** | Получение новой последовательности, состоящей из всех элементов исходной, кроме первого. | +| **apply** | Индексирование. | +| **update** | Функциональное обновление (с помощью `updated`) для неизменяемых последовательностей, обновление с побочными действиями (с помощью `update`) для изменяемых. | +| **prepend**| Добавление элемента в начало последовательности. Для неизменяемых последовательностей создается новая последовательность, для изменяемых-- модифицируется существующая. | +| **append** | Добавление элемента в конец последовательности. Для неизменяемых последовательностей создается новая последовательность, для изменяемых-- модифицируется существующая. | +| **insert** | Вставка элемента в выбранную позицию последовательности. Поддерживается только изменяемыми последовательностями. | + +Вторая таблица рассматривает изменяемые и неизменяемые множества и ассоциативные массивы в контексте следующих операций: + +| | | +| --- | ---- | +| **lookup** | Проверка принадлежности элемента множеству, или получение значения, ассоциированного с ключом. | +| **add** | Добавление нового элемента во множество или новой пары ключ/значение в ассоциативный массив. | +| **remove** | Удаление элемента из множества или ключа из ассоциативного массива. | +| **min** | Минимальный элемент множества или минимальный ключ ассоциативного массива. | diff --git a/_ru/overviews/parallel-collections/configuration.md b/_ru/overviews/parallel-collections/configuration.md new file mode 100644 index 0000000000..9b616f8367 --- /dev/null +++ b/_ru/overviews/parallel-collections/configuration.md @@ -0,0 +1,60 @@ +--- +layout: multipage-overview +title: Конфигурирование параллельных коллекций + +discourse: false + +partof: parallel-collections +overview-name: Parallel Collections + +language: ru +num: 7 +--- + +## Обслуживание задач + +Параллельные коллекции предоставляют возможность выбора методов планирования задач и распределения нагрузки на процессоры. В числе параметров каждой параллельной коллекции есть так называемый объект обслуживания задач, который и отвечает за это планирование. + +Внутри объект обслуживания задач содержит ссылку на пул потоков; кроме того он определяет, как и когда задачи разбиваются на более мелкие подзадачи. Подробнее о том, как конкретно происходит этот процесс, можно узнать в техническом отчете \[[1][1]\]. + +В настоящее время для параллельных коллекций доступно несколько реализаций объекта поддержки задач. Например, `ForkJoinTaskSupport` реализован посредством "fork-join" пула и используется по умолчанию на JVM 1.6 или более поздних. Менее эффективный `ThreadPoolTaskSupport` является резервом для JVM 1.5 и тех машин, которые не поддерживают пулы "fork-join". `ExecutionContextTaskSupport` по умолчанию берет из `scala.concurrent` объект контекста выполнения `ExecutionContext` и, таким образом, использует тот же пул потоков, что и `scala.concurrent` (в зависимости от версии JVM, это может быть пул "fork-join" или "thread pool executor"). По умолчанию каждой параллельной коллекции назначается именно обслуживание задач контекста выполнения, поэтому параллельные коллекции используют тот же пул "fork-join", что и API объектов "future". + +Сменить метод обслуживания задач для параллельной коллекции можно так: + + scala> import scala.collection.parallel._ + import scala.collection.parallel._ + + scala> val pc = mutable.ParArray(1, 2, 3) + pc: scala.collection.parallel.mutable.ParArray[Int] = ParArray(1, 2, 3) + + scala> pc.tasksupport = new ForkJoinTaskSupport(new scala.concurrent.forkjoin.ForkJoinPool(2)) + pc.tasksupport: scala.collection.parallel.TaskSupport = scala.collection.parallel.ForkJoinTaskSupport@4a5d484a + + scala> pc map { _ + 1 } + res0: scala.collection.parallel.mutable.ParArray[Int] = ParArray(2, 3, 4) + +Код выше настраивает параллельную коллекцию на использование "fork-join" пула с количеством потоков равным 2. Заставить коллекцию использовать "thread pool executor" можно так: + + scala> pc.tasksupport = new ThreadPoolTaskSupport() + pc.tasksupport: scala.collection.parallel.TaskSupport = scala.collection.parallel.ThreadPoolTaskSupport@1d914a39 + + scala> pc map { _ + 1 } + res1: scala.collection.parallel.mutable.ParArray[Int] = ParArray(2, 3, 4) + +Когда параллельная коллекция сериализуется, поле объекта обслуживания задач исключается из сериализуемых. Когда параллельная коллекция восстанавливается из полученной последовательности байт, это поле приобретает значение по умолчанию, то есть способ обслуживания задач берется из `ExecutionContext`. + +Чтобы реализовать собственный механизм поддержки задач, достаточно унаследовать трейт `TaskSupport` и реализовать следующие методы: + + def execute[R, Tp](task: Task[R, Tp]): () => R + + def executeAndWaitResult[R, Tp](task: Task[R, Tp]): R + + def parallelismLevel: Int + +Метод `execute` планирует асинхронное выполнение задачи и возвращает "future" в качестве ссылки к будущему результату выполнения. Метод `executeAndWait` делает то же самое, но возвращает результат только после завершения задачи. Метод `parallelismLevel` просто возвращает предпочитаемое количество ядер, которое будет использовано для вычислений. + +## Ссылки + +1. [On a Generic Parallel Collection Framework, June 2011][1] + + [1]: http://infoscience.epfl.ch/record/165523/files/techrep.pdf "parallel-collections" diff --git a/_ru/overviews/parallel-collections/conversions.md b/_ru/overviews/parallel-collections/conversions.md new file mode 100644 index 0000000000..8579774ec9 --- /dev/null +++ b/_ru/overviews/parallel-collections/conversions.md @@ -0,0 +1,53 @@ +--- +layout: multipage-overview +title: Преобразования параллельных коллекций + +discourse: false + +partof: parallel-collections +overview-name: Parallel Collections + +language: ru +num: 3 +--- + +## Взаимные преобразования последовательных и параллельных коллекций + +Любая последовательная коллекция может быть преобразована в свою параллельную альтернативу вызовом метода `par`, причем некоторые типы последовательных коллекций имеют прямой параллельный аналог. Для таких коллекций конвертация эффективна-- она занимает постоянное время, так как и последовательная и параллельная коллекция представлены одной и той же структурой данных (за исключением изменяемых хэш-таблиц и хэш-множеств, преобразование которых требует больше времени в первый вызов метода `par`, тогда как последующие вызовы `par` занимают постоянное время). Нужно заметить, что если изменяемые коллекции делят одну лежащую в основе структуру данных, то изменения, сделанные в последовательной коллекции, будут видны в ее параллельной ответной части. + +| Последовательные | Параллельные | +| ---------------- | -------------- | +| **изменяемые** | | +| `Array` | `ParArray` | +| `HashMap` | `ParHashMap` | +| `HashSet` | `ParHashSet` | +| `TrieMap` | `ParTrieMap` | +| **неизменяемые** | | +| `Vector` | `ParVector` | +| `Range` | `ParRange` | +| `HashMap` | `ParHashMap` | +| `HashSet` | `ParHashSet` | + +Другие коллекции, такие, как списки, очереди или потоки, последовательны по своей сути, в том смысле, что элементы должны выбираться один за другим. Такие коллекции преобразуются в свои параллельные альтернативы копированием элементов в схожую параллельную коллекцию. Например, односвязный список преобразуется в стандартную неизменяемую параллельную последовательность, то есть в параллельный вектор. + +Любая параллельная коллекция может быть преобразована в её последовательный вариант вызовом метода `seq`. Конвертирование параллельной коллекции в последовательную эффективно всегда-- оно занимает постоянное время. Вызов `seq` на изменяемой параллельной коллекции возвращает последовательную, которая отображает ту же область памяти-- изменения, сделанные в одной коллекции, будут видимы в другой. + +## Преобразования между различными типами коллекций + +Параллельные коллекции могут конвертироваться в другие типы коллекций, не теряя при этом своей параллельности. Например, вызов метода `toSeq` последовательное множество преобразует в обычную последовательность, а параллельное-- в параллельную. Общий принцип такой: если есть параллельный вариант коллекции `X`, то метод `toX` преобразует коллекцию к типу `ParX`. + +Ниже приведена сводная таблица всех методов преобразования: + +| Метод | Тип возвращаемого значения | +| -------------- | -------------------------- | +| `toArray` | `Array` | +| `toList` | `List` | +| `toIndexedSeq` | `IndexedSeq` | +| `toStream` | `Stream` | +| `toIterator` | `Iterator` | +| `toBuffer` | `Buffer` | +| `toTraversable`| `GenTraverable` | +| `toIterable` | `ParIterable` | +| `toSeq` | `ParSeq` | +| `toSet` | `ParSet` | +| `toMap` | `ParMap` | diff --git a/_ru/overviews/parallel-collections/ctries.md b/_ru/overviews/parallel-collections/ctries.md new file mode 100644 index 0000000000..b463a48e21 --- /dev/null +++ b/_ru/overviews/parallel-collections/ctries.md @@ -0,0 +1,123 @@ +--- +layout: multipage-overview +title: Многопоточные префиксные деревья + +discourse: false + +partof: parallel-collections +overview-name: Parallel Collections + +language: ru +num: 4 +--- + +Большинство многопоточных структур данных не гарантирует неизменности порядка элементов в случае, если эта структура изменяется во время прохождения. То же верно, кстати, и в случае большинства изменяемых коллекций. Особенность многопоточных префиксных деревьев-- `tries`-- заключается в том, что они позволяют модифицировать само дерево, которое в данный момент просматривается. Сделанные изменения становятся видимыми только при следующем прохождении. Так ведут себя и последовательные префиксные деревья, и их параллельные аналоги; единственное отличие-- в том, что первые перебирают элементы последовательно, а вторые-- параллельно. + +Это замечательное свойство позволяет упростить ряд алгоритмов. Обычно это такие алгоритмы, в которых некоторый набор данных обрабатывается итеративно, причем для обработки различных элементов требуется различное количество итераций. + +В следующем примере вычисляются квадратные корни некоторого набор чисел. Каждая итерация обновляет значение квадратного корня. Числа, квадратные корни которых достигли необходимой точности, исключаются из перебираемого набора. + + case class Entry(num: Double) { + var sqrt = num + } + + val length = 50000 + + // готовим исходные данные + val entries = (1 until length) map { num => Entry(num.toDouble) } + val results = ParTrieMap() + for (e <- entries) results += ((e.num, e)) + + // вычисляем квадратные корни + while (results.nonEmpty) { + for ((num, e) <- results) { + val nsqrt = 0.5 * (e.sqrt + e.num / e.sqrt) + if (math.abs(nsqrt - e.sqrt) < 0.01) { + results.remove(num) + } else e.sqrt = nsqrt + } + } + +Отметим, что в приведенном выше вычислении квадратных корней вавилонским методом (\[[3][3]\]) некоторые значения могут сойтись гораздо быстрее, чем остальные. По этой причине мы исключаем их из `results`, чтобы перебирались только те элементы, которые нуждаются в дальнейшей обработке. + +Другим примером является алгоритм поиска в ширину, который итеративно расширяет очередь перебираемых узлов до тех пор, пока или не будет найден целевой узел, или не закончатся узлы, за счет которых можно расширить поиск. Определим точку на двухмерной карте как кортеж значений `Int`. Обозначим как `map` двухмерный массив булевых значений, которые обозначают, занята соответствующая ячейка или нет. Затем объявим два многопоточных дерева-- `open`, которое содержит все точки, которые требуется раскрыть, и `closed`, в котором хранятся уже обработанные точки. Мы намерены начать поиск с углов карты и найти путь к центру-- инициализируем ассоциативный массив `open` подходящими точками. Затем будем раскрывать параллельно все точки, содержащиеся в ассоциативном массиве `open` до тех пор, пока больше не останется точек. Каждый раз, когда точка раскрывается, она удаляется из массива `open` и помещается в массив `closed`. + +Выполнив все это, выведем путь от целевого до стартового узла. + + val length = 1000 + + // объявляем тип Node + type Node = (Int, Int); + type Parent = (Int, Int); + + // операции над типом Node + def up(n: Node) = (n._1, n._2 - 1); + def down(n: Node) = (n._1, n._2 + 1); + def left(n: Node) = (n._1 - 1, n._2); + def right(n: Node) = (n._1 + 1, n._2); + + // создаем карту и целевую точку + val target = (length / 2, length / 2); + val map = Array.tabulate(length, length)((x, y) => (x % 3) != 0 || (y % 3) != 0 || (x, y) == target) + def onMap(n: Node) = n._1 >= 0 && n._1 < length && n._2 >= 0 && n._2 < length + + // список open - фронт обработки + // список closed - уже обработанные точки + val open = ParTrieMap[Node, Parent]() + val closed = ParTrieMap[Node, Parent]() + + // добавляем несколько стартовых позиций + open((0, 0)) = null + open((length - 1, length - 1)) = null + open((0, length - 1)) = null + open((length - 1, 0)) = null + + // "жадный" поиск в ширину + while (open.nonEmpty && !open.contains(target)) { + for ((node, parent) <- open) { + def expand(next: Node) { + if (onMap(next) && map(next._1)(next._2) && !closed.contains(next) && !open.contains(next)) { + open(next) = node + } + } + expand(up(node)) + expand(down(node)) + expand(left(node)) + expand(right(node)) + closed(node) = parent + open.remove(node) + } + } + + // выводим путь + var pathnode = open(target) + while (closed.contains(pathnode)) { + print(pathnode + "->") + pathnode = closed(pathnode) + } + println() + +На GitHub есть пример реализации игры "Жизнь", который использует многопоточные хэш-деревья-- `Ctries`, чтобы выборочно симулировать только те части механизма игры, которые в настоящий момент активны \[[4][4]\]. +Он также включает в себя основанную на `Swing` визуализацию, которая позволяет посмотреть, как подстройка параметров влияет на производительность. + +Многопоточные префиксные деревья также поддерживают атомарную, неблокирующую(lock-free) операцию `snapshot`, выполнение которой осуществляется за постоянное время. Эта операция создает новое многопоточное дерево со всеми элементами на некоторый выбранный момент времени, создавая таким образом снимок состояния дерева в этот момент. +На самом деле, операция `snapshot` просто создает новый корень дерева. Последующие изменения отложенно перестраивают ту часть многопоточного дерева, которая соответствует изменению, и оставляет нетронутой ту часть, которая не изменилась. Прежде всего это означает, что операция 'snapshot' сама по себе не затратна, так как не происходит копирования элементов. Кроме того, так как оптимизация "копирования при записи" создает копии только измененных частей дерева, последующие модификации горизонтально масштабируемы. +Метод `readOnlySnapshot` чуть более эффективен, чем метод `snapshot`, но он возвращает неизменяемый ассоциативный массив, который доступен только для чтения. Многопоточные деревья также поддерживают атомарную операцию постоянного времени `clear`, основанную на рассмотренном механизме снимков. +Чтобы подробнее узнать о том, как работают многопоточные деревья и их снимки, смотрите \[[1][1]\] и \[[2][2]\]. + +На рассмотренном механизме снимков основана работа итераторов многопоточных деревьев. Прежде чем будет создан объект-итератор, берется снимок многопоточного дерева. Таким образом, итератор перебирает только те элементы дерева, которые присутствовали на момент создания снимка. Фактически, итераторы используют те снимки, которые дают доступ только на чтение. + +На том же механизме снимков основана операция `size`. В качестве примитивной реализации этой операции можно просто создать итератор (то есть, снимок) и перебрать все элементы, подсчитывая их. Таким образом, каждый вызов операции `size` будет требовать времени, прямо пропорционального числу элементов. Однако, многопоточные деревья в целях оптимизации кэшируют размеры своих отдельных частей, тем самым уменьшая временную сложность метода `size` до амортизированно-логарифмической. В результате получается, что если вызвать метод `size` один раз, можно осуществлять последующие вызовы `size` затрачивая минимум ресурсов, вычисляя, как правило, размеры только тех частей, которые изменились после последнего вызова `size`. Кроме того, вычисление размера параллельных многопоточных деревьев выполняется параллельно. + + +## Ссылки + +1. ["Cache-Aware" неблокирующие многопоточные хэш-деревья][1] +2. [Многопоточные деревья, поддерживающие эффективные неблокирующие снимки][2] +3. [Методы вычисления квадратных корней][3] +4. [Симуляция игры "Жизнь"][4] + + [1]: http://infoscience.epfl.ch/record/166908/files/ctries-techreport.pdf "Ctries-techreport" + [2]: http://lampwww.epfl.ch/~prokopec/ctries-snapshot.pdf "Ctries-snapshot" + [3]: http://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method "babylonian-method" + [4]: https://github.com/axel22/ScalaDays2012-TrieMap "game-of-life-ctries" diff --git a/_ru/overviews/parallel-collections/custom-parallel-collections.md b/_ru/overviews/parallel-collections/custom-parallel-collections.md new file mode 100644 index 0000000000..6fd6fa9f58 --- /dev/null +++ b/_ru/overviews/parallel-collections/custom-parallel-collections.md @@ -0,0 +1,197 @@ +--- +layout: multipage-overview +title: Создание пользовательской параллельной коллекции + +discourse: false + +partof: parallel-collections +overview-name: Parallel Collections + +language: ru +num: 6 +--- + +## Параллельная коллекция без компоновщиков + +Определить параллельную коллекцию без определения ее компоновщика возможно, так же, как возможно определить собственную последовательную коллекцию без определения ее строителей (`builders`). Вследствие отсутствия компоновщика получится, что методы трансформаций (т.е. `map`, `flatMap`, `collect`, `filter`, ...) по умолчанию будут возвращать ближайшую по иерархии стандартную коллекцию. Например, диапазоны строителей не имеют, и поэтому создание отображения элементов диапазона-- `map` -- создает вектор. + +В следующем примере определим параллельную коллекцию-строку. Так как строки по сути являются неизменяемыми последовательностями, сделаем их класс наследником `immutable.ParSeq[Char]`: + + class ParString(val str: String) + extends immutable.ParSeq[Char] { + +Затем определим методы, которые есть в любой неизменяемой последовательности: + + def apply(i: Int) = str.charAt(i) + + def length = str.length + +Кроме того, мы должны решить, что будем возвращать в качестве последовательного аналога нашей параллельной коллекции. Пусть это будет класс `WrappedString`: + + def seq = new collection.immutable.WrappedString(str) + +И наконец, требуется задать разделитель для наших параллельных строк. Назовем его `ParStringSplitter` и сделаем его потомком разделителя последовательностей, то есть типа `SeqSplitter[Char]`: + + def splitter = new ParStringSplitter(str, 0, str.length) + + class ParStringSplitter(private var s: String, private var i: Int, private val ntl: Int) + extends SeqSplitter[Char] { + + final def hasNext = i < ntl + + final def next = { + val r = s.charAt(i) + i += 1 + r + } + +В примере выше, `ntl` отображает общую длину строки, `i`-- текущую позицию, и наконец `s`-- саму строку. + +Итераторы (или разделители) параллельных коллекций требуют еще несколько методов помимо `next` и `hasNext`, характерных для итераторов последовательных коллекций. Для начала, у них есть метод `remaining`, возвращающий количество элементов, которые данному разделителю еще предстоит перебрать. Затем, метод `dup`, дублирующий текущий разделитель. + + def remaining = ntl - i + + def dup = new ParStringSplitter(s, i, ntl) + +И наконец, методы `split` и `psplit`, которые используются для создания разделителей, перебирающих подмножества элементов текущего разделителя. Для метода `split` действует соглашение, что он возвращает последовательность разделителей, перебирающих непересекающиеся подмножества элементов текущего разделителя, ни одно из которых не является пустым. Если текущий разделитель покрывает один или менее элементов, `split` возвращает саму последовательность этого разделителя. Метод `psplit` должен возвращать последовательность разделителей, перебирающих точно такое количество элементов, которое задано значениями размеров, указанных параметром `sizes`. Если параметр `sizes` требует отделить меньше элементов, чем покрыто текущим разделителем, то дополнительный разделитель со всеми остальными элементами размещается в конце последовательности. Если в параметре `sizes` указано больше элементов, чем содержится в текущем разделителе, для каждого размера, на который не хватило элементов, будет добавлен пустой разделитель. Наконец, вызов `split` или `psplit` делает текущий разделитель недействительным. + + def split = { + val rem = remaining + if (rem >= 2) psplit(rem / 2, rem - rem / 2) + else Seq(this) + } + + def psplit(sizes: Int*): Seq[ParStringSplitter] = { + val splitted = new ArrayBuffer[ParStringSplitter] + for (sz <- sizes) { + val next = (i + sz) min ntl + splitted += new ParStringSplitter(s, i, next) + i = next + } + if (remaining > 0) splitted += new ParStringSplitter(s, i, ntl) + splitted + } + } + } + +Выше приведена реализация метода `split` посредством вызова `psplit`, что часто наиболее оправдано в случае параллельных коллекций. Написать реализацию разделителя для параллельных ассоциативных массивов, множеств или итерируемых объектов чаще всего проще, так как они не требуют реализации метода `psplit`. + +Итак, мы получили класс параллельных строк. Единственным недостатком является то, что вызов методов трансформации, таких, как `filter`, произведет параллельный вектор вместо параллельной строки, что в ряде случаев может оказаться не самым оптимальным решением, так как воссоздание строки из вектора после фильтрации может оказаться затратным. + +## Параллельные коллекции с компоновщиками + +Допустим, мы хотим применить `filter` к символам параллельной строки, например, чтобы избавиться от запятых. Как отмечено выше, вызов `filter` вернет параллельный вектор, в то время как мы хотим получить строку (так как некоторые интерфейсы используемого API могут требовать последовательную строку). + +Чтобы избежать этого, для параллельной строки требуется написать компоновщик. На этот раз мы унаследуем трейт `ParSeqLike`, чтобы конкретизировать значение, возвращаемое методом `filter`-- а именно `ParString` вместо `ParSeq[Char]`. Третий параметр-тип трейта `ParSeqLike` указывает тип последовательного аналога параллельной коллекции (в этом отличие от последовательных трейтов вида `*Like`, имеющих только два параметра). + + class ParString(val str: String) + extends immutable.ParSeq[Char] + with ParSeqLike[Char, ParString, collection.immutable.WrappedString] + +Все методы остаются такими же, как в предыдущем примере, только дополнительно добавляется защищенный метод `newCombiner`, который используется при выполнении метода `filter`. + + protected[this] override def newCombiner: Combiner[Char, ParString] = new ParStringCombiner + +Следующим шагом определяем класс `ParStringCombiner`. Компоновщики являются подтипами строителей, в которых появляется дополнительный метод `combine`, принимающий другой компоновщик как аргумент и возвращающий новый компоновщик, который содержит элементы и текущего и принятого компоновщика. И текущий компоновщик, и компоновщик-аргумент становятся недействительными после вызова `combine`. Если передать аргументом сам текущий компоновщик, метод `combine` просто вернет его же как результат. Предполагается, что метод должен быть эффективным, то есть в худшем случае требовать для выполнения логарифмического времени по отношению к количеству элементов, так как в ходе параллельного вычисления он вызывается большое количество раз. + +Наш `ParStringCombiner` будет содержать последовательность строителей строк. Он будет реализовывать `+=` путем добавления элемента к последнему строителю строки в последовательности, и `combine` конкатенацией списков строителей строк текущего компоновщика и компоновщика-аргумента. Метод `result`, вызываемый в конце параллельного вычисления, произведет параллельную строку соединив все строители строк вместе. Таким образом, элементы копируются только один раз в конце, а не каждый раз, когда вызывается метод `combine`. В идеале, мы должны подумать о том, чтобы еще и копирование проводить параллельно (именно так и происходит в случае параллельных массивов), но без погружения в детали внутреннего представления строк это лучшее, чего мы можем добиться-- остается смириться с этим последовательным узким местом. + + private class ParStringCombiner extends Combiner[Char, ParString] { + var sz = 0 + val chunks = new ArrayBuffer[StringBuilder] += new StringBuilder + var lastc = chunks.last + + def size: Int = sz + + def +=(elem: Char): this.type = { + lastc += elem + sz += 1 + this + } + + def clear = { + chunks.clear + chunks += new StringBuilder + lastc = chunks.last + sz = 0 + } + + def result: ParString = { + val rsb = new StringBuilder + for (sb <- chunks) rsb.append(sb) + new ParString(rsb.toString) + } + + def combine[U <: Char, NewTo >: ParString](other: Combiner[U, NewTo]) = if (other eq this) this else { + val that = other.asInstanceOf[ParStringCombiner] + sz += that.sz + chunks ++= that.chunks + lastc = chunks.last + this + } + } + + +## Как реализовать собственный компоновщик? + +Тут нет стандартного рецепта, -- все зависит от имеющейся структуры данных, и обычно требует изобретательности со стороны того, кто пишет реализацию. Тем не менее, можно выделить несколько подходов, которые обычно применяются: + +1. Конкатенация и объединение. Некоторые структуры данных позволяют реализовать эти операции эффективно (обычно с логарифмической сложностью), и если требуемая коллекция представлена такой структурой данных, ее компоновщик может быть самой такой коллекцией. Особенно хорошо этот подход работает для подвешенных деревьев (finger trees), веревок (ropes) и различных видов куч. + +2. Двухфазное выполнение. Подход, применяемый в случае параллельных массивов и параллельных хэш-таблиц; он предполагает, что элементы могут быть эффективно рассортированы по готовым для конкатенации блокам, из которых результирующая структура данных может быть построена параллельно. В первую фазу блоки заполняются независимо различными процессорами, и в конце просто соединяются конкатенацией. Во вторую фазу происходит выделение памяти для целевой структуры данных, и после этого различные процессоры заполняют различные ее части, используя элементы непересекающихся блоков. +Следует принять меры для того, чтобы различные процессоры никогда не изменяли одну и ту же часть структуры данных, иначе не избежать трудноуловимых, связанных с многопоточностью ошибок. Такой подход легко применить к последовательностям с произвольным доступом, как было показано в предыдущем разделе. + +3. Многопоточная структура данных. Так как последние два подхода, в сущности, не требуют использования примитивных механизмов синхронизации, предполагается, что структура будет строиться несколькими потоками так, что два различных процессора никогда не будут изменять одну и ту же область памяти. Существует большое количество многопоточных структур данных, которые могут безопасно изменяться несколькими процессорами одновременно, среди таких можно упомянуть многопоточные списки с пропусками (skip lists), многопоточные хэш-таблицы, `split-ordered` списки и многопоточные АВЛ-деревья. +При этом требуется следить, чтобы у выбранной многопоточной структуры был горизонтально масштабируемый метод вставки. У многопоточных параллельных коллекций компоновщик может быть представлен самой коллекцией, и единственный его экземпляр обычно используется всеми процессорами, занятыми в выполнении параллельной операции. + +## Интеграция с фреймворком коллекций + +Наш класс `ParString` оказался не вполне завершен: несмотря на то, что мы реализовали собственный компоновщик, который будут использовать такие методы, как `filter`, `partition`, `takeWhile` или `span`, большинство методов трансформации требуют скрытый параметр-доказательство `CanBuildFrom` (подробное объяснение можно посмотреть в "Scala collections guide" (прим. перев. скорее {{ site.baseurl }}/overviews/core/architecture-of-scala-collections.html)). Чтобы обеспечить его доступность и тем самым полностью интегрировать наш класс `ParString` с фреймворком коллекций, требуется примешать дополнительный трейт `GenericParTemplate` и определить объект-компаньон для `ParString`. + + class ParString(val str: String) + extends immutable.ParSeq[Char] + with GenericParTemplate[Char, ParString] + with ParSeqLike[Char, ParString, collection.immutable.WrappedString] { + + def companion = ParString + +Внутрь объекта-компаньона помещаем скрытый параметр-доказательство `CanBuildFrom`. + + object ParString { + implicit def canBuildFrom: CanCombineFrom[ParString, Char, ParString] = + new CanCombinerFrom[ParString, Char, ParString] { + def apply(from: ParString) = newCombiner + def apply() = newCombiner + } + + def newBuilder: Combiner[Char, ParString] = newCombiner + + def newCombiner: Combiner[Char, ParString] = new ParStringCombiner + + def apply(elems: Char*): ParString = { + val cb = newCombiner + cb ++= elems + cb.result + } + } + +## Дальнейшие настройки-- многопоточные и другие коллекции + +Процесс реализации многопоточной коллекции (в отличие от параллельных, многопоточные коллекции могут подобно `collection.concurrent.TrieMap` изменяться одновременно несколькими потоками) не всегда прост и очевиден. При этом особенно нуждаются в тщательном обдумывании компоновщики. Компоновщики большинства _параллельных_ коллекций, которые были рассмотрены до этого момента, используют двухфазное выполнение. На первом этапе элементы добавляются различными процессорами к своим компоновщикам и последние объединяются вместе. На втором шаге, когда становятся доступными все элементы, строится результирующая коллекция. + +Другим подходом является построение результирующей коллекции как структуры элементов компоновщика. Для этого коллекция должна быть потокозащищенной-- компоновщик должен позволять выполнить _многопоточную_ вставку элемента. В этом случае один компоновщик может использоваться всеми процессорами. + +Если требуется сделать многопоточную коллекцию параллельной, в ее компоновщике нужно перегрузить метод `canBeShared` так, чтобы он возвращал `true`. Этим мы заставим проверять, что при выполнении параллельной операции создается только один компоновщик. Далее, метод `+=` должен быть потокозащищенным. И наконец, метод `combine` по-прежнему должен возвращать текущий компоновщик в случае, если он совпадает с аргументом, а в противном случае вполне может выбросить исключение. + +Чтобы добиться лучшей балансировки нагрузки, разделители делятся на более мелкие разделители. По умолчанию решение о том, что дальнейшее разделение не требуется, принимается на основе информации, возвращенной методом `remaining`. Для некоторых коллекций вызов метода `remaining` может быть затратным, и решение о разделении лучше принять другими способами. В этом случае нужно перегрузить метод `shouldSplitFurther` разделителя. + +В реализации по умолчанию разделитель делится, если число оставшихся элементов больше, чем размер коллекции деленный на взятый восемь раз уровень параллелизма. + + def shouldSplitFurther[S](coll: ParIterable[S], parallelismLevel: Int) = + remaining > thresholdFromSize(coll.size, parallelismLevel) + +Как вариант, разделитель может иметь счетчик количества проведенных над ним разделений и реализовывать метод `shouldSplitFurther`, возвращая `true`, если количество разделений больше, чем `3 + log(parallelismLevel)`. Это и позволяет избежать вызова метода `remaining`. + +Более того, если для некоторой коллекции вызов `remaining` затратен (то есть требует обработки большого числа элементов), то метод `isRemainingCheap` в разделителях следует перегрузить, так, чтобы он возвращал `false`. + +Наконец, если реализовать метод `remaining` в разделителях весьма затруднительно, можно возвращать `false` в перегруженном методе `isStrictSplitterCollection` соответствующей коллекции. Над такими коллекциями не получится выполнить ряд методов, в частности таких, которые требуют точности разделителей (последнее предполагает как раз, что метод `remaining` возвращает правильное значение). Но, что важно, это не относится к методам, используемым для обработки for-включений (for-comprehensions). diff --git a/_ru/overviews/parallel-collections/overview.md b/_ru/overviews/parallel-collections/overview.md new file mode 100644 index 0000000000..5d2f43f083 --- /dev/null +++ b/_ru/overviews/parallel-collections/overview.md @@ -0,0 +1,181 @@ +--- +layout: multipage-overview +title: Обзор + +discourse: false + +partof: parallel-collections +overview-name: Parallel Collections + +num: 1 +language: ru +--- + +**Авторы оригинала: Aleksandar Prokopec, Heather Miller** + +**Перевод Анастасии Маркиной** + +## Мотивация + +Пока производители процессоров в последние годы дружно переходили от одноядерных к многоядерным архитектурам, научное и производственное сообщества не менее дружно признали, что многопоточное программирование по-прежнему трудно сделать популярным. + +Чтобы упростить написание многопоточных программ, в стандартную библиотеку Scala были включены параллельные коллекции, которые скрыли от пользователей низкоуровневые подробности параллелизации, дав им привычную высокоуровневую абстракцию. Надежда была (и остается) на то, что скрытая под уровнем абстракции параллельность позволит на шаг приблизиться к ситуации, когда среднестатистический разработчик будет повседневно использовать в работе надежно исполняемый параллельный код. + +Идея проста: коллекции -- хорошо понятная и часто используемая программистами абстракция. Упорядоченность коллекций позволяет эффективно и прозрачно (для пользователя) обрабатывать их параллельно. Позволив пользователю "подменить" последовательные коллекции на те, что обрабатываются параллельно, решение Scala делает большой шаг вперед к охвату большего количества кода возможностями параллельной обработки. + +Рассмотрим следующий пример, где мы исполняем монадическую операцию на некоторой большой последовательной коллекции: + + val list = (1 to 10000).toList + list.map(_ + 42) + +Чтобы выполнить ту же самую операцию параллельно, требуется просто вызвать метод `par` +на последовательной коллекции `list`. После этого можно работать с параллельной коллекцией так же, как и с последовательной. То есть, пример выше примет вид: + + list.par.map(_ + 42) + +Библиотека параллельных коллекций Scala тесно связана с "последовательной" библиотекой коллекций Scala (представлена в версии 2.8), во многом потому, что последняя служила вдохновением к ее дизайну. Он предоставляет параллельную альтернативу ряду важных структур данных из библиотеки (последовательных) коллекций Scala, в том числе: + +* `ParArray` +* `ParVector` +* `mutable.ParHashMap` +* `mutable.ParHashSet` +* `immutable.ParHashMap` +* `immutable.ParHashSet` +* `ParRange` +* `ParTrieMap` (`collection.concurrent.TrieMap` впервые в версии 2.10) + +Библиотека параллельных коллекций Scala _расширяема_ также как и последовательные коллекции, представленные в стандартной библиотеке. Другими словами, как и в случае с обычными последовательными коллекциями, пользователи могут внедрять свои собственные типы коллекций, автоматически наследуя все предопределенные (параллельные) операции, доступные для других параллельных коллекций в стандартной библиотеке. + +## Несколько примеров + +Попробуем изобразить всеобщность и полезность представленных коллекций на ряде простых примеров, для каждого из которых характерно прозрачно-параллельное выполнение. + +_Примечание:_ Некоторые из последующих примеров оперируют небольшими коллекциями, для которых такой подход не рекомендуется. Они должны рассматриваться только как иллюстрация. Эвристически, ускорение становится заметным, когда размер коллекции дорастает до нескольких тысяч элементов. (Более подробно о взаимосвязи между размером коллекции и производительностью, смотрите [соответствующий подраздел]({{ site.baseurl}}/overviews/parallel-collections/performance.html#how_big_should_a_collection_be_to_go_parallel) раздела, посвященного [производительности]({{ site.baseurl }}/ru/overviews/parallel-collections/performance.html) в данном руководстве.) + +#### map + +Используем параллельную `map` для преобразования набора строк `String` в верхний регистр: + + scala> val lastNames = List("Smith","Jones","Frankenstein","Bach","Jackson","Rodin").par + lastNames: scala.collection.parallel.immutable.ParSeq[String] = ParVector(Smith, Jones, Frankenstein, Bach, Jackson, Rodin) + + scala> lastNames.map(_.toUpperCase) + res0: scala.collection.parallel.immutable.ParSeq[String] = ParVector(SMITH, JONES, FRANKENSTEIN, BACH, JACKSON, RODIN) + +#### fold + +Суммируем через `fold` на `ParArray`: + + scala> val parArray = (1 to 10000).toArray.par + parArray: scala.collection.parallel.mutable.ParArray[Int] = ParArray(1, 2, 3, ... + + scala> parArray.fold(0)(_ + _) + res0: Int = 50005000 + +#### filter + +Используем параллельный `filter` для отбора фамилий, которые начинаются с буквы "J" или стоящей дальше в алфавите: + + scala> val lastNames = List("Smith","Jones","Frankenstein","Bach","Jackson","Rodin").par + lastNames: scala.collection.parallel.immutable.ParSeq[String] = ParVector(Smith, Jones, Frankenstein, Bach, Jackson, Rodin) + + scala> lastNames.filter(_.head >= 'J') + res0: scala.collection.parallel.immutable.ParSeq[String] = ParVector(Smith, Jones, Jackson, Rodin) + +## Создание параллельной коллекции + +Параллельные коллекции предназначались для того, чтобы быть использованными таким же образом, как и последовательные; единственное значимое отличие -- в методе _получения_ параллельной коллекции. + +В общем виде, есть два варианта создания параллельной коллекции: + +Первый, с использованием ключевого слова `new` и подходящего оператора import: + + import scala.collection.parallel.immutable.ParVector + val pv = new ParVector[Int] + +Второй, _преобразованием_ последовательной коллекции: + + val pv = Vector(1,2,3,4,5,6,7,8,9).par + +Разовьем эту важную мысль -- последовательные коллекции можно конвертировать в параллельные вызовом метода `par` на последовательных, и, соответственно, параллельные коллекции также можно конвертировать в последовательные вызовом метода `seq` на параллельных. + +_На заметку:_ Коллекции, являющиеся последовательными в силу наследования (в том смысле, что доступ к их элементам требуется получать по порядку, один элемент за другим), такие, как списки, очереди и потоки (streams), преобразовываются в свои параллельные аналоги копированием элементов в соответствующие параллельные коллекции. Например, список `List` конвертируется в стандартную неизменяемую параллельную последовательность, то есть в `ParVector`. Естественно, что копирование, которое для этого требуется, вносит дополнительный расход производительности, которого не требуют другие типы коллекций, такие как `Array`, `Vector`, `HashMap` и т.д. + +Больше информации о конвертировании можно найти в разделах [преобразования]({{ site.baseurl }}/ru/overviews/parallel-collections/conversions.html) и [конкретные классы параллельных коллекций]({{ site.baseurl }}/ru/overviews/parallel-collections/concrete-parallel-collections.html) этого руководства. + +## Семантика + +В то время, как абстракция параллельной коллекции заставляет думать о ней так, как если бы речь шла о нормальной последовательной коллекции, важно помнить, что семантика различается, особенно в том, что касается побочных эффектов и неассоциативных операций. + +Для того, чтобы увидеть, что происходит, для начала представим, _как именно_ операции выполняются параллельно. В концепции, когда фреймворк параллельных коллекций Scala распараллеливает операцию на соответствующей коллекции, он рекурсивно "разбивает" данную коллекцию, параллельно выполняет операцию на каждом разделе коллекции, а затем "комбинирует" все полученные результаты. + +Эти многопоточные, "неупорядоченные" семантики параллельных коллекций приводят к следующим скрытым следствиям: + +1. **Операции, имеющие побочные эффекты, могут нарушать детерминизм** +2. **Неассоциативные операции могут нарушать детерминизм** + +### Операции, имеющие побочные эффекты. + +Вследствие использования фреймворком параллельных коллекций семантики _многопоточного_ выполнения, в большинстве случаев для соблюдения детерминизма требуется избегать выполнения на коллекциях операций, которые выполняют побочные действия. В качестве простого примера попробуем использовать метод доступа `foreach` для увеличения значения переменной `var`, объявленной вне замыкания, которое было передано `foreach`. + + scala> var sum = 0 + sum: Int = 0 + + scala> val list = (1 to 1000).toList.par + list: scala.collection.parallel.immutable.ParSeq[Int] = ParVector(1, 2, 3,… + + scala> list.foreach(sum += _); sum + res01: Int = 467766 + + scala> var sum = 0 + sum: Int = 0 + + scala> list.foreach(sum += _); sum + res02: Int = 457073 + + scala> var sum = 0 + sum: Int = 0 + + scala> list.foreach(sum += _); sum + res03: Int = 468520 + +В примере видно, что несмотря на то, что каждый раз `sum` инициализируется в 0, при каждом новом вызове `foreach` на предложенном `list`, `sum` получает различное значение. Источником недетерминизма является так называемая _гонка_-- параллельное чтение/запись одной и той же изменяемой переменной. + +В примере выше возможен случай, когда два потока прочитают _одно и то же_ значение переменной `sum`, потратят некоторое время на выполнение операции над этим значением `sum`, а потом попытаются записать новое значение в `sum`, что может привести к перезаписи (а следовательно, к потере) значимого результата, как показано ниже: + + Поток A: читает значение sum, sum = 0 значение sum: 0 + Поток B: читает значение sum, sum = 0 значение sum: 0 + Поток A: увеличивает sum на 760, пишет sum = 760 значение sum: 760 + Поток B: увеличивает sum на 12, пишет sum = 12 значение sum: 12 + +Приведенный выше пример демонстрирует сценарий, где два потока успевают прочитать одно и то же значение, `0`, прежде чем один или другой из них успеет прибавить к этому `0` элемент из своего куска параллельной коллекции. В этом случае `Поток A` читает `0` и прибавляет к нему свой элемент, `0+760`, в то время, как `Поток B` прибавляет `0` к своему элементу, `0+12`. После того, как они вычислили свои суммы, каждый из них записывает свое значение в `sum`. Получилось так, что `Поток A` успевает записать значение первым, только для того, чтобы это помещенное в `sum` значение было практически сразу же перезаписано потоком `B`, тем самым полностью перезаписав (и потеряв) значение `760`. + +### Неассоциативные операции + +Из-за _"неупорядоченной"_ семантики, нелишней осторожностью становится требование выполнять только ассоциативные операции во избежание недетерминированности. То есть, если мы имеем параллельную коллекцию `pcoll`, нужно убедиться, что при вызове на `pcoll` функции более высокого уровня, такой как `pcoll.reduce(func)`, порядок, в котором `func` применяется к элементам `pcoll`, может быть произвольным. Простым и очевидным примером неассоциативной операции является вычитание: + + scala> val list = (1 to 1000).toList.par + list: scala.collection.parallel.immutable.ParSeq[Int] = ParVector(1, 2, 3,… + + scala> list.reduce(_-_) + res01: Int = -228888 + + scala> list.reduce(_-_) + res02: Int = -61000 + + scala> list.reduce(_-_) + res03: Int = -331818 + +В примере выше, мы берем `ParVector[Int]`, вызываем функцию `reduce`, и передаем ей `_-_`, которая просто берет два неименованных элемента, и вычитает один из другого. Вследствие того, что фреймворк параллельных коллекций порождает потоки и независимо выполняет `reduce(_-_)` на разных частях коллекции, результат двух запусков `reduce(_-_)` на одной и той же коллекции не будет одним и тем же. + +_Примечание:_ Часто возникает мысль, что так же, как и в случае с неассоциативными, некоммутативные операции, переданные в более высокую уровнем функцию на параллельной коллекции, приводят к недетеминированному поведению. Это неверно, простой пример -- конкатенация строк -- ассоциативная, но некоммутативная операция: + + scala> val strings = List("abc","def","ghi","jk","lmnop","qrs","tuv","wx","yz").par + strings: scala.collection.parallel.immutable.ParSeq[java.lang.String] = ParVector(abc, def, ghi, jk, lmnop, qrs, tuv, wx, yz) + + scala> val alphabet = strings.reduce(_++_) + alphabet: java.lang.String = abcdefghijklmnopqrstuvwxyz + +_"Неупорядоченная"_ семантика параллельных коллекций означает только то, что операции будут выполнены не по порядку (во _временном_ отношении. То есть, не последовательно), она не означает, что результат будет "*перемешан*" относительно изначального порядка (в _пространственном_ отношении). Напротив, результат будет практически всегда пересобран _по-порядку_-- то есть, параллельная коллекция, разбитая на части в порядке A, B, C, будет снова объединена в том же порядке A, B, C, а не в каком-то произвольном, например, B, C, A. + +Если требуется больше информации о том, как разделяются и комбинируются операции на различных типах коллекций, посетите раздел [Архитектура]({{ site.baseurl }}/ru/overviews/parallel-collections/architecture.html) этого руководства. diff --git a/_ru/overviews/parallel-collections/performance.md b/_ru/overviews/parallel-collections/performance.md new file mode 100644 index 0000000000..f1ae027304 --- /dev/null +++ b/_ru/overviews/parallel-collections/performance.md @@ -0,0 +1,188 @@ +--- +layout: multipage-overview +title: Измерение производительности + +discourse: false + +partof: parallel-collections +overview-name: Parallel Collections + +num: 8 +language: ru +--- + +## Производительность JVM + +При описании модели производительности выполнения кода на JVM иногда ограничиваются несколькими комментариями, и как результат-- не всегда становится хорошо понятно, что в силу различных причин написанный код может быть не таким производительным или расширяемым, как можно было бы ожидать. В этой главе будут приведены несколько примеров. + +Одной из причин является то, что процесс компиляции выполняющегося на JVM приложения не такой, как у языка со статической компиляцией (как можно увидеть здесь \[[2][2]\]). Компиляторы Java и Scala обходятся минимальной оптимизацией при преобразовании исходных текстов в байткод JVM. При первом запуске на большинстве современных виртуальных Java-машин байткод преобразуется в машинный код той архитектуры, на которой он запущен. Это преобразование называется компиляцией "на лету" или JIT-компиляцией (JIT от just-in-time). Однако из-за того, что компиляция "на лету" должна быть быстрой, уровень оптимизации при такой компиляции остается низким. Более того, чтобы избежать повторной компиляции, компилятор HotSpot оптимизирует только те участки кода, которые выполняются часто. Поэтому тот, кто пишет тест производительности, должен учитывать, что программа может показывать разную производительность каждый раз, когда ее запускают: многократное выполнение одного и того же куска кода (то есть, метода) на одном экземпляре JVM может демонстрировать очень разные результаты замеров производительности в зависимости от того, оптимизировался ли определенный код между запусками. Более того, измеренное время выполнения некоторого участка кода может включать в себя время, за которое произошла сама оптимизация JIT-компилятором, что сделает результат измерения нерепрезентативным. + +Кроме этого, результат может включать в себя потраченное на стороне JVM время на осуществление операций автоматического управления памятью. Время от времени выполнение программы прерывается и вызывается сборщик мусора. Если исследуемая программа размещает хоть какие-нибудь данные в куче (а большинство программ JVM размещают), значит сборщик мусора должен запуститься, возможно, исказив при этом результаты измерений. Можно нивелировать влияние сборщика мусора на результат, запустив измеряемую программу множество раз, и тем самым спровоцировав большое количество циклов сборки мусора. + +Одной из распространенных причин ухудшения производительности является упаковка и распаковка примитивов, которые неявно происходят в случаях, когда примитивный тип передается аргументом в обобщенный (generic) метод. Чтобы примитивные типы можно было передать в метод с параметром обобщенного типа, они во время выполнения преобразуются в представляющие их объекты. Этот процесс замедляет выполнение, а кроме того порождает необходимость в дополнительном выделении памяти и, соответственно, создает дополнительный мусор в куче. + +В качестве распространенной причины ухудшения параллельной производительности можно назвать соперничество за память (memory contention), возникающее из-за того, что программист не может явно указать, где следует размещать объекты. Фактически, из-за влияния сборщика мусора, это соперничество может произойти на более поздней стадии жизни приложения, а именно после того, как объекты начнут перемещаться в памяти. Такие влияния нужно учитывать при написании теста. + +## Пример микротеста производительности + +Существует несколько подходов, позволяющих избежать описанных выше эффектов во время измерений. В первую очередь следует убедиться, что JIT-компилятор преобразовал исходный текст в машинный код (и что последний был оптимизирован), прогнав микротест производительности достаточное количество раз. Этот процесс известен как фаза разогрева (warm-up). + +Для того, чтобы уменьшить число помех, вызванных сборкой мусора от объектов, размещенных другими участками программы или несвязанной компиляцией "на лету", требуется запустить микротест на отдельном экземпляре JVM. + +Кроме того, запуск следует производить на серверной версии HotSpot JVM, которая выполняет более агрессивную оптимизацию. + +Наконец, чтобы уменьшить вероятность того, что сборка мусора произойдет посреди микротеста, лучше всего добиться выполнения цикла сборки мусора перед началом теста, а следующий цикл отложить настолько, насколько это возможно. + +В стандартной библиотеке Scala предопределен трейт `scala.testing.Benchmark`, спроектированный с учетом приведенных выше соображений. Ниже приведен пример тестирования производительности операции `map` многопоточного префиксного дерева: + + import collection.parallel.mutable.ParTrieMap + import collection.parallel.ForkJoinTaskSupport + + object Map extends testing.Benchmark { + val length = sys.props("length").toInt + val par = sys.props("par").toInt + val partrie = ParTrieMap((0 until length) zip (0 until length): _*) + + partrie.tasksupport = new ForkJoinTaskSupport(new scala.concurrent.forkjoin.ForkJoinPool(par)) + + def run = { + partrie map { + kv => kv + } + } + } + +Метод `run` содержит код микротеста, который будет повторно запускаться для измерения времени своего выполнения. Объект `Map`, расширяющий трейт `scala.testing.Benchmark`, запрашивает передаваемые системой параметры уровня параллелизма `par` и количества элементов дерева `length`. + +После компиляции программу, приведенную выше, следует запустить так: + + java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=1 -Dlength=300000 Map 10 + +Флаг `server` требует использовать серверную VM. Флаг `cp` означает "classpath", то есть указывает, что файлы классов требуется искать в текущем каталоге и в jar-архиве библиотеки Scala. Аргументы `-Dpar` и `-Dlength`-- это количество потоков и количество элементов соответственно. Наконец, `10` означает что тест производительности будет запущен на одной и той же JVM именно 10 раз. + +Устанавливая количество потоков `par` в `1`, `2`, `4` и `8`, получаем следующее время выполнения на четырехъядерном i7 с поддержкой гиперпоточности: + + Map$ 126 57 56 57 54 54 54 53 53 53 + Map$ 90 99 28 28 26 26 26 26 26 26 + Map$ 201 17 17 16 15 15 16 14 18 15 + Map$ 182 12 13 17 16 14 14 12 12 12 + +Можно заметить, что на первые запуски требуется больше времени, но после оптимизации кода оно уменьшается. Кроме того, мы можем увидеть что гиперпотоковость не дает большого преимущества в нашем примере, это следует из того, что увеличение количества потоков от `4` до `8` не приводит к значительному увеличению производительности. + +## Насколько большую коллекцию стоит сделать параллельной? + +Этот вопрос задается часто, но ответ на него достаточно запутан. + +Размер коллекции, при котором оправданы затраты на параллелизацию, в действительности зависит от многих факторов. Некоторые из них (но не все) приведены ниже: + +- Архитектура системы. Различные типы CPU имеют различную архитектуру и различные характеристики масштабируемости. Помимо этого, машина может быть многоядерной, а может иметь несколько процессоров, взаимодействующих через материнскую плату. +- Производитель и версия JVM. Различные виртуальные машины применяют различные оптимизации кода во время выполнения и реализуют различные механизмы синхронизации и управления памятью. Некоторые из них не поддерживают `ForkJoinPool`, возвращая нас к использованию `ThreadPoolExecutor`, что приводит к увеличению накладных расходов. +- Поэлементная нагрузка. Величина нагрузки, оказываемой обработкой одного элемента, зависит от функции или предиката, которые требуется выполнить параллельно. Чем меньше эта нагрузка, тем выше должно быть количество элементов для получения ускорения производительности при параллельном выполнении. +- Выбранная коллекция. Например, разделители `ParArray` и `ParTrieMap` перебирают элементы коллекции с различными скоростями, а значит разницу количества нагрузки при обработке каждого элемента создает уже сам перебор. +- Выбранная операция. Например, у `ParVector` намного медленнее методы трансформации (такие, как `filter`) чем методы получения доступа (как `foreach`) +- Побочные эффекты. При изменении областей памяти несколькими потоками или при использовании механизмов синхронизации внутри тела `foreach`, `map`, и тому подобных, может возникнуть соперничество. +- Управление памятью. Размещение большого количества объектов может спровоцировать цикл сборки мусора. В зависимости от способа передачи ссылок на новые объекты, цикл сборки мусора может занимать больше или меньше времени. + +Даже рассматривая вышеперечисленные факторы по отдельности, не так-то просто рассуждать о влиянии каждого, а тем более дать точный ответ, каким же должен быть размер коллекции. Чтобы в первом приближении проиллюстрировать, каким же он должен быть, приведем пример выполнения быстрой и не вызывающей побочных эффектов операции сокращения параллельного вектора (в нашем случае-- суммированием) на четырехъядерном процессоре i7 (без использования гиперпоточности) на JDK7: + + import collection.parallel.immutable.ParVector + + object Reduce extends testing.Benchmark { + val length = sys.props("length").toInt + val par = sys.props("par").toInt + val parvector = ParVector((0 until length): _*) + + parvector.tasksupport = new collection.parallel.ForkJoinTaskSupport(new scala.concurrent.forkjoin.ForkJoinPool(par)) + + def run = { + parvector reduce { + (a, b) => a + b + } + } + } + + object ReduceSeq extends testing.Benchmark { + val length = sys.props("length").toInt + val vector = collection.immutable.Vector((0 until length): _*) + + def run = { + vector reduce { + (a, b) => a + b + } + } + } + +Сначала запустим тест производительности с `250000` элементами и получим следующие результаты для `1`, `2` и `4` потоков: + + java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=1 -Dlength=250000 Reduce 10 10 + Reduce$ 54 24 18 18 18 19 19 18 19 19 + java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=2 -Dlength=250000 Reduce 10 10 + Reduce$ 60 19 17 13 13 13 13 14 12 13 + java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=4 -Dlength=250000 Reduce 10 10 + Reduce$ 62 17 15 14 13 11 11 11 11 9 + +Затем уменьшим количество элементов до `120000` и будем использовать `4` потока для сравнения со временем сокращения последовательного вектора: + + java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=4 -Dlength=120000 Reduce 10 10 + Reduce$ 54 10 8 8 8 7 8 7 6 5 + java -server -cp .:../../build/pack/lib/scala-library.jar -Dlength=120000 ReduceSeq 10 10 + ReduceSeq$ 31 7 8 8 7 7 7 8 7 8 + +Похоже, что `120000` близко к пограничному значению в этом случае. + +В качестве еще одного примера возьмем метод `map` (метод трансформации) коллекции `mutable.ParHashMap` и запустим следующий тест производительности в той же среде: + + import collection.parallel.mutable.ParHashMap + + object Map extends testing.Benchmark { + val length = sys.props("length").toInt + val par = sys.props("par").toInt + val phm = ParHashMap((0 until length) zip (0 until length): _*) + + phm.tasksupport = new collection.parallel.ForkJoinTaskSupport(new scala.concurrent.forkjoin.ForkJoinPool(par)) + + def run = { + phm map { + kv => kv + } + } + } + + object MapSeq extends testing.Benchmark { + val length = sys.props("length").toInt + val hm = collection.mutable.HashMap((0 until length) zip (0 until length): _*) + + def run = { + hm map { + kv => kv + } + } + } + +Для `120000` элементов получаем следующие значения времени на количестве потоков от `1` до `4`: + + java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=1 -Dlength=120000 Map 10 10 + Map$ 187 108 97 96 96 95 95 95 96 95 + java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=2 -Dlength=120000 Map 10 10 + Map$ 138 68 57 56 57 56 56 55 54 55 + java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=4 -Dlength=120000 Map 10 10 + Map$ 124 54 42 40 38 41 40 40 39 39 + +Теперь уменьшим число элементов до `15000` и сравним с последовательным хэш-отображением: + + java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=1 -Dlength=15000 Map 10 10 + Map$ 41 13 10 10 10 9 9 9 10 9 + java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=2 -Dlength=15000 Map 10 10 + Map$ 48 15 9 8 7 7 6 7 8 6 + java -server -cp .:../../build/pack/lib/scala-library.jar -Dlength=15000 MapSeq 10 10 + MapSeq$ 39 9 9 9 8 9 9 9 9 9 + +Для выбранных в этом случае коллекции и операции есть смысл сделать вычисление параллельным при количестве элементов больше `15000` (в общем случае хэш-отображения и хэш-множества возможно делать параллельными на меньших количествах элементов, чем требовалось бы для массивов или векторов). + +## Ссылки + +1. [Anatomy of a flawed microbenchmark, Brian Goetz][1] +2. [Dynamic compilation and performance measurement, Brian Goetz][2] + + [1]: http://www.ibm.com/developerworks/java/library/j-jtp02225/index.html "flawed-benchmark" + [2]: http://www.ibm.com/developerworks/library/j-jtp12214/ "dynamic-compilation" diff --git a/_sass/base/body.scss b/_sass/base/body.scss new file mode 100755 index 0000000000..a87c024512 --- /dev/null +++ b/_sass/base/body.scss @@ -0,0 +1,18 @@ +// BODY +//------------------------------------------------ +//------------------------------------------------ +html { + box-sizing: border-box; +} + +*, +*::before, +*::after { + box-sizing: inherit; +} + +html, +body { + height: 90%; + margin: 0; +} diff --git a/_sass/base/form.scss b/_sass/base/form.scss new file mode 100755 index 0000000000..5f69c1b80f --- /dev/null +++ b/_sass/base/form.scss @@ -0,0 +1,30 @@ +// FORM +//------------------------------------------------ +//------------------------------------------------ + +input, +select, +textarea { + font-family: $base-font-family; + font-size: $font-size-large; + display: block; + appearance: none; + border: none; +} + + +::-webkit-input-placeholder { + color: $base-font-color-light; +} + +:-moz-placeholder { /* Firefox 18- */ + color: $base-font-color-light; +} + +::-moz-placeholder { /* Firefox 19+ */ + color: $base-font-color-light; +} + +:-ms-input-placeholder { + color: $base-font-color-light; +} diff --git a/_sass/base/helper.scss b/_sass/base/helper.scss new file mode 100755 index 0000000000..1b32d31165 --- /dev/null +++ b/_sass/base/helper.scss @@ -0,0 +1,14 @@ +// HELPER +//------------------------------------------------ +//------------------------------------------------ + +.wrap { + @include outer-container; + @include padding(0 20px); +} + +.dot { + font-size: 10px; + color: rgba($base-font-color-light, 0.6); + margin: 0 3px; +} diff --git a/_sass/base/lists.scss b/_sass/base/lists.scss new file mode 100755 index 0000000000..b5520d5c92 --- /dev/null +++ b/_sass/base/lists.scss @@ -0,0 +1,22 @@ +// LISTS +//------------------------------------------------ +//------------------------------------------------ +ul, +ol { + list-style-type: none; + margin: 0; + padding: 0; +} + +dl { + margin: 0; +} + +dt { + font-weight: 600; + margin: 0; +} + +dd { + margin: 0; +} diff --git a/_sass/base/media.scss b/_sass/base/media.scss new file mode 100755 index 0000000000..ef8cc88440 --- /dev/null +++ b/_sass/base/media.scss @@ -0,0 +1,12 @@ +// MEDIA +//------------------------------------------------ +//------------------------------------------------ +figure { + margin: 0; +} + +img, +picture { + margin: 0; + max-width: 100%; +} diff --git a/_sass/base/typography.scss b/_sass/base/typography.scss new file mode 100755 index 0000000000..0a7abfc796 --- /dev/null +++ b/_sass/base/typography.scss @@ -0,0 +1,62 @@ +// TYPOGRAPHY +//------------------------------------------------ +//------------------------------------------------ +html { +font-size: $base-font-size; + +} + +body { + color: $base-font-color; + font-family: $base-font-family; + font-weight: $font-regular; + line-height: $base-line-height; + @include font-smoothing(on); +} +* { + transition: $base-transition; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + font-family: $heading-font-family; + line-height: $heading-line-height; + font-weight: $font-black; + margin: 0; +} + +h2 { + font-weight: $font-regular; +} + +p { + margin: 0; +} + +a { + color: $base-link-color; + text-decoration: none; + transition: $base-transition; + &:active, + &:focus, + &:hover { + color: $hover-link-color; + text-decoration: underline; + } +} + +hr { + border-bottom: $base-border-gray; + border-left: 0; + border-right: 0; + border-top: 0; + margin: 0; +} + +blockquote { + margin: 0; +} diff --git a/_sass/components/buttons.scss b/_sass/components/buttons.scss new file mode 100755 index 0000000000..ebc9d98b2f --- /dev/null +++ b/_sass/components/buttons.scss @@ -0,0 +1,22 @@ +// BUTTONS +//------------------------------------------------ +//------------------------------------------------ +.button { + padding: 8px 18px; + font-size: $font-size-small; + font-weight: $font-bold; + text-transform: uppercase; + color: #fff; + background: $brand-secondary; + border-radius: $border-radius-base; + display: inline-block; + + &:active, + &:focus, + &:hover { + text-decoration: none; + color: #fff; + background: $brand-primary; + } + +} diff --git a/_sass/components/calendar.scss b/_sass/components/calendar.scss new file mode 100755 index 0000000000..bd2ef3a496 --- /dev/null +++ b/_sass/components/calendar.scss @@ -0,0 +1,33 @@ +// CALENDAR +//------------------------------------------------ +//------------------------------------------------ + +.calendar { + width: 28px; + height: 32px; + background: #fff; + border-radius: $border-radius-small; + overflow: hidden; + + span { + display: block; + font-size: 10px; + font-weight: $font-bold; + text-align: center; + } + span:first-child { + background: $brand-primary; + color: #fff; + } + +} + +a { + .calendar { + span:last-child { + color: $gray-dark; + font-size: 12px; + margin-top: -1px; + } + } +} diff --git a/_sass/components/call-to-action.scss b/_sass/components/call-to-action.scss new file mode 100755 index 0000000000..34c3d7ad7b --- /dev/null +++ b/_sass/components/call-to-action.scss @@ -0,0 +1,39 @@ +// CALL TO ACTION +//------------------------------------------------ +//------------------------------------------------ + +.call-to-action { + text-align: center; + margin-top: $padding-large; + + &.action-medium { + margin-top: $padding-medium; + } + + &.action-small { + margin-top: $padding-small; + } + + p { + font-size: $font-size-small; + color: $base-font-color-inverse; + + &.align-top { + margin-bottom: 12px; + } + + &.align-bottom { + margin-top: 12px; + } + + a { + text-decoration: underline; + color: $base-font-color-inverse; + &:active, + &:focus, + &:hover { + color: #fff; + } + } + } +} diff --git a/_sass/components/card.scss b/_sass/components/card.scss new file mode 100755 index 0000000000..6760614971 --- /dev/null +++ b/_sass/components/card.scss @@ -0,0 +1,60 @@ +// CARD +//------------------------------------------------ +//------------------------------------------------ + +.card { + padding: 18px; + border-radius: $border-radius-base; + @include display(flex); + @include flex-direction(row); + background: $gray; + transition: $base-transition; + margin-bottom: 14px; + + &:hover { + background: $gray-dark; + } + + img { + width: 28px; + height: 28px; + border-radius: $border-radius-small; + } + + .card-text { + margin-left: 14px; + + h4 { + font-family: $base-font-family; + font-size: $font-size-large; + color: #fff; + } + + ul { + li { + color: $base-font-color-inverse; + display: inline-block; + + &.online-courses-price, + &.event-location { + font-size: $font-size-xsmall; + text-transform: uppercase; + } + + &.online-courses-date, + &.date-event { + font-size: $font-size-small; + } + + &.dot { + color: rgba($base-font-color-inverse, 0.4); + } + } + } + } + &:active, + &:focus, + &:hover { + text-decoration: none; + } +} diff --git a/_sass/components/code.scss b/_sass/components/code.scss new file mode 100755 index 0000000000..e771eb6ad1 --- /dev/null +++ b/_sass/components/code.scss @@ -0,0 +1,25 @@ +// CODE +//------------------------------------------------ +//------------------------------------------------ +.code-element { + margin-bottom: 20px; + + pre { + margin-top: 0; + } + + code { + padding: 20px; + } + + .bar-code { + background: #B4BBBD; + text-align: center; + padding: 2px 0; + font-size: $font-size-small; + font-weight: $font-bold; + min-height: 26px; + @include border-radius($border-radius-base $border-radius-base 0 0); + + } +} diff --git a/_sass/components/dropdown.scss b/_sass/components/dropdown.scss new file mode 100644 index 0000000000..30f41cb713 --- /dev/null +++ b/_sass/components/dropdown.scss @@ -0,0 +1,162 @@ +// Search +//------------------------------------------------ +//------------------------------------------------ + +.language-dropdown { + position: relative; + margin-top: 50px; + @include span-columns(3); + @include bp(medium) { + // @include span-columns(12); + // margin-top: 20px; + // margin-bottom: 20px; + display: none; + } + width: 160px; + // @include bp(medium) { + // width: 100%; + // } + float: right; + + .wrapper-dropdown { + padding: 8px 18px 8px 40px; + appearance: none; + font-size: $base-font-size; + border-radius: $border-radius-base; + box-sizing: border-box; + + &:focus { + outline: none; + background: rgba(#fff, 0.9); + @include box-shadow($box-shadow-inner); + color: $base-font-color; + // border-radius: 0; + } + /* Styles */ + // background: #fff; + background: rgba(255, 255, 255, 0.4); + // border: 1px solid rgba(0,0,0,0.15); + box-shadow: 0 1px 1px rgba(50,50,50,0.1); + cursor: pointer; + outline: none; + /* Font settings */ + color: darken(#8aa8bd, 5%); + font-weight: $font-bold; + + &:before { + content: ""; + width: 0; + height: 0; + position: absolute; + right: 15px; + top: 50%; + margin-top: -3px; + border-width: 6px 6px 0 6px; + border-style: solid; + border-color: #8aa8bd transparent; + } + + .dropdown { + /* Size & position */ + position: absolute; + z-index: 10; + top: 110%; + left: 0; + right: 0; + /* Styles */ + background: white; + border-radius: inherit; + border: 1px solid rgba(0,0,0,0.17); + // box-shadow: 0 0 5px rgba(0,0,0,0.1); + box-shadow: $box-shadow-item; + font-weight: normal; + transition: $base-transition; + list-style: none; + /* Hiding */ + opacity: 0; + pointer-events: none; + // &:after { + // content: ""; + // width: 0; + // height: 0; + // position: absolute; + // bottom: 100%; + // left: 15px; + // border-width: 0 6px 6px 6px; + // border-style: solid; + // border-color: #fff transparent; + // } + // + // &:before { + // content: ""; + // width: 0; + // height: 0; + // position: absolute; + // bottom: 100%; + // left: 13px; + // border-width: 0 8px 8px 8px; + // border-style: solid; + // border-color: rgba(0,0,0,0.1) transparent; + // } + li { + a { + display: block; + padding: 10px; + text-decoration: none; + color: #8aa8bd; + border-bottom: 1px solid #e6e8ea; + box-shadow: inset 0 1px 0 rgba(255,255,255,1); + transition: $base-transition; + } + + i { + float: right; + color: inherit; + } + + &:first-of-type a { + border-radius: 7px 7px 0 0; + } + + &:last-of-type a { + border: none; + border-radius: 0 0 7px 7px; + } + + &:hover a { + background: #f3f8f8; + } + } + } + + &.active .dropdown { + opacity: 1; + pointer-events: auto; + border-radius: $border-radius-base; + } + } + + .result-container { + position: absolute; + display: none; + width: 100%; + left: 0; + top: 38px; + background: #fff; + @include box-shadow($box-shadow-search); + padding: 10px; + + li { + border-bottom: $base-border-gray; + + a { + display: block; + padding: 4px 16px; + } + + &:last-child { + border-bottom: none; + } + } + } +} diff --git a/_sass/components/heading-line.scss b/_sass/components/heading-line.scss new file mode 100755 index 0000000000..827b1cd966 --- /dev/null +++ b/_sass/components/heading-line.scss @@ -0,0 +1,52 @@ +// HEADING LINE +//------------------------------------------------ +//------------------------------------------------ + +.heading-line { + margin-bottom: $padding-large; + text-align: center; + + h2 { + color: #fff; + position: relative; + font-size: $font-size-h2; + font-weight: $font-bold; + + span { + padding: 0 30px; + position: relative; + background: $gray-dark; + z-index: 5; + } + + + + &:before { + content: ""; + display: block; + height: 1px; + position: absolute; + top: 50%; + width: 100%; + background: $base-border-color-white; + } + } + + .sub-heading { + font-size: $font-size-small; + color: $base-font-color-inverse; + font-style: italic; + } + + .lead { + font-size: $font-size-large; + color: $base-font-color-inverse; + margin-top: 10px; + @include span-columns(8); + @include shift(2); + @include bp(medium) { + @include span-columns(12); + @include shift(0); + } + } +} diff --git a/_sass/components/pagination.scss b/_sass/components/pagination.scss new file mode 100755 index 0000000000..38bcccaf28 --- /dev/null +++ b/_sass/components/pagination.scss @@ -0,0 +1,33 @@ +// PAGINATION +//------------------------------------------------ +//------------------------------------------------ +.pagination { + margin-top: $padding-medium; + @include display(flex); + @include align-items(center); + @include justify-content(center); + + .pagination-item { + a { + display: block; + padding: 3px 10px; + background: $brand-tertiary; + color: #fff; + margin-right: 2px; + border-radius: $border-radius-small; + + &:active, + &:focus, + &:hover { + text-decoration: none; + background: darken($brand-tertiary, 15%); + } + + &.active { + background: #fff; + color: $base-font-color-light; + pointer-events: none; + } + } + } +} diff --git a/_sass/components/search.scss b/_sass/components/search.scss new file mode 100755 index 0000000000..6bccdd20b8 --- /dev/null +++ b/_sass/components/search.scss @@ -0,0 +1,54 @@ +// Search +//------------------------------------------------ +//------------------------------------------------ + +.search-container { + position: relative; + + .icon-search { + position: absolute; + left: 14px; + top: 4px; + z-index: 30; + } + + input { + padding: 8px 18px 8px 40px; + appearance: none; + font-size: $base-font-size; + border-radius: $border-radius-base; + width: 100%; + box-sizing: border-box; + + &:focus { + outline: none; + background: rgba(#fff, 0.9); + @include box-shadow($box-shadow-inner); + border-radius: 0; + } + } + + .result-container { + position: absolute; + display: none; + width: 100%; + left: 0; + top: 38px; + background: #fff; + @include box-shadow($box-shadow-search); + padding: 10px; + + li { + border-bottom: $base-border-gray; + + a { + display: block; + padding: 4px 16px; + } + + &:last-child { + border-bottom: none; + } + } + } +} diff --git a/_sass/components/slider.scss b/_sass/components/slider.scss new file mode 100755 index 0000000000..9717861a76 --- /dev/null +++ b/_sass/components/slider.scss @@ -0,0 +1,35 @@ +// SLIDER +//------------------------------------------------ +//------------------------------------------------ +.unslider { + ul { + li { + padding: 0 1px; + } + } + + .unslider-arrow { + display: none; + } + + .unslider-nav { + margin-top: 10px; + @include bp(large) { + margin-top: 10px; + } + + ol { + li { + width: 7px; + height: 7px; + border: none; + background: rgba(#fff, 0.3); + + &.unslider-active { + pointer-events: none; + background: #fff; + } + } + } + } +} diff --git a/_sass/components/tab.scss b/_sass/components/tab.scss new file mode 100755 index 0000000000..b5a9bb89c3 --- /dev/null +++ b/_sass/components/tab.scss @@ -0,0 +1,43 @@ +// TAB +//------------------------------------------------ +//------------------------------------------------ + +.nav-tab { + border-bottom: $base-border-gray; + @include display(flex); + @include align-items(center); + @include justify-content(flex-start); + margin-bottom: 10px; + + .item-tab { + a { + color: $base-font-color-light; + display: block; + padding: 0 20px 10px; + margin-bottom: -1px; + + &:active, + &:focus, + &:hover { + text-decoration: none; + color: $base-font-color; + } + + &.active { + border-bottom: 2px solid $brand-primary; + color: $brand-primary; + pointer-events: none; + } + } + + } + @include bp(small) { + @include justify-content(space-between); + .item-tab { + a { + padding: 0 10px 10px; + font-size: $font-size-medium; + } + } + } +} diff --git a/_sass/components/tag.scss b/_sass/components/tag.scss new file mode 100755 index 0000000000..4b242f43b3 --- /dev/null +++ b/_sass/components/tag.scss @@ -0,0 +1,20 @@ +// TAG +//------------------------------------------------ +//------------------------------------------------ +.tag-list { + @include display(flex); + @include align-items(center); + @include justify-content(flex-start); + flex-wrap: wrap; + padding-left: 0; + .tag-item { + font-size: 0.6875rem; + background: $gray-lighter; + padding: 2px 10px; + text-transform: uppercase; + color: $base-font-color-light; + margin-right: 8px; + margin-bottom: 8px; + list-style: none; + } +} diff --git a/_sass/components/tooltip.scss b/_sass/components/tooltip.scss new file mode 100755 index 0000000000..861cf5edd6 --- /dev/null +++ b/_sass/components/tooltip.scss @@ -0,0 +1,13 @@ +// MAINTENANCE +//------------------------------------------------ +//------------------------------------------------ + +.tooltip { + display:none; + position:absolute; + background-color: rgba(#002B36, 0.95); + border-radius: $border-radius-base; + padding: 5px 12px; + color: #fff; + font-size: $font-size-small; +} diff --git a/_sass/layout/blog.scss b/_sass/layout/blog.scss new file mode 100755 index 0000000000..b0b3f3fdf1 --- /dev/null +++ b/_sass/layout/blog.scss @@ -0,0 +1,113 @@ +// BLOG +//------------------------------------------------ +//------------------------------------------------ + +.title-page { + h1 { + line-height: 1.875rem; + } + .content-title-blog { + h1 { + @include span-columns(9); + @include bp(medium) { + @include span-columns(12); + } + } + + .search-container { + @include span-columns(3); + margin-top: 50px; + @include bp(medium) { + @include span-columns(12); + margin-top: 20px; + margin-bottom: 20px; + } + } + } +} + +.blog-list { + .blog-item { + display: block; + border-bottom: $base-border-gray; + padding-bottom: 18px; + + h2 { + margin-bottom: 6px; + font-size: 1.5rem; + } + + .blog-date { + text-transform: uppercase; + margin-bottom: 4px; + font-size: 0.875rem; + } + + .blog-author { + margin-bottom: 12px; + } + + a { + &:active, + &:focus, + &:hover { + text-decoration: none; + } + } + } +} + +.blog-list-nav { + margin-top: 18px; + + .blog-list-nav-item { + border-bottom: $base-border-gray; + padding-bottom: 10px; + margin-top: 14px; + + &:last-child { + border-bottom: none; + padding-bottom: 0; + } + + h4 { + font-family: $base-font-family; + } + + p { + margin-bottom: 10px; + font-size: $font-size-medium; + } + + a { + color: $headings-font-color; + + &:active, + &:focus, + &:hover { + text-decoration: none; + color: $brand-primary; + } + } + } +} + +.blog-detail-head { + border-bottom: $base-border-gray; + @include display(flex); + @include align-items(flex-start); + @include justify-content(space-between); + flex-wrap: wrap; + padding-bottom: 18px; + + div { + p:first-child { + text-transform: uppercase; + margin-bottom: 8px; + font-size: 0.875rem; + } + p { + margin-bottom: 0; + } + } +} diff --git a/_sass/layout/books.scss b/_sass/layout/books.scss new file mode 100755 index 0000000000..b107747692 --- /dev/null +++ b/_sass/layout/books.scss @@ -0,0 +1,89 @@ +// BOOKS +//------------------------------------------------ +//------------------------------------------------ + +.books { + h2 { + margin-bottom: 30px; + } +} + +.books-list { + @include clearfix; + + .book-item { + @include span-columns(4); + @include omega(3n); + margin-bottom: $padding-xlarge; + @include bp(medium) { + @include span-columns(12); + } + + .book-item-header { + .content-img-boook { + border-bottom: $base-border-gray; + height: 120px; + overflow: hidden; + + img { + height: 145px; + width: auto; + display: block; + margin-top: 10px; + transition: $base-transition; + border: { + left: $base-border-gray; + top: $base-border-gray; + right: $base-border-gray; + } + } + } + + h3 { + margin-bottom: 10px; + transition: $base-transition; + } + + a { + &:active, + &:focus, + &:hover { + text-decoration: none; + color: $brand-primary; + + img { + margin-top: 0; + } + + h3 { + color: $brand-primary; + } + } + } + } + + .book-item-main { + .author, + .published { + color: $base-font-color-light; + } + + .published { + font-style: italic; + } + + .description { + margin-top: 10px; + } + + ul { + padding-left: 18px; + + li { + list-style: disc; + margin-bottom: 3px; + } + } + } + } +} diff --git a/_sass/layout/cheatsheet.scss b/_sass/layout/cheatsheet.scss new file mode 100644 index 0000000000..691a34b38b --- /dev/null +++ b/_sass/layout/cheatsheet.scss @@ -0,0 +1,55 @@ +// CHEATSHEET +//------------------------------------------------ +//------------------------------------------------ + +.content-primary.cheatsheet { + code { + font-family: 'Consolas'; + } + + pre.highlight { + margin: 0; + code { + padding: 0; + background: #fff; + border: 0; + } + } + + .h2 { + display: block; + font-size: $font-size-h2; + font-weight: $font-black; + color: $gray-darker; + padding-top: 26px; + } + + h6 { + color: $base-font-color-light; + font-family: $base-font-family; + text-transform: uppercase; + font-size: $font-size-small; + } + + .label { + display: inline-block; + // position: absolute; + // right: 0; + position: relative; + // float: right; + top: 3px; + color: #fff; + text-transform: uppercase; + font-size: 11px; + font-weight: 700; + padding: 1px 5px; + } + + .important { + background: $brand-primary; + } + + .success { + background: $brand-secondary; + } +} diff --git a/_sass/layout/courses.scss b/_sass/layout/courses.scss new file mode 100755 index 0000000000..9533e336cc --- /dev/null +++ b/_sass/layout/courses.scss @@ -0,0 +1,31 @@ +// COURSES +//------------------------------------------------ +//------------------------------------------------ + +.courses { + background: $gray-li; + + .heading-line { + h2 { + + span { + background: $gray-li; + } + } + } + + .online-courses, + .upcoming-training { + @include span-columns(6); + + @include bp(large) { + @include span-columns(12); + } + } + + .online-courses { + @include bp(large) { + margin-bottom: 40px; + } + } +} diff --git a/_sass/layout/doc-navigation.scss b/_sass/layout/doc-navigation.scss new file mode 100644 index 0000000000..1d11f17010 --- /dev/null +++ b/_sass/layout/doc-navigation.scss @@ -0,0 +1,130 @@ +// DOC NAVIGATION +//------------------------------------------------ +//------------------------------------------------ + +$nav-height: 46px; + +.doc-navigation { + padding: 10px 20px; + @include display(flex); + @include flex-direction(row); + @include align-items(center); + @include justify-content(space-between); + .navigation-bdand { + img { + width: 58px; + height: 20px; + @include bp(large) { + display: none; + } + } + } + .navigation-ellipsis { + display: none; + font-size: 1.333rem; + cursor: pointer; + @include bp(medium) { + color: rgba(255, 255, 255, 0.5); + order: 3; + display: block; + + a { + &:active, + &:hover { + color: #ffffff; + } + } + } + } + + .navigation-menu { + .navigation-menu-item { + display: inline-block; + + .navigation-dropdown { + background: $gray; + min-width: 190px; + position: absolute; + margin-top: 10px; + display: none; + z-index: 1; + box-shadow: 0 3px 12px rgba(0, 0, 0, 0.15); + + @include bp(medium) { + order: 3; + } + + li { + line-height: $nav-height; + &:hover { + background: $gray-darker; + text-decoration: none; + a { + color: #ffffff; + &:hover { + text-decoration: none; + } + } + } + } + } + + @include bp(medium) { + @include after(3) { + display: none; + } + } + + &:last-child { + margin-right: 0; + } + + a { + display: block; + padding: 5px 15px; + color: rgba(255, 255, 255, 0.5); + font-weight: $font-bold; + + &:focus, + &.active { + color: #ffffff; + text-decoration: none; + } + + &:hover { + text-decoration: none; + } + + &:not(:only-child):after { + padding-left: 4px; + content: ' ▾'; + } // Dropdown list + } + } + } +} + +.navigation-submenu { + li { + text-align: center; + line-height: $nav-height; + + &:hover { + background: $gray-darker; + text-decoration: none; + a { + color: #ffffff; + } + } + + a { + padding-left: 20px; + display: block; + color: rgba(255, 255, 255, 0.5); + font-weight: $font-bold; + &:hover { + text-decoration: none; + } + } + } +} diff --git a/_sass/layout/documentation.scss b/_sass/layout/documentation.scss new file mode 100644 index 0000000000..9a04495b27 --- /dev/null +++ b/_sass/layout/documentation.scss @@ -0,0 +1,59 @@ +// BLOG +//------------------------------------------------ +//------------------------------------------------ + +.title-page { + h1 { + line-height: 1.875rem; + } + + .content-title-documentation { + .titles { + @include span-columns(9); + @include bp(medium) { + @include span-columns(12); + } + min-height: 70px; + max-height: 160px; + margin-top: 30px; + margin-bottom: 100px; + @include bp(medium) { + margin-bottom: 0; + } + + .supertitle { + text-transform: uppercase; + font-weight: $font-black; + font-size: 20px; + color: darken(#53b6d3, 15%); + } + + h1 { + padding-top: 0; + } + } + + .spacer { + height: 80px; + } + } +} + +.content-primary.documentation { + line-height: 1.3; + + ol { + margin-bottom: 18px; + } + + .tag { + float: right; + top: 3px; + background: $brand-primary; + color: #fff; + text-transform: uppercase; + font-size: 11px; + font-weight: $font-bold; + padding: 1px 5px; + } +} diff --git a/_sass/layout/download.scss b/_sass/layout/download.scss new file mode 100755 index 0000000000..cfc0838995 --- /dev/null +++ b/_sass/layout/download.scss @@ -0,0 +1,250 @@ +// DOWNLOAD +//------------------------------------------------ +//------------------------------------------------ + +.download { + @include clearfix; + position: relative; + + .content-ribbon { + position: absolute; + right: 30px; + top: -10px; + z-index: 50; + + .ribbon-version { + background: $brand-primary; + text-align: center; + padding: 8px 36px; + + span { + color: #fff; + font-size: 2.25rem; + } + } + + ul { + @include display(flex); + @include align-items(center); + @include justify-content(space-between); + margin-top: 8px; + + li { + display: inline-block; + font-size: $font-size-small; + + &:nth-child(2) { + font-size: 0.625rem; + } + } + } + } + + .main-download { + margin-top: 70px; + @include span-columns(8); + @include shift(2); + @include bp(large) { + @include span-columns(12); + @include shift(0); + } + + h2 { + margin-top: 0; + font-size: 1.75rem; + } + + .install-steps { + @include clearfix; + margin-top: 48px; + border-bottom: $base-border-gray; + padding-bottom: 60px; + + .step { + &:first-child { + margin-bottom: 100px; + @include bp(small) { + margin-bottom: 50px; + } + position: relative; + + img { + width: 12px; + height: 100px; + position: absolute; + left: 25px; + top: 50px; + } + } + @include clearfix; + + .number-step { + width: 64px; + height: 64px; + float: left; + background: $brand-primary; + border-radius: 100%; + color: #fff; + font-size: 1.75rem; + @include display(flex); + @include align-items(center); + @include justify-content(center); + } + + .text-step { + margin-left: 90px; + + h3 { + margin-bottom: 10px; + font-size: 1.25rem; + } + + p { + span { + font-style: italic; + color: $base-font-color-light; + } + } + } + } + + .download-options { + @include clearfix; + margin-top: 38px; + + .download-intellij, + .download-sbt { + position: relative; + @include span-columns(4 of 8); + @include bp(large) { + @include span-columns(8 of 8); + margin-bottom: 34px; + } + + .btn-download { + background: $brand-primary; + display: block; + text-align: center; + color: #fff; + text-transform: uppercase; + padding: 16px 0; + border-radius: 100px; + font-weight: $font-bold; + margin-bottom: 34px; + + &:active, + &:focus, + &:hover { + text-decoration: none; + background: darken($brand-primary, 10%); + } + + .fa { + margin-right: 10px; + font-size: 1.25rem; + } + } + + ul { + position: relative; + z-index: 50; + li { + &:first-child { + border-top: $base-border-gray; + } + border-bottom: $base-border-gray; + + a { + padding: 10px 16px; + display: block; + color: $base-font-color; + + &:active, + &:focus, + &:hover { + background: $gray-lighter; + text-decoration: none; + } + + .fa { + margin-right: 8px; + } + } + } + } + } + } + } + } + + .description { + position: absolute; + left: -130px; + top: 60px; + z-index: 40; + color: $brand-primary; + width: 280px; + font-family: 'Kalam', cursive; + + img { + width: 92px; + height: 165px; + margin-bottom: 24px; + } + @include bp(xlarge) { + display: none; + } + } + + .download-intellij { + padding-right: 10px; + position: relative; + .or { + font-size: 1.15rem; + position: absolute; + right: -22px; + top: 10px; + z-index: 100; + + @include bp(large) { + display: none; + } + } + } + + .download-sbt { + padding-left: 10px; + } + + .download-sbt { + .description { + top: 60px; + left: auto; + right: -120px; + text-align: right; + } + } + + .bottom-lead { + margin-top: 80px; + @include bp(large) { + margin-top: 20px; + } + } + + .other-ways-lead { + margin-top: 30px; + @include bp(large) { + margin-top: 20px; + } + margin-bottom: 50px; + @include bp(large) { + margin-bottom: 10px; + } + } + + .install { + font-size: 11px; + font-style: italic; + } +} diff --git a/_sass/layout/footer.scss b/_sass/layout/footer.scss new file mode 100755 index 0000000000..9b3d58fbfb --- /dev/null +++ b/_sass/layout/footer.scss @@ -0,0 +1,76 @@ +// FOOTER +//------------------------------------------------ +//------------------------------------------------ + +#site-footer { + padding: $padding-xlarge 0; + background: $gray-darker; + color: rgba(#fff, 0.5); + + ul { + @include span-columns(2); + @include bp(large) { + @include span-columns(4); + @include omega(3n); + margin-bottom: 20px; + } + + @include bp(small) { + @include span-columns(12); + } + + li { + margin-bottom: 3px; + h3 { + color: #fff; + margin-bottom: 10px; + text-transform: uppercase; + font-family: $base-font-family; + font-weight: $font-bold; + font-size: $font-size-large; + } + + a { + color: rgba(#fff, 0.5); + font-size: $font-size-medium; + + &:active, + &:focus, + &:hover { + color: #fff; + text-decoration: none; + } + } + } + } + + .site-footer-top { + @include clearfix; + margin-bottom: 40px; + } + + .site-footer-bottom { + @include display(flex); + @include flex-direction(row); + @include align-items(center); + @include justify-content(space-between); + + img { + width: 104px; + height: auto; + } + + @include bp(small) { + @include justify-content(flex-start); + flex-wrap: wrap; + img { + margin-top: 18px; + } + } + + img { + opacity: 0.4; + margin-right: 65px; + } + } +} diff --git a/_sass/layout/glossary.scss b/_sass/layout/glossary.scss new file mode 100644 index 0000000000..e55ef1c9aa --- /dev/null +++ b/_sass/layout/glossary.scss @@ -0,0 +1,75 @@ +// GLOSSARY +//------------------------------------------------ +//------------------------------------------------ + +.glossary { + + .filterbar { + position: relative; + margin-bottom: 24px; + + #filter-count { + visibility: hidden; + font-size: $font-size-xsmall; + text-transform: uppercase; + color: $base-font-color-light; + transition: $base-transition; + } + + .icon-search { + position: absolute; + left: 14px; + top: 4px; + z-index: 30; + } + + input { + padding: 8px 18px 8px 40px; + background: rgba($brand-tertiary, 0.1); + appearance: none; + font-size: $base-font-size; + border-radius: $border-radius-base; + width: 100%; + box-sizing: border-box; + + &:focus { + outline: none; + background: rgba($brand-tertiary, 0.07); + @include box-shadow($box-shadow-inner); + } + } + } + + h5 { + font-size: $font-size-medium !important; + color: $base-font-color-light !important; + } + + ul { + padding-left: 0 !important; + } + + ul li { + list-style-type: none !important; + padding-left: 0 !important; + } + + li { + h4 { + text-transform: none; + margin-bottom: 2px; + font-weight: $font-black; + } + } +} + +.glossary-toc { + #toc { + ul { + li { + // margin-bottom: 0; + margin-top: 2px; + } + } + } +} diff --git a/_sass/layout/header.scss b/_sass/layout/header.scss new file mode 100755 index 0000000000..ab392dfd77 --- /dev/null +++ b/_sass/layout/header.scss @@ -0,0 +1,111 @@ +// HEADER +//------------------------------------------------ +//------------------------------------------------ +//------------------------------------------------ + +#site-header { + background: $gray-darker; + + .new-on-the-blog { + background: rgba($gray-darker, 0.8); + text-align: center; + padding: 8px 0; + color: #fff; + transition: $base-transition; + position: relative; + + @include bp(medium) { + padding: 10px 40px; + } + + span { + position: absolute; + right: 20px; + top: 3px; + z-index: 1; + cursor: pointer; + font-size: 1.275rem; + opacity: 0.6; + transition: $base-transition; + &:hover { + opacity: 1; + } + } + a { + color: #fff; + text-decoration: underline; + &:active, + &:focus, + &:hover { + text-decoration: none; + } + } + } + &.header-home { + background: none; + .header-background { + background: rgba($gray-darker, 0.45); + position: relative; + padding-bottom: 150px; + + &:before { + content: ""; + position: absolute; + background: url("../img/frontpage/background-header-home.jpg") no-repeat center center; + @include image-size(); + left: 0; + width: 100%; + height: 100%; + z-index: -1; + } + } + } +} + +#doc-header { + background: $gray; + + // .new-on-the-blog { + // background: rgba($gray-darker, 0.8); + // text-align: center; + // padding: 8px 0; + // color: #fff; + // transition: $base-transition; + // position: relative; + // + // @include bp(medium) { + // padding: 10px 40px; + // } + // + // span { + // position: absolute; + // right: 20px; + // top: 3px; + // z-index: 1; + // cursor: pointer; + // font-size: 1.275rem; + // opacity: 0.6; + // transition: $base-transition; + // &:hover { + // opacity: 1; + // } + // } + // a { + // color: #fff; + // text-decoration: underline; + // &:active, + // &:focus, + // &:hover { + // text-decoration: none; + // } + // } + // } + &.header-home { + background: none; + .header-background { + background: rgba($gray-darker, 0.45); + position: relative; + padding-bottom: 150px; + } + } +} diff --git a/_sass/layout/ides.scss b/_sass/layout/ides.scss new file mode 100755 index 0000000000..fe0c99fdea --- /dev/null +++ b/_sass/layout/ides.scss @@ -0,0 +1,81 @@ +// IDES +//------------------------------------------------ +//------------------------------------------------ + +.ides { + background: $gray-dark; + + ul { + @include display(flex); + @include align-items(top); + @include justify-content(space-around); + + li { + text-align: center; + position: relative; + + &:nth-child(2n) { + width: 1px; + height: 94px; + background: $base-border-color-white; + } + + a { + display: inline-block; + .bullet { + position: absolute; + top: -12px; + right: -14px; + background: $gray; + border-radius: 100%; + width: 24px; + height: 24px; + z-index: 10; + transition: $base-transition; + text-align: center; + + img { + width: 16px; + height: 16px; + margin-top: 4px; + } + } + + &.sublime { + .bullet { + top: -10px; + right: 0; + } + } + + color: rgba(#fff, 0.5); + font-family: $heading-font-family; + img { + height: 56px; + width: auto; + margin-bottom: 6px; + opacity: 0.4; + transition: $base-transition; + } + + span { + display: block; + font-size: $font-size-small; + } + + + &:hover { + img { + opacity: 1; + } + .bullet { + background: $brand-secondary; + } + + color: #fff; + text-decoration: none; + } + } + } + } +} diff --git a/_sass/layout/inner-main.scss b/_sass/layout/inner-main.scss new file mode 100755 index 0000000000..5361b48902 --- /dev/null +++ b/_sass/layout/inner-main.scss @@ -0,0 +1,178 @@ +// INNER MAIN +//------------------------------------------------ +//------------------------------------------------ + +#inner-main { + background: $gray-lighter; + padding-bottom: $padding-xlarge; + + + section:nth-child(2) { + position: relative; + top: -80px; + } + + .inner-box { + padding: $padding-medium; + background: #fff; + @include border-radius($border-radius-base); + @include box-shadow($box-shadow-inner); + } + + .content { + .wrap { + @include display(flex); + flex-wrap: wrap; + } + + .content-primary, + .content-primary-blog { + @include span-columns(8); + @include bp(large) { + @include fill-parent; + order: 2; + margin-right: 0; + } + } + + .content-nav, + .content-nav-blog { + @include span-columns(4); + @include bp(large) { + @include fill-parent; + order: 1; + margin-bottom: 30px; + } + } + + .content-nav-blog { + @include bp(large) { + display: none; + } + } + + .content-primary { + .documentation, + .tools { + .doc-item, + .tool-item { + margin-bottom: 0; + transition: $base-transition; + @include span-columns(6); + @include omega(2n); + @include bp(large) { + @include fill-parent; + } + + a { + &:active, + &:focus, + &:hover { + h4 { + color: $brand-primary; + } + } + } + + &:nth-child(2n) { + clear: none; + } + + &:active, + &:focus, + &:hover { + text-decoration: none; + background: none; + } + } + } + } + + .content-nav { + .inner-box { + .inner-toc { + & > ul { + & > li { + margin-top: 10px; + line-height: 1.2; + + .active { + font-weight: $font-black; + color: $hover-link-color; + } + + & > a { + color: $gray-dark; + + &:active, + &:focus, + &:hover { + color: $hover-link-color; + text-decoration: none; + } + } + + & > ul { + margin: 5px 0; + padding-left: 14px; + color: rgba($base-font-color-light, 0.7); + border-left: $base-border-gray; + + li { + font-size: $font-size-medium; + margin-bottom: -2px; + + ul { + li { + font-size: $font-size-small; + + &:before { + color: rgba($base-font-color-light, 0.5); + padding-left: 0; + margin-right: 6px; + content: "\2192"; + } + + a { + font-style: italic; + } + } + } + } + } + } + } + } + + hr { + border: none; + height: 1px; + width: 60px; + background: $base-border-color-gray; + margin: 18px 0; + } + + .help-us { + line-height: 1.1; + + a { + color: $gray-li; + font-style: italic; + font-size: $font-size-xsmall; + + &:active, + &:focus, + &:hover { + text-decoration: none; + background: rgba($brand-tertiary, 0.15); + } + + br { + height: 5px; + } + } + } + } + } + } +} diff --git a/_sass/layout/inner-text.scss b/_sass/layout/inner-text.scss new file mode 100755 index 0000000000..ef9274e8b5 --- /dev/null +++ b/_sass/layout/inner-text.scss @@ -0,0 +1,28 @@ +// INNER TEXT +//------------------------------------------------ +//------------------------------------------------ +.inner-text { + position: relative; + h1 { + font-family: $base-font-family; + font-size: 2.813rem; + font-weight: $font-black; + color: #fff; + margin: $padding-medium 0 ($padding-medium / 2) 0; + } + + p { + font-size: 1.25rem; + color: #fff; + margin-bottom: $padding-medium; + } + @include span-columns(8); + @include bp(large) { + @include span-columns(12); + + h1 { + font-size: 2.213rem; + margin: 0 0 ($padding-medium / 2) 0; + } + } +} diff --git a/_sass/layout/maintenance.scss b/_sass/layout/maintenance.scss new file mode 100755 index 0000000000..12d961d9e5 --- /dev/null +++ b/_sass/layout/maintenance.scss @@ -0,0 +1,89 @@ +// MAINTENANCE +//------------------------------------------------ +//------------------------------------------------ + +.maintenance { + background: $gray-dark; + + .heading-line { + h2 { + span { + background: $gray-dark; + } + } + } + + h3 { + color: #fff; + text-align: center; + font-size: 0.9375rem; + } + + .maintained { + @include display(flex); + @include align-items(center); + @include justify-content(center); + flex-wrap: wrap; + margin-bottom: 40px; + + li { + &:first-child { + margin-right: 32px; + } + + a { + img { + height: 43px; + width: auto; + opacity: 0.3; + transition: $base-transition; + } + + &:hover { + img { + opacity: 1; + } + } + } + } + @include bp(small) { + li:first-child { + margin-right: 0; + } + } + } + + .supported { + padding-top: 30px; + @include display(flex); + @include flex-direction(row); + @include align-items(center); + @include justify-content(space-between); + flex-wrap: wrap; + + a { + + img { + height: 42px; + width: auto; + display: block; + opacity: 0.3; + transition: $base-transition; + + @include bp(large) { + margin: 5px; + } + + @include bp(medium) { + margin: 10px; + } + + } + &:hover { + img { + opacity: 1; + } + } + } + } +} diff --git a/_sass/layout/marker.scss b/_sass/layout/marker.scss new file mode 100755 index 0000000000..769d78ef55 --- /dev/null +++ b/_sass/layout/marker.scss @@ -0,0 +1,64 @@ +// MARKER +//------------------------------------------------ +//------------------------------------------------ + +.marker { + position: absolute; + width: 16px; + height: 8px; + background: #A4302E; + left: 0px; + top: 0px; + border-radius: 100%; + cursor: pointer; + + @include bp(xxlarge) { + display: none; + } + + &:hover { + .info-marker, + .arrow { + visibility: visible; + opacity: 1; + transition: $base-transition; + } + } + + &:before { + content: ""; + position: absolute; + background: rgba(#A4302E, 0.3); + left: -8px; + top: -4px; + width: 32px; + height: 16px; + border-radius: 100%; + z-index: 1; + } + + .info-marker { + width: 472px; + position: absolute; + left: -13px; + top: 30px; + font-size: $font-size-small; + color: #fff; + font-family: $heading-font-family; + background: rgba($gray-dark, 0.8); + padding: 20px; + visibility: hidden; + opacity: 0; + + + .arrow { + position: absolute; + left: 0; + top: -16px; + width: 23px; + height: 13px; + z-index: 1; + } + } + +} diff --git a/_sass/layout/navigation.scss b/_sass/layout/navigation.scss new file mode 100755 index 0000000000..90960f33cf --- /dev/null +++ b/_sass/layout/navigation.scss @@ -0,0 +1,94 @@ +// NAVIGATION +//------------------------------------------------ +//------------------------------------------------ + +.navigation { + padding: $padding-medium 0; + @include display(flex); + @include flex-direction(row); + @include align-items(center); + @include justify-content(space-between); + .navigation-bdand { + img { + width: 104px; + height: 43px; + } + } + .navigation-panel-button { + display: none; + font-size: 1.333rem; + color: #fff; + cursor: pointer; + @include bp(large) { + order: 3; + display: block; + } + } + + .navigation-menu { + .navigation-menu-item { + display: inline-block; + + &:last-child { + margin-right: 0; + } + + a { + padding: 5px 15px; + text-transform: uppercase; + color: #fff; + border-radius: 300px; + font-weight: $font-bold; + + &:active, + &:focus, + &:hover, + &.active { + background: $brand-primary; + text-decoration: none; + } + } + } + } +} +@include bp(large) { + .navigation { + .navigation-menu { + padding: 20px; + $sliding-panel-width: 270px; + @include position(fixed, 0 0 0 auto); + @include size($sliding-panel-width 100%); + @include transform(translateX(+ $sliding-panel-width)); + @include transition(all 0.25s linear); + background: #fff; + -webkit-overflow-scrolling: touch; + overflow-y: auto; + z-index: 100; + background: rgba($gray-darker, 0.99); + + &.is-visible { + @include transform(translateX(0)); + } + + .navigation-menu-item { + margin-right: 16px; + padding: 10px 0; + display: block; + } + } + } +} + +.navigation-fade-screen { + @include position(fixed, 0 0 0 0); + @include transition; + background: #000; + opacity: 0; + visibility: hidden; + z-index: 90; + + &.is-visible { + opacity: 0.6; + visibility: visible; + } +} diff --git a/_sass/layout/new-blog.scss b/_sass/layout/new-blog.scss new file mode 100755 index 0000000000..2305e669d8 --- /dev/null +++ b/_sass/layout/new-blog.scss @@ -0,0 +1,154 @@ +// NEW BLOG +//------------------------------------------------ +//------------------------------------------------ + +.new-blog { + background: $gray-lighter; + + .heading-line { + h2 { + color: $gray-dark; + + span { + background: $gray-lighter; + } + + &:before { + background: $base-border-color-gray; + } + } + } + + .new, + .recently { + @include span-columns(6); + @include bp(large) { + @include span-columns(12); + } + + h3 { + font-family: $base-font-family; + text-transform: uppercase; + border-bottom: $base-border-gray; + font-size: $font-size-large; + } + + .content-card { + background: #fff; + padding: 22px; + display: block; + border-radius: $border-radius-base; + + } + } + + .new { + @include bp(large) { + margin-bottom: 40px; + } + + .content-card { + height: 516px; + overflow: hidden; + position: relative; + + &:before { + content: ""; + position: absolute; + background: #fff; + left: 0; + bottom: 0; + width: 100%; + height: 20px;; + z-index: 1; + } + } + .tag-new { + text-transform: uppercase; + font-size: $font-size-medium; + color: $brand-primary; + font-weight: $font-bold; + } + + h3 { + font-size: 1.5rem; + color: $gray-dark; + padding-bottom: 15px; + margin-bottom: 15px; + + a { + color: $gray-dark; + + &:active, + &:focus, + &:hover { + text-decoration: none; + color: $brand-primary; + } + } + } + + .date { + color: $base-font-color-light; + display: block; + margin-bottom: 5px; + font-style: italic; + } + } + + .recently { + a { + margin-bottom: 16px; + + h3 { + color: $gray-dark; + transition: $base-transition; + padding-bottom: 8px; + margin-bottom: 6px; + } + + ul { + position: relative; + margin-bottom: 4px; + li { + color: $base-font-color-light; + font-size: $font-size-small; + display: inline-block; + + &.dot { + font-size: 10px; + } + + &.tag { + position: absolute; + right: 0; + top: 3px; + background: $brand-primary; + color: #fff; + text-transform: uppercase; + font-size: 11px; + font-weight: $font-bold; + padding: 1px 5px; + } + } + + + } + + p { + color: $base-font-color; + } + + &:active, + &:focus, + &:hover { + text-decoration: none; + box-shadow: $box-shadow-item; + h3 { + color: #765; + color: $brand-primary; + } + } + } + } +} diff --git a/_sass/layout/nutshell.scss b/_sass/layout/nutshell.scss new file mode 100755 index 0000000000..64dec85d01 --- /dev/null +++ b/_sass/layout/nutshell.scss @@ -0,0 +1,111 @@ +// NUTSHELL +//------------------------------------------------ +//------------------------------------------------ + +.nutshell { + background: $gray; + + .heading-line { + h2 { + span { + background: $gray; + } + } + } + + .scala-items-list { + .items-menu { + .scala-item { + @include span-columns(4); + @include omega(3n); + padding: $padding-small; + text-align: center; + transition: $base-transition; + @include bp(medium) { + @include span-columns(12); + } + + min-height: 165px; + + h3 { + color: #fff; + font-size: $font-size-h3; + text-transform: uppercase; + font-family: $base-font-family; + margin-bottom: 10px; + } + + p { + color: $base-font-color-inverse; + font-size: $font-size-large; + } + + + &:active, + &:focus, + &:hover { + cursor: pointer; + background: $gray-dark; + } + + &.active { + background: $gray-dark; + } + } + + .items-content { + background: $gray-dark; + transition: $base-transition; + + .items-code { + display: none; + background: $gray-dark; + padding: 65px 0; + + .scala-code { + + @include span-columns(6); + @include bp(large) { + @include span-columns(12); + } + } + + .scala-text { + code { + background: $gray-darker; + padding: 2px 8px; + color: #859900; + border-radius: 2px; + margin: 0 3px; + } + @include span-columns(6); + @include bp(large) { + @include span-columns(12); + } + + h3 { + font-size: 1.625rem; + color: #fff; + margin-bottom: 20px; + } + + p { + color: $base-font-color-inverse; + } + + &.scala-text-large { + @include span-columns(12); + margin-bottom: 30px; + } + } + } + } + } + } + + .scala-item-expanded { + display: none; + height: 400px; + background: $gray-dark; + } +} diff --git a/_sass/layout/overviews.scss b/_sass/layout/overviews.scss new file mode 100644 index 0000000000..930502116b --- /dev/null +++ b/_sass/layout/overviews.scss @@ -0,0 +1,184 @@ +// OVERVIEWS INDEX PAGE +//------------------------------------------------ +//------------------------------------------------ + +.overviews { + .inner-box { + background: #fafafa !important; + } +} + +.two-columns { + @include display(flex); + @include flex-direction(row); + @include flex-wrap(wrap); + @include justify-content(space-between); + + .first, + .second { + flex: 0 1 calc(50% - 1em); + @include bp(medium) { + flex: 0 1 calc(100% - 0.5em); + } + } +} + +.card-group { + // @include span-columns(6); + // @include bp(large) { + // @include span-columns(12); + // } + @include display(flex); + @include flex-direction(row); + @include flex-wrap(wrap); + @include justify-content(space-between); + + // @include align-items(center); + + h3 { + font-family: $base-font-family; + } + + + .white-card { + display: flex; + flex-direction: column; + // justify-content: flex-end; + position: relative; + flex: 0 1 calc(50% - 1em); + margin-bottom: 2em; + transition: max-height .3s ease-out; + text-decoration: none; + border-radius: 2px; + box-shadow: 0 1px 3px 0 rgba(0,0,0,.2), 0 1px 1px 0 rgba(0,0,0,.14), 0 2px 1px -1px rgba(0,0,0,.12); + background: #fff; + max-height: 400px; + overflow: hidden; + + @include bp(medium) { + flex: 0 1 calc(100% - 0.5em); + } + + .card-wrap { + // align-self: stretch; + position: relative; + // padding: 22px; + + .card-header { + display: flex; + padding: 16px; + + .text { + display: flex; + } + } + + .card-content { + padding: 20px; + padding-top: 0px; + padding-bottom: 0px; + } + + h2 { + margin-bottom: 0px; + } + + .card-avatar { + width: 40px; + height: 40px; + margin-right: 12px; + + .icon { + font-size: 26px; + color: #fff; + border-radius: 50%; + margin: auto; + text-align: center; + display: inline-block; + vertical-align: middle; + background-color: $brand-primary; + height: 40px; + width: 40px; + min-height: 40px; + min-width: 40px; + } + } + + .by { + font-weight: $font-regular; + color: $base-font-color-light; + font-size: $font-size-small; + padding-bottom: 10px; + line-height: 1.4; + } + + .tag { + // position: absolute; + // right: 0; + position: relative; + float: right; + top: 3px; + background: #DC322F; + color: #fff; + text-transform: uppercase; + font-size: 11px; + font-weight: 700; + padding: 1px 5px; + } + + a { + h3 { + display: inline-block; + padding: 0; + margin: 0; + color: $gray-dark; + transition: $base-transition; + font-weight: $font-black; + font-size: 26px; + line-height: 1.15; + + &:hover { + text-decoration: none; + color: $brand-primary; + } + } + &:hover { + text-decoration: none; + } + } + + ul { + li { + margin-bottom: 0px; + } + } + } + + } + + .card-footer { + position: absolute; + bottom: 0; + width: 100%; + padding: 20px; + height: 66px; + background: #fff; + border-top: 1px solid lighten($base-font-color-light, 35%); + + &:hover { + cursor: pointer; + background: $brand-primary; + .expand-btn, + .go-btn { + color: #fff; + } + } + + .expand-btn, + .go-btn { + color: lighten($base-font-color-light, 15%); + font-weight: $font-regular; + font-size: $font-regular; + } + } +} diff --git a/_sass/layout/run-scala.scss b/_sass/layout/run-scala.scss new file mode 100755 index 0000000000..79eac19a5f --- /dev/null +++ b/_sass/layout/run-scala.scss @@ -0,0 +1,57 @@ +// RUN SCALA +//------------------------------------------------ +//------------------------------------------------ + +#site-main { + .run-scala { + background: $gray-dark; + padding-bottom: 0; + overflow: hidden; + + .code-element { + margin-bottom: -1px; + margin-top: 48px; + position: relative; + + textarea { + width: 100%; + background: $gray-darker; + height: 400px; + overflow: hidden; + + &:focus { + outline: none; + } + } + + .btn-run { + position: absolute; + right: 20px; + bottom: 20px; + background: rgba(#fff, 0.1); + padding: 3px 14px; + color: #fff; + font-size: $font-size-small; + cursor: pointer; + transition: $base-transition; + @include user-select(none); + + + + &:hover { + background: rgba(#fff, 0.2); + } + + &.inactive { + color: rgba(#fff, 0.5); + background: rgba(#fff, 0.05); + pointer-events: none; + } + + i { + margin-right: 10px; + } + } + } + } +} diff --git a/_sass/layout/runs.scss b/_sass/layout/runs.scss new file mode 100755 index 0000000000..172073a790 --- /dev/null +++ b/_sass/layout/runs.scss @@ -0,0 +1,71 @@ +// RUNS +//------------------------------------------------ +//------------------------------------------------ + +#site-main { + .runs { + padding: 30px 0; + background: $gray; + + h2 { + color: #fff; + text-align: center; + font-size: $font-size-large; + } + + ul { + @include display(flex); + @include align-items(center); + @include justify-content(center); + margin-top: 30px; + li:nth-child(2) { + height: 100px; + width: 1px; + background: $base-border-color-white; + margin-left: 36px; + margin-right: 36px; + } + li:nth-child(1), + li:nth-child(3) { + + @include border-radius(100%); + @include display(flex); + @include align-items(center); + @include justify-content(center); + + + + span { + + @include border-radius(100%); + @include display(flex); + @include align-items(center); + @include justify-content(center); + + img { + height: 56px; + width: 56px; + opacity: 0.3; + transition: $base-transition; + } + } + + &:hover { + span { + img { + opacity: 1; + } + } + } + } + } + + p { + text-align: center; + color: rgba(#fff, 0.6); + margin-top: 28px; + text-transform: uppercase; + font-size: $font-size-small; + } + } +} diff --git a/_sass/layout/scala-ecosystem.scss b/_sass/layout/scala-ecosystem.scss new file mode 100755 index 0000000000..1fb9925e16 --- /dev/null +++ b/_sass/layout/scala-ecosystem.scss @@ -0,0 +1,125 @@ +// SCALA ECOSISTEM +//------------------------------------------------ +//------------------------------------------------ + +#site-main { + .scala-ecosystem { + padding-bottom: 0; + background: url("../img/frontpage/background-scala-ecosystem.png") no-repeat center bottom $gray-li; + @include image-size(); + + .heading-line { + @include clearfix; + + h2 { + span { + background: #234D57; + } + } + } + + .browser { + .header-browser { + background: $gray-dark; + padding: 14px 20px; + @include display(flex); + @include align-items(center); + @include justify-content(space-between); + + img { + width: 116px; + height: auto; + } + + img:last-child { + width: 86px; + height: auto; + } + } + + .main-browser { + background: rgba($gray, 0.5); + transition: $base-transition; + text-align: center; + padding: 70px 0 80px; + + h2 { + color: #fff; + font-size: 2.5rem; + margin-bottom: 24px; + } + + .input-control { + position: relative; + background: #333; + width: 550px; + margin-left: auto; + margin-right: auto; + color: $base-font-color-light; + + span { + position: absolute; + left: 20px; + top: 8px; + } + + input { + padding: 12px 18px 12px 50px; + border-radius: $border-radius-small; + width: 100%; + font-weight: $font-bold; + } + } + @include bp(medium) { + padding-left: 20px; + padding-right: 20px; + + h2 { + font-size: 1.4rem; + } + + .input-control { + width: 100%; + } + } + } + } + + &:hover { + .main-browser { + background: $gray; + padding-bottom: 140px; + } + } + } + + .autocomplete-suggestions { + border: 1px solid $gray-light; + background: #FFF; + overflow: auto; + } + + .autocomplete-suggestion { + padding: 2px 5px; + white-space: nowrap; + overflow: hidden; + } + + .autocomplete-selected { + background: $gray-lighter; + } + + .autocomplete-suggestions strong { + font-weight: normal; + color: $gray-dark; + } + + .autocomplete-group { + padding: 2px 5px; + } + + .autocomplete-group strong { + display: block; + border-bottom: 1px solid $gray-light; + } +} diff --git a/_sass/layout/scala-main-resources.scss b/_sass/layout/scala-main-resources.scss new file mode 100755 index 0000000000..29a1ae5755 --- /dev/null +++ b/_sass/layout/scala-main-resources.scss @@ -0,0 +1,181 @@ +// SCALA MAIN RESOURCES +//------------------------------------------------ +//------------------------------------------------ + +.scala-main-resources { + height: 180px; + background: $gray-darker; + position: relative; + + .resources { + .button { + font-size: $font-size-large; + display: block; + @include border-top-radius(200px); + @include border-right-radius(0); + @include border-bottom-radius(0); + @include border-left-radius(200px); + padding: $padding-small $padding-large; + } + + .download { + @include span-columns(4); + @include shift(2); + } + + .api-docs { + @include span-columns(4); + + .button { + text-align: right; + @include border-top-radius(0); + @include border-right-radius(200px); + @include border-bottom-radius(200px); + @include border-left-radius(0); + } + } + @include bp(large) { + .download { + @include span-columns(6); + @include shift(0); + } + + .api-docs { + @include span-columns(6); + margin-right: 0; + } + } + + .api-docs, + .download { + margin-top: -40px; + + ul { + margin-top: 12px; + max-width: 180px; + text-align: center; + + li { + &:first-child { + border-bottom: $base-border-white; + font-family: $heading-font-family; + padding-bottom: 4px; + margin-bottom: 2px; + font-weight: $font-bold; + + a { + font-size: $font-size-large; + } + } + + a { + color: rgba(#fff, 0.90); + font-size: $font-size-medium; + + &:active, + &:focus, + &:hover { + text-decoration: none; + color: rgba(#fff, 0.50); + } + } + } + } + } + + .api-docs { + ul { + float: right; + } + } + + .scala-brand-circle { + width: 340px; + height: 340px; + left: 50%; + top: -178px; + margin-left: -170px; + background: rgba($gray-darker, 0.4); + border-radius: 100%; + position: absolute; + z-index: 60; + @include display(flex); + @include align-items(center); + @include justify-content(center); + + .circle-solid { + background: $gray-darker; + width: 224px; + height: 224px; + border-radius: 100%; + text-align: center; + + > img { + width: 152px; + height: auto; + margin-top: -28px; + } + + .scala-version { + span { + display: block; + color: #fff; + font-family: $heading-font-family; + } + + span:first-child { + font-size: 1.375rem; + margin-top: -9px; + } + + span:nth-child(2) { + font-size: 1.9rem; + margin-top: -10px; + } + p { + color: rgba(#fff, 0.50); + font-style: italic; + font-size: $font-size-medium; + line-height: 1.3; + margin-top: 10px; + } + } + } + } + } + @include bp(medium) { + height: auto; + padding-bottom: $padding-xlarge; + + .resources { + .download { + margin-top: 180px; + } + + .api-docs { + margin-top: 40px; + } + + .api-docs, + .download { + @include span-columns(12); + + .button { + border-radius: 100px; + padding: ($padding-small / 2) ($padding-large / 2); + font-size: $base-font-size; + text-align: center; + } + + ul { + max-width: 100%; + float: none; + } + } + + .scala-brand-circle { + transform: scale(0.8); + } + } + } +} diff --git a/_sass/layout/scaladex.scss b/_sass/layout/scaladex.scss new file mode 100755 index 0000000000..7ea6115783 --- /dev/null +++ b/_sass/layout/scaladex.scss @@ -0,0 +1,10 @@ +// SCALADEX +//------------------------------------------------ +//------------------------------------------------ + +.autocomplete-suggestions { border: 1px solid #999; background: #FFF; overflow: auto; } +.autocomplete-suggestion { padding: 2px 5px; white-space: nowrap; overflow: hidden; } +.autocomplete-selected { background: #F0F0F0; } +.autocomplete-suggestions strong { font-weight: normal; color: #3399FF; } +.autocomplete-group { padding: 2px 5px; } +.autocomplete-group strong { display: block; border-bottom: 1px solid #000; } diff --git a/_sass/layout/sips.scss b/_sass/layout/sips.scss new file mode 100644 index 0000000000..12e351ee5c --- /dev/null +++ b/_sass/layout/sips.scss @@ -0,0 +1,201 @@ +// SIPS INDEX PAGE AND INDIVIDUAL SIP PAGES +//------------------------------------------------ +//------------------------------------------------ + +.sip-toc { + .tag { + // position: absolute; + // right: 0; + position: relative; + display: inline-block; + top: 3px; + color: #fff; + text-transform: uppercase; + font-size: 11px; + font-weight: 700; + padding: 1px 5px; + margin-bottom: 8px; + } + + .vote-text { + line-height: 1.2; + margin-bottom: 24px; + font-style: italic; + } + + ul { + margin-bottom: 18px; + li { + margin-top: 6px; + a { + color: $headings-font-color; + // font-weight: $font-black; + &:hover { + color: $hover-link-color; + text-decoration: none; + } + } + } + } + #sip-meetings { + margin-bottom: 20px; + } + + #live-on-youtube { + margin-top: 4px; + font-style: italic; + line-height: 1.1; + color: $headings-font-color; + font-size: $font-size-small; + + #watch-meeting { + display: inline-block; + margin-top: 6px; + color: $brand-primary; + } + } + + #meeting-minutes { + a { + color: $headings-font-color; + // font-weight: $font-black; + &:hover { + color: $hover-link-color; + text-decoration: none; + } + } + } + + #meeting-time { + font-size: $font-size-xsmall; + text-transform: uppercase; + font-weight: $font-black; + color: $base-font-color-light; + margin-top: 0px; + } + + #meeting-title { + a { + color: $headings-font-color; + font-weight: $font-black; + &:hover { + color: $hover-link-color; + text-decoration: none; + } + } + } + + #upcoming-meeting { + margin-top: 10px; + padding: 8px; + padding-left: 10px; + padding-bottom: 14px; + background-color: rgba($brand-primary, 0.1); + + h6 { + font-family: $heading-font-family; + color: $brand-primary; + font-size: $font-size-large; + font-weight: $font-regular; + } + } +} + +.sips { + + .pending, + .completed, + .rejected { + .date { + display: block; + font-size: $font-size-small; + text-transform: uppercase; + font-weight: $font-bold; + color: $base-font-color-light; + margin-top: 4px; + } + + strong { + font-weight: $font-black; + } + + .tag { + float: left; + position: relative; + display: block; + top: 3px; + color: #fff; + text-transform: uppercase; + font-size: 11px; + font-weight: 700; + padding: 1px 5px; + } + } + + .pending { + display: flex; + // flex-direction: column; + // flex-wrap: wrap; + // max-height: 400px; + ul { + list-style: inside disc; + -webkit-column-count: 2; /* Chrome, Safari, Opera */ + -moz-column-count: 2; /* Firefox */ + column-count: 2; + padding-left: 0px; + + li { + margin-left: 1em; + padding-bottom: 24px; + line-height: 1; + -webkit-column-break-inside: avoid; + page-break-inside: avoid; + break-inside: avoid; + } + + a { + color: $gray-darker; + &:hover { + color: $brand-primary; + } + } + } + } + + .other-sips { + @include display(flex); + @include flex-direction(row); + @include flex-wrap(wrap); + @include justify-content(space-between); + + .completed, + .rejected { + display: flex; + flex-direction: column; + // justify-content: flex-end; + position: relative; + flex: 0 1 calc(50% - 1em); + + @include bp(medium) { + flex: 0 1 calc(100% - 0.5em); + } + + ul { + // padding-left: 0px; + + li { + // margin-left: 1em; + padding-bottom: 24px; + line-height: 1; + } + + a { + color: $gray-darker; + &:hover { + color: $brand-primary; + } + } + } + } + } +} diff --git a/_sass/layout/site-main.scss b/_sass/layout/site-main.scss new file mode 100755 index 0000000000..095e749ae0 --- /dev/null +++ b/_sass/layout/site-main.scss @@ -0,0 +1,26 @@ +// MAIN +//------------------------------------------------ +//------------------------------------------------ +#site-main { + section { + padding: $padding-xlarge 0; + } + + .spire { + min-height: 330px; + background: rgba($gray-darker, 0.4); + position: relative; + padding: 0; + + &:before { + content: ""; + position: absolute; + background: url("../img/frontpage/epfl-bc.jpg") no-repeat center center; + @include image-size(); + left: 0; + width: 100%; + height: 100%; + z-index: -1; + } + } +} diff --git a/_sass/layout/style-guide.scss b/_sass/layout/style-guide.scss new file mode 100644 index 0000000000..9621d3d267 --- /dev/null +++ b/_sass/layout/style-guide.scss @@ -0,0 +1,23 @@ +// STYLE GUIDE +//------------------------------------------------ +//------------------------------------------------ + +.style-guide { + ul { + li { + font-weight: $font-black; + ul { + margin-top: 6px !important; + margin-bottom: 0; + li { + font-weight: $font-regular; + margin-bottom: 0 !important; + ul { + margin-top: 0 !important; + margin-bottom: 4px; + } + } + } + } + } +} diff --git a/_sass/layout/table-of-content.scss b/_sass/layout/table-of-content.scss new file mode 100755 index 0000000000..5dca9f7945 --- /dev/null +++ b/_sass/layout/table-of-content.scss @@ -0,0 +1,199 @@ +// DOCUMENTATION +//------------------------------------------------ +//------------------------------------------------ + +#inner-main { + .table-of-content { + margin-bottom: $padding-medium; + + .inner-box { + padding-bottom: 0; + } + + } +} + +.documentation { + @include clearfix; + + + h2.frontpage { + color: $brand-primary; + margin-top: 8px; + margin-bottom: 24px; + } + + + .section, + .more-resources { + @include clearfix; + } + + .more-resources { + color: $headings-font-color; + line-height: 1.2; + margin-bottom: 24px; + text-align: center; + .heading { + font-weight: $font-bold; + } + ul { + li { + display: inline; + padding-left: 10px; + padding-right: 10px; + } + li+li { border-left: 1px solid $base-font-color } + } + } + + + .doc-item { + @include span-columns(4); + @include omega; + // margin-bottom: $padding-medium; + min-height: 120px; + padding: 15px; + @include bp(large) { + @include span-columns(12); + min-height: auto; + } + + .doc-item-header { + @include display(flex); + @include align-items(center); + @include justify-content(flex-start); + margin-bottom: 10px; + + .fa { + font-size: 1.563rem; + margin-right: 14px; + color: $brand-primary; + } + + h4 { + color: $headings-font-color; + margin-bottom: 0; + transition: $base-transition; + } + + a { + &:active, + &:focus, + &:hover { + text-decoration: none; + } + } + } + + .doc-item-main { + p { + color: $base-font-color; + } + } + + &:active, + &:focus, + &:hover { + text-decoration: none; + background: $gray-lighter; + } + } + + .nth-doc-item { + @include omega(3n); + } +} + +.community { + @include clearfix; + padding-bottom: $padding-small; + .discourse, + .gitter { + h3 { + margin-top: 0; + } + @include span-columns(6); + + @include bp(medium) { + @include span-columns(12); + } + + span { + border-bottom: $base-border-gray; + display: block; + color: $base-font-color-light; + font-style: italic; + padding-bottom: 10px; + margin-bottom: 30px; + } + + img { + width: 28px; + height: 28px; + } + + a { + &:active, + &:focus, + &:hover { + text-decoration: none; + color: $brand-primary; + + h4 { + color: $brand-primary; + } + + } + } + } + .discourse { + ul { + li { + @include display(flex); + @include align-items(top); + @include justify-content(flex-start); + + img { + margin-right: 15px; + } + + h4 { + margin-bottom: 8px; + + } + + margin-bottom: $padding-large; + } + } + } + + .gitter { + ul { + li { + @include span-columns(3 of 6); + @include omega(2n); + margin-bottom: 20px; + + @include bp(small) { + @include span-columns(12); + } + + a { + @include display(flex); + @include align-items(center); + @include justify-content(flex-start); + + img { + margin-right: 10px; + } + + h4 { + text-transform: none; + } + } + + } + } + } +} diff --git a/_sass/layout/talk-to-us.scss b/_sass/layout/talk-to-us.scss new file mode 100755 index 0000000000..ca83f9676e --- /dev/null +++ b/_sass/layout/talk-to-us.scss @@ -0,0 +1,197 @@ +// TLAK TO US +//------------------------------------------------ +//------------------------------------------------ + +.talk-to-us { + .heading-line { + h2 { + color: $gray-dark; + + span { + background: #fff; + } + + &:before { + background: $base-border-color-gray; + } + } + } + + h3 { + text-align: center; + color: $gray-dark; + font-size: $font-size-medium; + margin-bottom: 40px; + } + + .discourse, + .gitter { + margin-bottom: 50px; + @include clearfix; + } + + .discourse { + .scala-user-discourse { + @include shift(2); + } + + .scala-contributors-discourse, + .scala-user-discourse { + padding: 20px; + + img { + width: 34px; + height: auto; + } + + h4 { + font-family: $base-font-family; + font-size: $font-size-large; + text-transform: uppercase; + color: $gray-dark; + margin: 12px 0 8px; + } + text-align: center; + + p { + color: $base-font-color; + } + + &:active, + &:focus, + &:hover { + text-decoration: none; + background: $gray-lighter; + border-radius: $border-radius-small; + + h4 { + color: $brand-primary; + } + } + @include span-columns(4); + @include bp(large) { + @include span-columns(12); + @include shift(0); + } + } + } + + .gitter { + ul.first { + @include shift(2); + + li { + &:last-child { + border-bottom: $base-border-gray; + } + } + } + + ul, + ul.first { + @include span-columns(4); + @include bp(medium) { + @include span-columns(12); + @include shift(0); + + li { + &:last-child { + border-bottom: none; + } + } + } + } + + ul { + li { + a { + @include display(flex); + @include align-items(center); + @include justify-content(flex-start); + padding: 14px 0; + color: $base-font-color; + font-weight: $font-bold; + padding-left: 30px; + + &:active, + &:focus, + &:hover { + background: $gray-lighter; + text-decoration: none; + } + + img { + width: 28px; + height: auto; + margin-right: 10px; + } + } + border-top: $base-border-gray; + + &:last-child { + border-bottom: $base-border-gray; + } + } + } + } + + .communities { + ul { + text-align: center; + + li { + display: inline-block; + + &:first-child { + img { + width: 127px; + height: auto; + } + margin-right: 20px; + } + + &:last-child { + img { + width: 131px; + height: auto; + } + } + + a { + &:active, + &:focus, + &:hover { + opacity: 0.7; + } + } + } + } + } + + .social { + margin-top: 40px; + text-align: center; + ul { + li { + display: inline-block; + font-size: 1.75rem; + + &:first-child { + margin-right: 14px; + } + + a { + color: $gray; + + &:active, + &:focus, + &:hover { + color: $brand-primary; + } + } + + } + + } + } +} diff --git a/_sass/layout/title-page.scss b/_sass/layout/title-page.scss new file mode 100755 index 0000000000..44a33f8621 --- /dev/null +++ b/_sass/layout/title-page.scss @@ -0,0 +1,17 @@ +// TITLE PAGE +//------------------------------------------------ +//------------------------------------------------ + +.title-page { + background: $brand-tertiary; + // height: 200px; + min-height: 200px; + h1 { + font-size: 1.875rem; + font-family: $base-font-family; + padding-top: $padding-large; + text-transform: uppercase; + text-shadow: $text-shadow; + color: #fff; + } +} diff --git a/_sass/layout/toc.scss b/_sass/layout/toc.scss new file mode 100644 index 0000000000..a457ce903d --- /dev/null +++ b/_sass/layout/toc.scss @@ -0,0 +1,59 @@ +// SINGLE-PAGE TOC +//------------------------------------------------ +//------------------------------------------------ + +.sidebar-toc-wrapper { + @include bp(medium) { + display: none; + } + + .contents { + font-weight: 700; + } +} + +#toc { + ul { + list-style: none; + margin-left: 18px; + margin-top: 18px; + margin-bottom: 10px; + + a { + display: block; + list-style: none; + line-height: 1.3; + font-weight: $font-bold; + font-size: $font-size-small; + color: $gray-dark; + width: 100%; + + &:hover { + color: $brand-primary; + text-decoration: none; + } + } + + li { + margin-top: 10px; + + ul { + list-style: disc; + margin-left: 28px; + margin-top: -6px; + margin-bottom: 6px; + + a { + line-height: 1; + font-weight: normal; + } + + li { + margin-bottom: 0; + margin-top: 0; + line-height: 1.2; + } + } + } + } +} diff --git a/_sass/layout/tools.scss b/_sass/layout/tools.scss new file mode 100755 index 0000000000..deca4099c3 --- /dev/null +++ b/_sass/layout/tools.scss @@ -0,0 +1,66 @@ +// TOOLS +//------------------------------------------------ +//------------------------------------------------ +//------------------------------------------------ + +.content-primary { + .tools { + @include clearfix; + + .tool-item { + @include span-columns(4); + @include omega(3n); + margin-bottom: $padding-medium; + min-height: 120px; + padding: 15px; + @include bp(large) { + @include span-columns(12); + min-height: auto; + } + + .tool-item-header { + margin-bottom: 10px; + + img { + height: 50px; + width: auto; + } + + + h4 { + color: $headings-font-color; + margin-bottom: 0; + transition: $base-transition; + } + + a { + &:active, + &:focus, + &:hover { + text-decoration: none; + } + } + + } + + .tool-item-main { + p { + color: $base-font-color; + margin-bottom: 6px; + } + ul { + padding: 0; + margin: 0; + + li { + list-style: none; + padding: 0; + margin: 0; + display: inline-block; + + } + } + } + } + } +} diff --git a/_sass/layout/training-events.scss b/_sass/layout/training-events.scss new file mode 100755 index 0000000000..5851791642 --- /dev/null +++ b/_sass/layout/training-events.scss @@ -0,0 +1,82 @@ +// TRAINING-EVENTS +//------------------------------------------------ +//------------------------------------------------ + +.training-events { + h3 { + border-bottom: $base-border-gray; + padding-bottom: 14px; + } + + .training-list { + @include clearfix; + margin-top: $padding-medium; + + .training-item { + margin-bottom: $padding-medium; + @include span-columns(3); + @include omega(4n); + @include bp(medium) { + @include span-columns(12); + + padding-bottom: 20px; + border-bottom: $base-border-gray; + } + + img, + .calendar { + float: left; + } + + img { + width: 28px; + height: auto; + } + + .calendar { + span:last-child { + background: $gray-lighter; + } + } + + .training-text { + margin-left: 44px; + + h4 { + margin-bottom: 6px; + } + + p { + color: $base-font-color; + + &:nth-child(2) { + text-transform: uppercase; + } + } + } + + + &:active, + &:focus, + &:hover { + text-decoration: none; + + h4 { + color: $brand-primary; + } + } + } + } + + .org-scala-event { + // background: $gray-lighter; + padding: 24px; + border: 2px dashed $base-border-color-gray; + + h2 { + margin-top: 0; + margin-bottom: 10px; + } + margin-top: $padding-medium; + } +} diff --git a/_sass/layout/twitter-feed.scss b/_sass/layout/twitter-feed.scss new file mode 100755 index 0000000000..3f8ab6a0c7 --- /dev/null +++ b/_sass/layout/twitter-feed.scss @@ -0,0 +1,140 @@ +// TWITTER FEED +//------------------------------------------------ +//------------------------------------------------ + +.twitter-feed { + background: $brand-tertiary; + + .heading-line { + h2 { + span { + background: $brand-tertiary; + } + + &:before { + background: rgba(#fff, 0.5); + } + } + } + + .slider-twitter { + ul { + li { + padding: 0 15px; + @include display(flex); + @include flex-direction(row); + @include align-items(stretch); + @include justify-content(space-between); + + + @include bp(large) { + display: block; + } + + + + + .item-tweet { + padding: $padding-small; + background: #fff; + border-radius: $border-radius-base; + transition: $base-transition; + max-width: 360px; + margin-right: 20px; + + @include bp(large) { + max-width: 100%; + margin-right: 0; + margin-bottom: 20px; + + } + + + &:last-child { + margin-right: 0; + } + + + img { + border-radius: $border-radius-base; + width: 44px; + height: auto; + float: left; + } + + .tweet-text { + margin-left: 64px; + + .header-tweet { + @include display(flex); + @include flex-direction(row); + @include align-items(top); + @include justify-content(space-between); + + ul { + li { + padding: 0; + margin-right: 6px; + + &.user { + font-size: $font-size-large; + font-weight: $font-bold; + color: $headings-font-color; + + a { + color: $headings-font-color; + + &:active, + &:focus, + &:hover { + color: $hover-link-color; + text-decoration: none; + } + } + } + + &.username { + font-size: $font-size-small; + color: $base-font-color-light; + font-weight: $font-bold; + } + } + } + + .date { + font-size: $font-size-small; + color: $base-font-color-light; + } + } + + .main-tweet { + p { + font-size: $font-size-medium; + + .hastag { + color: rgba($base-font-color-light, 0.7); + + &:active, + &:focus, + &:hover { + color: $base-font-color-light; + } + } + } + } + } + + &:hover { + background: rgba(#fff, 0.88); + } + } + } + } + } + + .call-to-action { + p { + color: #fff; + } + } +} diff --git a/_sass/layout/type-md.scss b/_sass/layout/type-md.scss new file mode 100755 index 0000000000..0964604b1a --- /dev/null +++ b/_sass/layout/type-md.scss @@ -0,0 +1,214 @@ +// TYPE MD +//------------------------------------------------ +//------------------------------------------------ + +.full-width, +.books, +.content-primary, +.content-primary-blog, +.table-of-content, +.training-events { + h2, + h3 { + font-weight: $font-regular; + margin-top: 28px; + } + + h2, + h3, + h4, + h5 { + color: $headings-font-color; + font-weight: $font-regular; + + a { + color: $headings-font-color; + + &:active, + &:focus, + &:hover { + color: $brand-primary; + text-decoration: none; + } + } + } + + h2 { + font-size: 1.75rem; + } + + h3 { + font-size: 1.25rem; + } + + h4, + h5 { + font-size: 1.063rem; + font-family: $base-font-family; + text-transform: uppercase; + font-weight: $font-bold; + } +} + +.content-primary, +.content-primary-blog { + h2 { + color: $brand-primary; + a { + color: $brand-primary; + } + } +} + +.content-nav, +.content-nav-blog { + h5 { + font-size: 1.25rem; + margin-bottom: 12px; + color: $headings-font-color; + font-weight: $font-regular; + } +} + +.content-primary, +.text-step { + h2 { + margin-bottom: 24px; + + } + + blockquote, + h3, + h4, + h5, + img, + p, + pre, + table, + ul { + margin-bottom: 18px; + } + + ol, + ul { + padding-left: 18px; + } + + ol { + li { + list-style: decimal; + } + } + + ul { + li { + list-style: disc; + } + } + + ol, + ul { + li { + padding-left: 10px; + margin-bottom: 16px; + + ul { + margin-top: 18px; + + li { + margin-bottom: 8px; + list-style: circle; + padding-left: 0; + } + } + + &:last-child { + margin-bottom: 0; + } + } + } + + em { + font-style: italic; + } + + strong { + font-weight: $font-bold; + } + + del { + text-decoration: line-through; + } + + li, + p { + code { + font-family: 'Consolas'; + @include border-radius($border-radius-small); + font-size: $font-size-medium; + background: $gray-lighter; + color: #667b83; + // border: 1px solid #ced7d7; + padding: 0 6px; + margin: 0 4px; + } + } + + pre { + margin-bottom: 36px; + + code { + padding: 20px; + font-size: $font-size-medium; + @include border-radius($border-radius-base); + } + } + + table { + width: 100%; + text-align: left; + + thead { + font-weight: $font-bold; + } + + td, + th { + border-bottom: $base-border-gray; + padding: 6px 0; + } + } + + img { + width: 100%; + height: auto; + } + + blockquote { + padding: 20px; + border: 2px dashed $base-border-color-gray; + font-size: $font-size-large; + font-style: italic; + @include border-radius($border-radius-base); + + p { + margin: 0; + } + } + .tag-list { + padding: 0; + .tag-item { + &:last-child { + margin-bottom: 8px; + } + } + } + + .filter-tag { + margin-top: 30px; + margin-bottom: 24px; + text-transform: uppercase; + font-style: italic; + color: $base-font-color-light; + } +} diff --git a/_sass/layout/upcoming-events.scss b/_sass/layout/upcoming-events.scss new file mode 100755 index 0000000000..0daadc3eca --- /dev/null +++ b/_sass/layout/upcoming-events.scss @@ -0,0 +1,31 @@ +.upcoming-events { + background: $gray; + + .heading-line { + h2 { + span { + background: $gray; + } + } + } + + .events-items-list { + @include clearfix; + + .event-item { + @include span-columns(4); + @include omega(3n); + @include bp(large) { + @include span-columns(12); + } + } + } + + .card { + background: $gray-dark; + + &:hover { + background: $gray-darker; + } + } +} diff --git a/_sass/utils/_mixins.scss b/_sass/utils/_mixins.scss new file mode 100755 index 0000000000..e5ebca9147 --- /dev/null +++ b/_sass/utils/_mixins.scss @@ -0,0 +1,93 @@ +// MIXINS +//------------------------------------------------ +//------------------------------------------------ + +// Breakpoints +//------------------------------------------------ +@mixin bp($point) { + @if $point==xxlarge { + @media (max-width: $bp-xxlarge) { + @content; + } + } + @if $point==xlarge { + @media (max-width: $bp-xlarge) { + @content; + } + } + @if $point==large { + @media (max-width: $bp-large) { + @content; + } + } + @if $point==medium { + @media (max-width: $bp-medium) { + @content; + } + } + @if $point==small { + @media (max-width: $bp-small) { + @content; + } + } +} + +// Images +//------------------------------------------------ +@mixin image-size { + -webkit-background-size: cover; + -moz-background-size: cover; + -o-background-size: cover; + background-size: cover; +} + +// Font +//------------------------------------------------ +@mixin font-smoothing($value: on) { + @if $value == on { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + } + @else { + -webkit-font-smoothing: subpixel-antialiased; + -moz-osx-font-smoothing: auto; + } +} + +// Border +//------------------------------------------------ +@mixin border-radius($radius) { + border-radius: $radius; + -webkit-border-radius: $radius; + -moz-border-radius: $radius; + -ms-border-radius: $radius; + -o-border-radius: $radius; +} + +// User select +//------------------------------------------------ +@mixin no-select { + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + + +// Box Shadow +//------------------------------------------------ +@mixin box-shadow($params) { + -webkit-box-shadow: $params; + -moz-box-shadow: $params; + box-shadow: $params; +} + +// Select all elements after the nth element +//------------------------------------------------ +@mixin after($num) { + &:nth-child(n+#{$num + 1}) { + @content + } +} diff --git a/_sass/utils/_variables.scss b/_sass/utils/_variables.scss new file mode 100755 index 0000000000..b40b379981 --- /dev/null +++ b/_sass/utils/_variables.scss @@ -0,0 +1,94 @@ +// VARIABLES +//------------------------------------------------ +//------------------------------------------------ + +// Color +//------------------------------------------------ +$brand-primary: #DC322F; +$brand-secondary: #859900; +$brand-tertiary: #5CC6E4; +//------------------------------------------------- +$gray-darker: #002B36; +$gray-dark: #073642; +$gray: #15414C; +$gray-li: #244E58; +$gray-light: #E5EAEA; +$gray-lighter: #F0F3F3; +$apple-blue: #6dccf5; +//------------------------------------------------- +$headings-font-color: $gray-dark; +$base-font-color: #4A5659; +$base-font-color-light: #899295; +//------------------------------------------------- +$base-font-color-inverse: rgba(#fff, 0.70); + +// Typography +//------------------------------------------------ +@import url('https://fonts.googleapis.com/css?family=Lato:300,400,400i,700i,900'); +@import url('https://fonts.googleapis.com/css?family=Roboto+Slab:400,700'); +@import url('https://fonts.googleapis.com/css?family=Kalam:300,400,700'); +//------------------------------------------------ +$base-font-family: 'Lato', sans-serif; +$heading-font-family: 'Roboto Slab', serif; +//------------------------------------------------ +$em-base: 16px !global; +$base-font-size: $em-base; +//------------------------------------------------ +$font-size-large: 1.063rem; // 17px +$font-size-medium: 0.9375rem; // 15px +$font-size-small: 0.875rem; // 14px +$font-size-xsmall: 0.75rem; // 12px + +$font-size-h2: 1.375rem; // 22px +$font-size-h3: $font-size-large; // 17px + +//------------------------------------------------ +$base-line-height: 1.6; +$heading-line-height: 1.4; +//------------------------------------------------ +$font-light: 300; +$font-regular: 400; +$font-bold: 700; +$font-black: 900; + +// Link Colors +//------------------------------------------------ +$base-link-color: darken($brand-tertiary, 15%); +$hover-link-color: $brand-primary; + +// Border +//------------------------------------------------ +$base-border-color-gray: $gray-light; +$base-border-color-white: rgba(#fff, 0.14); +$base-border-gray: 1px solid $base-border-color-gray; +$base-border-white: 1px solid $base-border-color-white; + +// Other Sizes +//------------------------------------------------ +$padding-xlarge: 50px; +$padding-large: 40px; +$padding-medium: 30px; +$padding-small: 20px; +//------------------------------------------------ +$border-radius-base: 3px; +$border-radius-small: 2px; + +// Breakpoints +//------------------------------------------------ +$bp-small: 480px; +$bp-medium: 768px; +$bp-large: 992px; +$bp-xlarge: 1130px; +$bp-xxlarge: 1400px; + +// Animations +//------------------------------------------------ +$base-duration: 350ms; +$base-timing: ease; +$base-transition: all $base-duration $base-timing; + + +$box-shadow-item: rgba($gray-darker, 0.20) 0 1px 12px; +$box-shadow-inner: rgba($gray-darker, 0.04) 0 2px 1px; +$box-shadow-search: rgba($gray-darker, 0.2) 0 2px 8px; +$text-shadow: rgba($gray-darker, 0.10) 2px 2px 0; diff --git a/_sass/vendors/bourbon/_bourbon-deprecated-upcoming.scss b/_sass/vendors/bourbon/_bourbon-deprecated-upcoming.scss new file mode 100755 index 0000000000..e6d1b8cec0 --- /dev/null +++ b/_sass/vendors/bourbon/_bourbon-deprecated-upcoming.scss @@ -0,0 +1,411 @@ +// The following features have been deprecated and will be removed in the next MAJOR version release + +@mixin inline-block { + display: inline-block; + + @warn "The inline-block mixin is deprecated and will be removed in the next major version release"; +} + +@mixin button ($style: simple, $base-color: #4294f0, $text-size: inherit, $padding: 7px 18px) { + + @if type-of($style) == string and type-of($base-color) == color { + @include buttonstyle($style, $base-color, $text-size, $padding); + } + + @if type-of($style) == string and type-of($base-color) == number { + $padding: $text-size; + $text-size: $base-color; + $base-color: #4294f0; + + @if $padding == inherit { + $padding: 7px 18px; + } + + @include buttonstyle($style, $base-color, $text-size, $padding); + } + + @if type-of($style) == color and type-of($base-color) == color { + $base-color: $style; + $style: simple; + @include buttonstyle($style, $base-color, $text-size, $padding); + } + + @if type-of($style) == color and type-of($base-color) == number { + $padding: $text-size; + $text-size: $base-color; + $base-color: $style; + $style: simple; + + @if $padding == inherit { + $padding: 7px 18px; + } + + @include buttonstyle($style, $base-color, $text-size, $padding); + } + + @if type-of($style) == number { + $padding: $base-color; + $text-size: $style; + $base-color: #4294f0; + $style: simple; + + @if $padding == #4294f0 { + $padding: 7px 18px; + } + + @include buttonstyle($style, $base-color, $text-size, $padding); + } + + &:disabled { + cursor: not-allowed; + opacity: 0.5; + } + + @warn "The button mixin is deprecated and will be removed in the next major version release"; +} + +// Selector Style Button +@mixin buttonstyle($type, $b-color, $t-size, $pad) { + // Grayscale button + @if $type == simple and $b-color == grayscale($b-color) { + @include simple($b-color, true, $t-size, $pad); + } + + @if $type == shiny and $b-color == grayscale($b-color) { + @include shiny($b-color, true, $t-size, $pad); + } + + @if $type == pill and $b-color == grayscale($b-color) { + @include pill($b-color, true, $t-size, $pad); + } + + @if $type == flat and $b-color == grayscale($b-color) { + @include flat($b-color, true, $t-size, $pad); + } + + // Colored button + @if $type == simple { + @include simple($b-color, false, $t-size, $pad); + } + + @else if $type == shiny { + @include shiny($b-color, false, $t-size, $pad); + } + + @else if $type == pill { + @include pill($b-color, false, $t-size, $pad); + } + + @else if $type == flat { + @include flat($b-color, false, $t-size, $pad); + } +} + +// Simple Button +@mixin simple($base-color, $grayscale: false, $textsize: inherit, $padding: 7px 18px) { + $color: hsl(0, 0, 100%); + $border: adjust-color($base-color, $saturation: 9%, $lightness: -14%); + $inset-shadow: adjust-color($base-color, $saturation: -8%, $lightness: 15%); + $stop-gradient: adjust-color($base-color, $saturation: 9%, $lightness: -11%); + $text-shadow: adjust-color($base-color, $saturation: 15%, $lightness: -18%); + + @if is-light($base-color) { + $color: hsl(0, 0, 20%); + $text-shadow: adjust-color($base-color, $saturation: 10%, $lightness: 4%); + } + + @if $grayscale == true { + $border: grayscale($border); + $inset-shadow: grayscale($inset-shadow); + $stop-gradient: grayscale($stop-gradient); + $text-shadow: grayscale($text-shadow); + } + + border: 1px solid $border; + border-radius: 3px; + box-shadow: inset 0 1px 0 0 $inset-shadow; + color: $color; + display: inline-block; + font-size: $textsize; + font-weight: bold; + @include linear-gradient ($base-color, $stop-gradient); + padding: $padding; + text-decoration: none; + text-shadow: 0 1px 0 $text-shadow; + background-clip: padding-box; + + &:hover:not(:disabled) { + $base-color-hover: adjust-color($base-color, $saturation: -4%, $lightness: -5%); + $inset-shadow-hover: adjust-color($base-color, $saturation: -7%, $lightness: 5%); + $stop-gradient-hover: adjust-color($base-color, $saturation: 8%, $lightness: -14%); + + @if $grayscale == true { + $base-color-hover: grayscale($base-color-hover); + $inset-shadow-hover: grayscale($inset-shadow-hover); + $stop-gradient-hover: grayscale($stop-gradient-hover); + } + + @include linear-gradient ($base-color-hover, $stop-gradient-hover); + + box-shadow: inset 0 1px 0 0 $inset-shadow-hover; + cursor: pointer; + } + + &:active:not(:disabled), + &:focus:not(:disabled) { + $border-active: adjust-color($base-color, $saturation: 9%, $lightness: -14%); + $inset-shadow-active: adjust-color($base-color, $saturation: 7%, $lightness: -17%); + + @if $grayscale == true { + $border-active: grayscale($border-active); + $inset-shadow-active: grayscale($inset-shadow-active); + } + + border: 1px solid $border-active; + box-shadow: inset 0 0 8px 4px $inset-shadow-active, inset 0 0 8px 4px $inset-shadow-active; + } +} + +// Shiny Button +@mixin shiny($base-color, $grayscale: false, $textsize: inherit, $padding: 7px 18px) { + $color: hsl(0, 0, 100%); + $border: adjust-color($base-color, $red: -117, $green: -111, $blue: -81); + $border-bottom: adjust-color($base-color, $red: -126, $green: -127, $blue: -122); + $fourth-stop: adjust-color($base-color, $red: -79, $green: -70, $blue: -46); + $inset-shadow: adjust-color($base-color, $red: 37, $green: 29, $blue: 12); + $second-stop: adjust-color($base-color, $red: -56, $green: -50, $blue: -33); + $text-shadow: adjust-color($base-color, $red: -140, $green: -141, $blue: -114); + $third-stop: adjust-color($base-color, $red: -86, $green: -75, $blue: -48); + + @if is-light($base-color) { + $color: hsl(0, 0, 20%); + $text-shadow: adjust-color($base-color, $saturation: 10%, $lightness: 4%); + } + + @if $grayscale == true { + $border: grayscale($border); + $border-bottom: grayscale($border-bottom); + $fourth-stop: grayscale($fourth-stop); + $inset-shadow: grayscale($inset-shadow); + $second-stop: grayscale($second-stop); + $text-shadow: grayscale($text-shadow); + $third-stop: grayscale($third-stop); + } + + @include linear-gradient(top, $base-color 0%, $second-stop 50%, $third-stop 50%, $fourth-stop 100%); + + border: 1px solid $border; + border-bottom: 1px solid $border-bottom; + border-radius: 5px; + box-shadow: inset 0 1px 0 0 $inset-shadow; + color: $color; + display: inline-block; + font-size: $textsize; + font-weight: bold; + padding: $padding; + text-align: center; + text-decoration: none; + text-shadow: 0 -1px 1px $text-shadow; + + &:hover:not(:disabled) { + $first-stop-hover: adjust-color($base-color, $red: -13, $green: -15, $blue: -18); + $second-stop-hover: adjust-color($base-color, $red: -66, $green: -62, $blue: -51); + $third-stop-hover: adjust-color($base-color, $red: -93, $green: -85, $blue: -66); + $fourth-stop-hover: adjust-color($base-color, $red: -86, $green: -80, $blue: -63); + + @if $grayscale == true { + $first-stop-hover: grayscale($first-stop-hover); + $second-stop-hover: grayscale($second-stop-hover); + $third-stop-hover: grayscale($third-stop-hover); + $fourth-stop-hover: grayscale($fourth-stop-hover); + } + + @include linear-gradient(top, $first-stop-hover 0%, + $second-stop-hover 50%, + $third-stop-hover 50%, + $fourth-stop-hover 100%); + cursor: pointer; + } + + &:active:not(:disabled), + &:focus:not(:disabled) { + $inset-shadow-active: adjust-color($base-color, $red: -111, $green: -116, $blue: -122); + + @if $grayscale == true { + $inset-shadow-active: grayscale($inset-shadow-active); + } + + box-shadow: inset 0 0 20px 0 $inset-shadow-active; + } +} + +// Pill Button +@mixin pill($base-color, $grayscale: false, $textsize: inherit, $padding: 7px 18px) { + $color: hsl(0, 0, 100%); + $border-bottom: adjust-color($base-color, $hue: 8, $saturation: -11%, $lightness: -26%); + $border-sides: adjust-color($base-color, $hue: 4, $saturation: -21%, $lightness: -21%); + $border-top: adjust-color($base-color, $hue: -1, $saturation: -30%, $lightness: -15%); + $inset-shadow: adjust-color($base-color, $hue: -1, $saturation: -1%, $lightness: 7%); + $stop-gradient: adjust-color($base-color, $hue: 8, $saturation: 14%, $lightness: -10%); + $text-shadow: adjust-color($base-color, $hue: 5, $saturation: -19%, $lightness: -15%); + + @if is-light($base-color) { + $color: hsl(0, 0, 20%); + $text-shadow: adjust-color($base-color, $saturation: 10%, $lightness: 4%); + } + + @if $grayscale == true { + $border-bottom: grayscale($border-bottom); + $border-sides: grayscale($border-sides); + $border-top: grayscale($border-top); + $inset-shadow: grayscale($inset-shadow); + $stop-gradient: grayscale($stop-gradient); + $text-shadow: grayscale($text-shadow); + } + + border: 1px solid $border-top; + border-color: $border-top $border-sides $border-bottom; + border-radius: 16px; + box-shadow: inset 0 1px 0 0 $inset-shadow; + color: $color; + display: inline-block; + font-size: $textsize; + font-weight: normal; + line-height: 1; + @include linear-gradient ($base-color, $stop-gradient); + padding: $padding; + text-align: center; + text-decoration: none; + text-shadow: 0 -1px 1px $text-shadow; + background-clip: padding-box; + + &:hover:not(:disabled) { + $base-color-hover: adjust-color($base-color, $lightness: -4.5%); + $border-bottom: adjust-color($base-color, $hue: 8, $saturation: 13.5%, $lightness: -32%); + $border-sides: adjust-color($base-color, $hue: 4, $saturation: -2%, $lightness: -27%); + $border-top: adjust-color($base-color, $hue: -1, $saturation: -17%, $lightness: -21%); + $inset-shadow-hover: adjust-color($base-color, $saturation: -1%, $lightness: 3%); + $stop-gradient-hover: adjust-color($base-color, $hue: 8, $saturation: -4%, $lightness: -15.5%); + $text-shadow-hover: adjust-color($base-color, $hue: 5, $saturation: -5%, $lightness: -22%); + + @if $grayscale == true { + $base-color-hover: grayscale($base-color-hover); + $border-bottom: grayscale($border-bottom); + $border-sides: grayscale($border-sides); + $border-top: grayscale($border-top); + $inset-shadow-hover: grayscale($inset-shadow-hover); + $stop-gradient-hover: grayscale($stop-gradient-hover); + $text-shadow-hover: grayscale($text-shadow-hover); + } + + @include linear-gradient ($base-color-hover, $stop-gradient-hover); + + background-clip: padding-box; + border: 1px solid $border-top; + border-color: $border-top $border-sides $border-bottom; + box-shadow: inset 0 1px 0 0 $inset-shadow-hover; + cursor: pointer; + text-shadow: 0 -1px 1px $text-shadow-hover; + } + + &:active:not(:disabled), + &:focus:not(:disabled) { + $active-color: adjust-color($base-color, $hue: 4, $saturation: -12%, $lightness: -10%); + $border-active: adjust-color($base-color, $hue: 6, $saturation: -2.5%, $lightness: -30%); + $border-bottom-active: adjust-color($base-color, $hue: 11, $saturation: 6%, $lightness: -31%); + $inset-shadow-active: adjust-color($base-color, $hue: 9, $saturation: 2%, $lightness: -21.5%); + $text-shadow-active: adjust-color($base-color, $hue: 5, $saturation: -12%, $lightness: -21.5%); + + @if $grayscale == true { + $active-color: grayscale($active-color); + $border-active: grayscale($border-active); + $border-bottom-active: grayscale($border-bottom-active); + $inset-shadow-active: grayscale($inset-shadow-active); + $text-shadow-active: grayscale($text-shadow-active); + } + + background: $active-color; + border: 1px solid $border-active; + border-bottom: 1px solid $border-bottom-active; + box-shadow: inset 0 0 6px 3px $inset-shadow-active; + text-shadow: 0 -1px 1px $text-shadow-active; + } +} + +// Flat Button +@mixin flat($base-color, $grayscale: false, $textsize: inherit, $padding: 7px 18px) { + $color: hsl(0, 0, 100%); + + @if is-light($base-color) { + $color: hsl(0, 0, 20%); + } + + background-color: $base-color; + border-radius: 3px; + border: 0; + color: $color; + display: inline-block; + font-size: $textsize; + font-weight: bold; + padding: $padding; + text-decoration: none; + background-clip: padding-box; + + &:hover:not(:disabled){ + $base-color-hover: adjust-color($base-color, $saturation: 4%, $lightness: 5%); + + @if $grayscale == true { + $base-color-hover: grayscale($base-color-hover); + } + + background-color: $base-color-hover; + cursor: pointer; + } + + &:active:not(:disabled), + &:focus:not(:disabled) { + $base-color-active: adjust-color($base-color, $saturation: -4%, $lightness: -5%); + + @if $grayscale == true { + $base-color-active: grayscale($base-color-active); + } + + background-color: $base-color-active; + cursor: pointer; + } +} + +// Flexible grid +@function flex-grid($columns, $container-columns: $fg-max-columns) { + $width: $columns * $fg-column + ($columns - 1) * $fg-gutter; + $container-width: $container-columns * $fg-column + ($container-columns - 1) * $fg-gutter; + @return percentage($width / $container-width); + + @warn "The flex-grid function is deprecated and will be removed in the next major version release"; +} + +// Flexible gutter +@function flex-gutter($container-columns: $fg-max-columns, $gutter: $fg-gutter) { + $container-width: $container-columns * $fg-column + ($container-columns - 1) * $fg-gutter; + @return percentage($gutter / $container-width); + + @warn "The flex-gutter function is deprecated and will be removed in the next major version release"; +} + +@function grid-width($n) { + @return $n * $gw-column + ($n - 1) * $gw-gutter; + + @warn "The grid-width function is deprecated and will be removed in the next major version release"; +} + +@function golden-ratio($value, $increment) { + @return modular-scale($increment, $value, $ratio: $golden); + + @warn "The golden-ratio function is deprecated and will be removed in the next major version release. Please use the modular-scale function, instead."; +} + +@mixin box-sizing($box) { + @include prefixer(box-sizing, $box, webkit moz spec); + + @warn "The box-sizing mixin is deprecated and will be removed in the next major version release. This property can now be used un-prefixed."; +} diff --git a/_sass/vendors/bourbon/_bourbon.scss b/_sass/vendors/bourbon/_bourbon.scss new file mode 100755 index 0000000000..635c680414 --- /dev/null +++ b/_sass/vendors/bourbon/_bourbon.scss @@ -0,0 +1,87 @@ +// Bourbon 4.2.7 +// http://bourbon.io +// Copyright 2011-2015 thoughtbot, inc. +// MIT License + +@import "settings/prefixer"; +@import "settings/px-to-em"; +@import "settings/asset-pipeline"; + +@import "functions/assign-inputs"; +@import "functions/contains"; +@import "functions/contains-falsy"; +@import "functions/is-length"; +@import "functions/is-light"; +@import "functions/is-number"; +@import "functions/is-size"; +@import "functions/px-to-em"; +@import "functions/px-to-rem"; +@import "functions/shade"; +@import "functions/strip-units"; +@import "functions/tint"; +@import "functions/transition-property-name"; +@import "functions/unpack"; +@import "functions/modular-scale"; + +@import "helpers/convert-units"; +@import "helpers/directional-values"; +@import "helpers/font-source-declaration"; +@import "helpers/gradient-positions-parser"; +@import "helpers/linear-angle-parser"; +@import "helpers/linear-gradient-parser"; +@import "helpers/linear-positions-parser"; +@import "helpers/linear-side-corner-parser"; +@import "helpers/radial-arg-parser"; +@import "helpers/radial-positions-parser"; +@import "helpers/radial-gradient-parser"; +@import "helpers/render-gradients"; +@import "helpers/shape-size-stripper"; +@import "helpers/str-to-num"; + +@import "css3/animation"; +@import "css3/appearance"; +@import "css3/backface-visibility"; +@import "css3/background"; +@import "css3/background-image"; +@import "css3/border-image"; +@import "css3/calc"; +@import "css3/columns"; +@import "css3/filter"; +@import "css3/flex-box"; +@import "css3/font-face"; +@import "css3/font-feature-settings"; +@import "css3/hidpi-media-query"; +@import "css3/hyphens"; +@import "css3/image-rendering"; +@import "css3/keyframes"; +@import "css3/linear-gradient"; +@import "css3/perspective"; +@import "css3/placeholder"; +@import "css3/radial-gradient"; +@import "css3/selection"; +@import "css3/text-decoration"; +@import "css3/transform"; +@import "css3/transition"; +@import "css3/user-select"; + +@import "addons/border-color"; +@import "addons/border-radius"; +@import "addons/border-style"; +@import "addons/border-width"; +@import "addons/buttons"; +@import "addons/clearfix"; +@import "addons/ellipsis"; +@import "addons/font-stacks"; +@import "addons/hide-text"; +@import "addons/margin"; +@import "addons/padding"; +@import "addons/position"; +@import "addons/prefixer"; +@import "addons/retina-image"; +@import "addons/size"; +@import "addons/text-inputs"; +@import "addons/timing-functions"; +@import "addons/triangle"; +@import "addons/word-wrap"; + +@import "bourbon-deprecated-upcoming"; diff --git a/_sass/vendors/bourbon/addons/_border-color.scss b/_sass/vendors/bourbon/addons/_border-color.scss new file mode 100755 index 0000000000..6f6ab36c4e --- /dev/null +++ b/_sass/vendors/bourbon/addons/_border-color.scss @@ -0,0 +1,26 @@ +@charset "UTF-8"; + +/// Provides a quick method for targeting `border-color` on specific sides of a box. Use a `null` value to “skip” a side. +/// +/// @param {Arglist} $vals +/// List of arguments +/// +/// @example scss - Usage +/// .element { +/// @include border-color(#a60b55 #76cd9c null #e8ae1a); +/// } +/// +/// @example css - CSS Output +/// .element { +/// border-left-color: #e8ae1a; +/// border-right-color: #76cd9c; +/// border-top-color: #a60b55; +/// } +/// +/// @require {mixin} directional-property +/// +/// @output `border-color` + +@mixin border-color($vals...) { + @include directional-property(border, color, $vals...); +} diff --git a/_sass/vendors/bourbon/addons/_border-radius.scss b/_sass/vendors/bourbon/addons/_border-radius.scss new file mode 100755 index 0000000000..1f6586335c --- /dev/null +++ b/_sass/vendors/bourbon/addons/_border-radius.scss @@ -0,0 +1,48 @@ +@charset "UTF-8"; + +/// Provides a quick method for targeting `border-radius` on both corners on the side of a box. +/// +/// @param {Number} $radii +/// List of arguments +/// +/// @example scss - Usage +/// .element-one { +/// @include border-top-radius(5px); +/// } +/// +/// .element-two { +/// @include border-left-radius(3px); +/// } +/// +/// @example css - CSS Output +/// .element-one { +/// border-top-left-radius: 5px; +/// border-top-right-radius: 5px; +/// } +/// +/// .element-two { +/// border-bottom-left-radius: 3px; +/// border-top-left-radius: 3px; +/// } +/// +/// @output `border-radius` + +@mixin border-top-radius($radii) { + border-top-left-radius: $radii; + border-top-right-radius: $radii; +} + +@mixin border-right-radius($radii) { + border-bottom-right-radius: $radii; + border-top-right-radius: $radii; +} + +@mixin border-bottom-radius($radii) { + border-bottom-left-radius: $radii; + border-bottom-right-radius: $radii; +} + +@mixin border-left-radius($radii) { + border-bottom-left-radius: $radii; + border-top-left-radius: $radii; +} diff --git a/_sass/vendors/bourbon/addons/_border-style.scss b/_sass/vendors/bourbon/addons/_border-style.scss new file mode 100755 index 0000000000..d86ee79d93 --- /dev/null +++ b/_sass/vendors/bourbon/addons/_border-style.scss @@ -0,0 +1,25 @@ +@charset "UTF-8"; + +/// Provides a quick method for targeting `border-style` on specific sides of a box. Use a `null` value to “skip” a side. +/// +/// @param {Arglist} $vals +/// List of arguments +/// +/// @example scss - Usage +/// .element { +/// @include border-style(dashed null solid); +/// } +/// +/// @example css - CSS Output +/// .element { +/// border-bottom-style: solid; +/// border-top-style: dashed; +/// } +/// +/// @require {mixin} directional-property +/// +/// @output `border-style` + +@mixin border-style($vals...) { + @include directional-property(border, style, $vals...); +} diff --git a/_sass/vendors/bourbon/addons/_border-width.scss b/_sass/vendors/bourbon/addons/_border-width.scss new file mode 100755 index 0000000000..0ea2d4b71d --- /dev/null +++ b/_sass/vendors/bourbon/addons/_border-width.scss @@ -0,0 +1,25 @@ +@charset "UTF-8"; + +/// Provides a quick method for targeting `border-width` on specific sides of a box. Use a `null` value to “skip” a side. +/// +/// @param {Arglist} $vals +/// List of arguments +/// +/// @example scss - Usage +/// .element { +/// @include border-width(1em null 20px); +/// } +/// +/// @example css - CSS Output +/// .element { +/// border-bottom-width: 20px; +/// border-top-width: 1em; +/// } +/// +/// @require {mixin} directional-property +/// +/// @output `border-width` + +@mixin border-width($vals...) { + @include directional-property(border, width, $vals...); +} diff --git a/_sass/vendors/bourbon/addons/_buttons.scss b/_sass/vendors/bourbon/addons/_buttons.scss new file mode 100755 index 0000000000..debeabc539 --- /dev/null +++ b/_sass/vendors/bourbon/addons/_buttons.scss @@ -0,0 +1,64 @@ +@charset "UTF-8"; + +/// Generates variables for all buttons. Please note that you must use interpolation on the variable: `#{$all-buttons}`. +/// +/// @example scss - Usage +/// #{$all-buttons} { +/// background-color: #f00; +/// } +/// +/// #{$all-buttons-focus}, +/// #{$all-buttons-hover} { +/// background-color: #0f0; +/// } +/// +/// #{$all-buttons-active} { +/// background-color: #00f; +/// } +/// +/// @example css - CSS Output +/// button, +/// input[type="button"], +/// input[type="reset"], +/// input[type="submit"] { +/// background-color: #f00; +/// } +/// +/// button:focus, +/// input[type="button"]:focus, +/// input[type="reset"]:focus, +/// input[type="submit"]:focus, +/// button:hover, +/// input[type="button"]:hover, +/// input[type="reset"]:hover, +/// input[type="submit"]:hover { +/// background-color: #0f0; +/// } +/// +/// button:active, +/// input[type="button"]:active, +/// input[type="reset"]:active, +/// input[type="submit"]:active { +/// background-color: #00f; +/// } +/// +/// @require assign-inputs +/// +/// @type List +/// +/// @todo Remove double assigned variables (Lines 59–62) in v5.0.0 + +$buttons-list: 'button', + 'input[type="button"]', + 'input[type="reset"]', + 'input[type="submit"]'; + +$all-buttons: assign-inputs($buttons-list); +$all-buttons-active: assign-inputs($buttons-list, active); +$all-buttons-focus: assign-inputs($buttons-list, focus); +$all-buttons-hover: assign-inputs($buttons-list, hover); + +$all-button-inputs: $all-buttons; +$all-button-inputs-active: $all-buttons-active; +$all-button-inputs-focus: $all-buttons-focus; +$all-button-inputs-hover: $all-buttons-hover; diff --git a/_sass/vendors/bourbon/addons/_clearfix.scss b/_sass/vendors/bourbon/addons/_clearfix.scss new file mode 100755 index 0000000000..11313d66f1 --- /dev/null +++ b/_sass/vendors/bourbon/addons/_clearfix.scss @@ -0,0 +1,25 @@ +@charset "UTF-8"; + +/// Provides an easy way to include a clearfix for containing floats. +/// +/// @link http://cssmojo.com/latest_new_clearfix_so_far/ +/// +/// @example scss - Usage +/// .element { +/// @include clearfix; +/// } +/// +/// @example css - CSS Output +/// .element::after { +/// clear: both; +/// content: ""; +/// display: table; +/// } + +@mixin clearfix { + &::after { + clear: both; + content: ""; + display: table; + } +} diff --git a/_sass/vendors/bourbon/addons/_ellipsis.scss b/_sass/vendors/bourbon/addons/_ellipsis.scss new file mode 100755 index 0000000000..a367f651c1 --- /dev/null +++ b/_sass/vendors/bourbon/addons/_ellipsis.scss @@ -0,0 +1,30 @@ +@charset "UTF-8"; + +/// Truncates text and adds an ellipsis to represent overflow. +/// +/// @param {Number} $width [100%] +/// Max-width for the string to respect before being truncated +/// +/// @example scss - Usage +/// .element { +/// @include ellipsis; +/// } +/// +/// @example css - CSS Output +/// .element { +/// display: inline-block; +/// max-width: 100%; +/// overflow: hidden; +/// text-overflow: ellipsis; +/// white-space: nowrap; +/// word-wrap: normal; +/// } + +@mixin ellipsis($width: 100%) { + display: inline-block; + max-width: $width; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + word-wrap: normal; +} diff --git a/_sass/vendors/bourbon/addons/_font-stacks.scss b/_sass/vendors/bourbon/addons/_font-stacks.scss new file mode 100755 index 0000000000..57128f422a --- /dev/null +++ b/_sass/vendors/bourbon/addons/_font-stacks.scss @@ -0,0 +1,31 @@ +@charset "UTF-8"; + +/// Georgia font stack. +/// +/// @type List + +$georgia: "Georgia", "Cambria", "Times New Roman", "Times", serif; + +/// Helvetica font stack. +/// +/// @type List + +$helvetica: "Helvetica Neue", "Helvetica", "Roboto", "Arial", sans-serif; + +/// Lucida Grande font stack. +/// +/// @type List + +$lucida-grande: "Lucida Grande", "Tahoma", "Verdana", "Arial", sans-serif; + +/// Monospace font stack. +/// +/// @type List + +$monospace: "Bitstream Vera Sans Mono", "Consolas", "Courier", monospace; + +/// Verdana font stack. +/// +/// @type List + +$verdana: "Verdana", "Geneva", sans-serif; diff --git a/_sass/vendors/bourbon/addons/_hide-text.scss b/_sass/vendors/bourbon/addons/_hide-text.scss new file mode 100755 index 0000000000..4caf20ed58 --- /dev/null +++ b/_sass/vendors/bourbon/addons/_hide-text.scss @@ -0,0 +1,27 @@ +/// Hides the text in an element, commonly used to show an image. Some elements will need block-level styles applied. +/// +/// @link http://zeldman.com/2012/03/01/replacing-the-9999px-hack-new-image-replacement +/// +/// @example scss - Usage +/// .element { +/// @include hide-text; +/// } +/// +/// @example css - CSS Output +/// .element { +/// overflow: hidden; +/// text-indent: 101%; +/// white-space: nowrap; +/// } +/// +/// @todo Remove height argument in v5.0.0 + +@mixin hide-text($height: null) { + overflow: hidden; + text-indent: 101%; + white-space: nowrap; + + @if $height { + @warn "The `hide-text` mixin has changed and no longer requires a height. The height argument will no longer be accepted in v5.0.0"; + } +} diff --git a/_sass/vendors/bourbon/addons/_margin.scss b/_sass/vendors/bourbon/addons/_margin.scss new file mode 100755 index 0000000000..674f4e5f6e --- /dev/null +++ b/_sass/vendors/bourbon/addons/_margin.scss @@ -0,0 +1,26 @@ +@charset "UTF-8"; + +/// Provides a quick method for targeting `margin` on specific sides of a box. Use a `null` value to “skip” a side. +/// +/// @param {Arglist} $vals +/// List of arguments +/// +/// @example scss - Usage +/// .element { +/// @include margin(null 10px 3em 20vh); +/// } +/// +/// @example css - CSS Output +/// .element { +/// margin-bottom: 3em; +/// margin-left: 20vh; +/// margin-right: 10px; +/// } +/// +/// @require {mixin} directional-property +/// +/// @output `margin` + +@mixin margin($vals...) { + @include directional-property(margin, false, $vals...); +} diff --git a/_sass/vendors/bourbon/addons/_padding.scss b/_sass/vendors/bourbon/addons/_padding.scss new file mode 100755 index 0000000000..40a5f006b2 --- /dev/null +++ b/_sass/vendors/bourbon/addons/_padding.scss @@ -0,0 +1,26 @@ +@charset "UTF-8"; + +/// Provides a quick method for targeting `padding` on specific sides of a box. Use a `null` value to “skip” a side. +/// +/// @param {Arglist} $vals +/// List of arguments +/// +/// @example scss - Usage +/// .element { +/// @include padding(12vh null 10px 5%); +/// } +/// +/// @example css - CSS Output +/// .element { +/// padding-bottom: 10px; +/// padding-left: 5%; +/// padding-top: 12vh; +/// } +/// +/// @require {mixin} directional-property +/// +/// @output `padding` + +@mixin padding($vals...) { + @include directional-property(padding, false, $vals...); +} diff --git a/_sass/vendors/bourbon/addons/_position.scss b/_sass/vendors/bourbon/addons/_position.scss new file mode 100755 index 0000000000..e460f3ffdb --- /dev/null +++ b/_sass/vendors/bourbon/addons/_position.scss @@ -0,0 +1,48 @@ +@charset "UTF-8"; + +/// Provides a quick method for setting an element’s position. Use a `null` value to “skip” a side. +/// +/// @param {Position} $position [relative] +/// A CSS position value +/// +/// @param {Arglist} $coordinates [null null null null] +/// List of values that correspond to the 4-value syntax for the edges of a box +/// +/// @example scss - Usage +/// .element { +/// @include position(absolute, 0 null null 10em); +/// } +/// +/// @example css - CSS Output +/// .element { +/// left: 10em; +/// position: absolute; +/// top: 0; +/// } +/// +/// @require {function} is-length +/// @require {function} unpack + +@mixin position($position: relative, $coordinates: null null null null) { + @if type-of($position) == list { + $coordinates: $position; + $position: relative; + } + + $coordinates: unpack($coordinates); + + $offsets: ( + top: nth($coordinates, 1), + right: nth($coordinates, 2), + bottom: nth($coordinates, 3), + left: nth($coordinates, 4) + ); + + position: $position; + + @each $offset, $value in $offsets { + @if is-length($value) { + #{$offset}: $value; + } + } +} diff --git a/_sass/vendors/bourbon/addons/_prefixer.scss b/_sass/vendors/bourbon/addons/_prefixer.scss new file mode 100755 index 0000000000..2b6f731383 --- /dev/null +++ b/_sass/vendors/bourbon/addons/_prefixer.scss @@ -0,0 +1,66 @@ +@charset "UTF-8"; + +/// A mixin for generating vendor prefixes on non-standardized properties. +/// +/// @param {String} $property +/// Property to prefix +/// +/// @param {*} $value +/// Value to use +/// +/// @param {List} $prefixes +/// Prefixes to define +/// +/// @example scss - Usage +/// .element { +/// @include prefixer(border-radius, 10px, webkit ms spec); +/// } +/// +/// @example css - CSS Output +/// .element { +/// -webkit-border-radius: 10px; +/// -moz-border-radius: 10px; +/// border-radius: 10px; +/// } +/// +/// @require {variable} $prefix-for-webkit +/// @require {variable} $prefix-for-mozilla +/// @require {variable} $prefix-for-microsoft +/// @require {variable} $prefix-for-opera +/// @require {variable} $prefix-for-spec + +@mixin prefixer($property, $value, $prefixes) { + @each $prefix in $prefixes { + @if $prefix == webkit { + @if $prefix-for-webkit { + -webkit-#{$property}: $value; + } + } @else if $prefix == moz { + @if $prefix-for-mozilla { + -moz-#{$property}: $value; + } + } @else if $prefix == ms { + @if $prefix-for-microsoft { + -ms-#{$property}: $value; + } + } @else if $prefix == o { + @if $prefix-for-opera { + -o-#{$property}: $value; + } + } @else if $prefix == spec { + @if $prefix-for-spec { + #{$property}: $value; + } + } @else { + @warn "Unrecognized prefix: #{$prefix}"; + } + } +} + +@mixin disable-prefix-for-all() { + $prefix-for-webkit: false !global; + $prefix-for-mozilla: false !global; + $prefix-for-microsoft: false !global; + $prefix-for-opera: false !global; + $prefix-for-spec: false !global; +} diff --git a/_sass/vendors/bourbon/addons/_retina-image.scss b/_sass/vendors/bourbon/addons/_retina-image.scss new file mode 100755 index 0000000000..7febbd7513 --- /dev/null +++ b/_sass/vendors/bourbon/addons/_retina-image.scss @@ -0,0 +1,25 @@ +@mixin retina-image($filename, $background-size, $extension: png, $retina-filename: null, $retina-suffix: _2x, $asset-pipeline: $asset-pipeline) { + @if $asset-pipeline { + background-image: image-url("#{$filename}.#{$extension}"); + } @else { + background-image: url("#{$filename}.#{$extension}"); + } + + @include hidpi { + @if $asset-pipeline { + @if $retina-filename { + background-image: image-url("#{$retina-filename}.#{$extension}"); + } @else { + background-image: image-url("#{$filename}#{$retina-suffix}.#{$extension}"); + } + } @else { + @if $retina-filename { + background-image: url("#{$retina-filename}.#{$extension}"); + } @else { + background-image: url("#{$filename}#{$retina-suffix}.#{$extension}"); + } + } + + background-size: $background-size; + } +} diff --git a/_sass/vendors/bourbon/addons/_size.scss b/_sass/vendors/bourbon/addons/_size.scss new file mode 100755 index 0000000000..a2992a34c6 --- /dev/null +++ b/_sass/vendors/bourbon/addons/_size.scss @@ -0,0 +1,51 @@ +@charset "UTF-8"; + +/// Sets the `width` and `height` of the element. +/// +/// @param {List} $size +/// A list of at most 2 size values. +/// +/// If there is only a single value in `$size` it is used for both width and height. All units are supported. +/// +/// @example scss - Usage +/// .first-element { +/// @include size(2em); +/// } +/// +/// .second-element { +/// @include size(auto 10em); +/// } +/// +/// @example css - CSS Output +/// .first-element { +/// width: 2em; +/// height: 2em; +/// } +/// +/// .second-element { +/// width: auto; +/// height: 10em; +/// } +/// +/// @todo Refactor in 5.0.0 to use a comma-separated argument + +@mixin size($value) { + $width: nth($value, 1); + $height: $width; + + @if length($value) > 1 { + $height: nth($value, 2); + } + + @if is-size($height) { + height: $height; + } @else { + @warn "`#{$height}` is not a valid length for the `$height` parameter in the `size` mixin."; + } + + @if is-size($width) { + width: $width; + } @else { + @warn "`#{$width}` is not a valid length for the `$width` parameter in the `size` mixin."; + } +} diff --git a/_sass/vendors/bourbon/addons/_text-inputs.scss b/_sass/vendors/bourbon/addons/_text-inputs.scss new file mode 100755 index 0000000000..1eb7a5451a --- /dev/null +++ b/_sass/vendors/bourbon/addons/_text-inputs.scss @@ -0,0 +1,113 @@ +@charset "UTF-8"; + +/// Generates variables for all text-based inputs. Please note that you must use interpolation on the variable: `#{$all-text-inputs}`. +/// +/// @example scss - Usage +/// #{$all-text-inputs} { +/// border: 1px solid #f00; +/// } +/// +/// #{$all-text-inputs-focus}, +/// #{$all-text-inputs-hover} { +/// border: 1px solid #0f0; +/// } +/// +/// #{$all-text-inputs-active} { +/// border: 1px solid #00f; +/// } +/// +/// @example css - CSS Output +/// input[type="color"], +/// input[type="date"], +/// input[type="datetime"], +/// input[type="datetime-local"], +/// input[type="email"], +/// input[type="month"], +/// input[type="number"], +/// input[type="password"], +/// input[type="search"], +/// input[type="tel"], +/// input[type="text"], +/// input[type="time"], +/// input[type="url"], +/// input[type="week"], +/// textarea { +/// border: 1px solid #f00; +/// } +/// +/// input[type="color"]:focus, +/// input[type="date"]:focus, +/// input[type="datetime"]:focus, +/// input[type="datetime-local"]:focus, +/// input[type="email"]:focus, +/// input[type="month"]:focus, +/// input[type="number"]:focus, +/// input[type="password"]:focus, +/// input[type="search"]:focus, +/// input[type="tel"]:focus, +/// input[type="text"]:focus, +/// input[type="time"]:focus, +/// input[type="url"]:focus, +/// input[type="week"]:focus, +/// textarea:focus, +/// input[type="color"]:hover, +/// input[type="date"]:hover, +/// input[type="datetime"]:hover, +/// input[type="datetime-local"]:hover, +/// input[type="email"]:hover, +/// input[type="month"]:hover, +/// input[type="number"]:hover, +/// input[type="password"]:hover, +/// input[type="search"]:hover, +/// input[type="tel"]:hover, +/// input[type="text"]:hover, +/// input[type="time"]:hover, +/// input[type="url"]:hover, +/// input[type="week"]:hover, +/// textarea:hover { +/// border: 1px solid #0f0; +/// } +/// +/// input[type="color"]:active, +/// input[type="date"]:active, +/// input[type="datetime"]:active, +/// input[type="datetime-local"]:active, +/// input[type="email"]:active, +/// input[type="month"]:active, +/// input[type="number"]:active, +/// input[type="password"]:active, +/// input[type="search"]:active, +/// input[type="tel"]:active, +/// input[type="text"]:active, +/// input[type="time"]:active, +/// input[type="url"]:active, +/// input[type="week"]:active, +/// textarea:active { +/// border: 1px solid #00f; +/// } +/// +/// @require assign-inputs +/// +/// @type List + +$text-inputs-list: 'input[type="color"]', + 'input[type="date"]', + 'input[type="datetime"]', + 'input[type="datetime-local"]', + 'input[type="email"]', + 'input[type="month"]', + 'input[type="number"]', + 'input[type="password"]', + 'input[type="search"]', + 'input[type="tel"]', + 'input[type="text"]', + 'input[type="time"]', + 'input[type="url"]', + 'input[type="week"]', + 'input:not([type])', + 'textarea'; + +$all-text-inputs: assign-inputs($text-inputs-list); +$all-text-inputs-active: assign-inputs($text-inputs-list, active); +$all-text-inputs-focus: assign-inputs($text-inputs-list, focus); +$all-text-inputs-hover: assign-inputs($text-inputs-list, hover); diff --git a/_sass/vendors/bourbon/addons/_timing-functions.scss b/_sass/vendors/bourbon/addons/_timing-functions.scss new file mode 100755 index 0000000000..20e5f1d402 --- /dev/null +++ b/_sass/vendors/bourbon/addons/_timing-functions.scss @@ -0,0 +1,34 @@ +@charset "UTF-8"; + +/// CSS cubic-bezier timing functions. Timing functions courtesy of jquery.easie (github.com/jaukia/easie) +/// +/// Timing functions are the same as demoed here: http://jqueryui.com/resources/demos/effect/easing.html +/// +/// @type cubic-bezier + +$ease-in-quad: cubic-bezier(0.550, 0.085, 0.680, 0.530); +$ease-in-cubic: cubic-bezier(0.550, 0.055, 0.675, 0.190); +$ease-in-quart: cubic-bezier(0.895, 0.030, 0.685, 0.220); +$ease-in-quint: cubic-bezier(0.755, 0.050, 0.855, 0.060); +$ease-in-sine: cubic-bezier(0.470, 0.000, 0.745, 0.715); +$ease-in-expo: cubic-bezier(0.950, 0.050, 0.795, 0.035); +$ease-in-circ: cubic-bezier(0.600, 0.040, 0.980, 0.335); +$ease-in-back: cubic-bezier(0.600, -0.280, 0.735, 0.045); + +$ease-out-quad: cubic-bezier(0.250, 0.460, 0.450, 0.940); +$ease-out-cubic: cubic-bezier(0.215, 0.610, 0.355, 1.000); +$ease-out-quart: cubic-bezier(0.165, 0.840, 0.440, 1.000); +$ease-out-quint: cubic-bezier(0.230, 1.000, 0.320, 1.000); +$ease-out-sine: cubic-bezier(0.390, 0.575, 0.565, 1.000); +$ease-out-expo: cubic-bezier(0.190, 1.000, 0.220, 1.000); +$ease-out-circ: cubic-bezier(0.075, 0.820, 0.165, 1.000); +$ease-out-back: cubic-bezier(0.175, 0.885, 0.320, 1.275); + +$ease-in-out-quad: cubic-bezier(0.455, 0.030, 0.515, 0.955); +$ease-in-out-cubic: cubic-bezier(0.645, 0.045, 0.355, 1.000); +$ease-in-out-quart: cubic-bezier(0.770, 0.000, 0.175, 1.000); +$ease-in-out-quint: cubic-bezier(0.860, 0.000, 0.070, 1.000); +$ease-in-out-sine: cubic-bezier(0.445, 0.050, 0.550, 0.950); +$ease-in-out-expo: cubic-bezier(1.000, 0.000, 0.000, 1.000); +$ease-in-out-circ: cubic-bezier(0.785, 0.135, 0.150, 0.860); +$ease-in-out-back: cubic-bezier(0.680, -0.550, 0.265, 1.550); diff --git a/_sass/vendors/bourbon/addons/_triangle.scss b/_sass/vendors/bourbon/addons/_triangle.scss new file mode 100755 index 0000000000..8a1ed9cd08 --- /dev/null +++ b/_sass/vendors/bourbon/addons/_triangle.scss @@ -0,0 +1,63 @@ +@mixin triangle($size, $color, $direction) { + $width: nth($size, 1); + $height: nth($size, length($size)); + $foreground-color: nth($color, 1); + $background-color: if(length($color) == 2, nth($color, 2), transparent); + height: 0; + width: 0; + + @if ($direction == up) or ($direction == down) or ($direction == right) or ($direction == left) { + $width: $width / 2; + $height: if(length($size) > 1, $height, $height/2); + + @if $direction == up { + border-bottom: $height solid $foreground-color; + border-left: $width solid $background-color; + border-right: $width solid $background-color; + } @else if $direction == right { + border-bottom: $width solid $background-color; + border-left: $height solid $foreground-color; + border-top: $width solid $background-color; + } @else if $direction == down { + border-left: $width solid $background-color; + border-right: $width solid $background-color; + border-top: $height solid $foreground-color; + } @else if $direction == left { + border-bottom: $width solid $background-color; + border-right: $height solid $foreground-color; + border-top: $width solid $background-color; + } + } @else if ($direction == up-right) or ($direction == up-left) { + border-top: $height solid $foreground-color; + + @if $direction == up-right { + border-left: $width solid $background-color; + } @else if $direction == up-left { + border-right: $width solid $background-color; + } + } @else if ($direction == down-right) or ($direction == down-left) { + border-bottom: $height solid $foreground-color; + + @if $direction == down-right { + border-left: $width solid $background-color; + } @else if $direction == down-left { + border-right: $width solid $background-color; + } + } @else if ($direction == inset-up) { + border-color: $background-color $background-color $foreground-color; + border-style: solid; + border-width: $height $width; + } @else if ($direction == inset-down) { + border-color: $foreground-color $background-color $background-color; + border-style: solid; + border-width: $height $width; + } @else if ($direction == inset-right) { + border-color: $background-color $background-color $background-color $foreground-color; + border-style: solid; + border-width: $width $height; + } @else if ($direction == inset-left) { + border-color: $background-color $foreground-color $background-color $background-color; + border-style: solid; + border-width: $width $height; + } +} diff --git a/_sass/vendors/bourbon/addons/_word-wrap.scss b/_sass/vendors/bourbon/addons/_word-wrap.scss new file mode 100755 index 0000000000..64856a925a --- /dev/null +++ b/_sass/vendors/bourbon/addons/_word-wrap.scss @@ -0,0 +1,29 @@ +@charset "UTF-8"; + +/// Provides an easy way to change the `word-wrap` property. +/// +/// @param {String} $wrap [break-word] +/// Value for the `word-break` property. +/// +/// @example scss - Usage +/// .wrapper { +/// @include word-wrap(break-word); +/// } +/// +/// @example css - CSS Output +/// .wrapper { +/// overflow-wrap: break-word; +/// word-break: break-all; +/// word-wrap: break-word; +/// } + +@mixin word-wrap($wrap: break-word) { + overflow-wrap: $wrap; + word-wrap: $wrap; + + @if $wrap == break-word { + word-break: break-all; + } @else { + word-break: $wrap; + } +} diff --git a/_sass/vendors/bourbon/css3/_animation.scss b/_sass/vendors/bourbon/css3/_animation.scss new file mode 100755 index 0000000000..aac675f5a1 --- /dev/null +++ b/_sass/vendors/bourbon/css3/_animation.scss @@ -0,0 +1,43 @@ +// http://www.w3.org/TR/css3-animations/#the-animation-name-property- +// Each of these mixins support comma separated lists of values, which allows different transitions for individual properties to be described in a single style rule. Each value in the list corresponds to the value at that same position in the other properties. + +@mixin animation($animations...) { + @include prefixer(animation, $animations, webkit moz spec); +} + +@mixin animation-name($names...) { + @include prefixer(animation-name, $names, webkit moz spec); +} + +@mixin animation-duration($times...) { + @include prefixer(animation-duration, $times, webkit moz spec); +} + +@mixin animation-timing-function($motions...) { + // ease | linear | ease-in | ease-out | ease-in-out + @include prefixer(animation-timing-function, $motions, webkit moz spec); +} + +@mixin animation-iteration-count($values...) { + // infinite | + @include prefixer(animation-iteration-count, $values, webkit moz spec); +} + +@mixin animation-direction($directions...) { + // normal | alternate + @include prefixer(animation-direction, $directions, webkit moz spec); +} + +@mixin animation-play-state($states...) { + // running | paused + @include prefixer(animation-play-state, $states, webkit moz spec); +} + +@mixin animation-delay($times...) { + @include prefixer(animation-delay, $times, webkit moz spec); +} + +@mixin animation-fill-mode($modes...) { + // none | forwards | backwards | both + @include prefixer(animation-fill-mode, $modes, webkit moz spec); +} diff --git a/_sass/vendors/bourbon/css3/_appearance.scss b/_sass/vendors/bourbon/css3/_appearance.scss new file mode 100755 index 0000000000..abddc02047 --- /dev/null +++ b/_sass/vendors/bourbon/css3/_appearance.scss @@ -0,0 +1,3 @@ +@mixin appearance($value) { + @include prefixer(appearance, $value, webkit moz ms o spec); +} diff --git a/_sass/vendors/bourbon/css3/_backface-visibility.scss b/_sass/vendors/bourbon/css3/_backface-visibility.scss new file mode 100755 index 0000000000..fc68e2dd02 --- /dev/null +++ b/_sass/vendors/bourbon/css3/_backface-visibility.scss @@ -0,0 +1,3 @@ +@mixin backface-visibility($visibility) { + @include prefixer(backface-visibility, $visibility, webkit spec); +} diff --git a/_sass/vendors/bourbon/css3/_background-image.scss b/_sass/vendors/bourbon/css3/_background-image.scss new file mode 100755 index 0000000000..6ed19ab580 --- /dev/null +++ b/_sass/vendors/bourbon/css3/_background-image.scss @@ -0,0 +1,42 @@ +//************************************************************************// +// Background-image property for adding multiple background images with +// gradients, or for stringing multiple gradients together. +//************************************************************************// + +@mixin background-image($images...) { + $webkit-images: (); + $spec-images: (); + + @each $image in $images { + $webkit-image: (); + $spec-image: (); + + @if (type-of($image) == string) { + $url-str: str-slice($image, 1, 3); + $gradient-type: str-slice($image, 1, 6); + + @if $url-str == "url" { + $webkit-image: $image; + $spec-image: $image; + } + + @else if $gradient-type == "linear" { + $gradients: _linear-gradient-parser($image); + $webkit-image: map-get($gradients, webkit-image); + $spec-image: map-get($gradients, spec-image); + } + + @else if $gradient-type == "radial" { + $gradients: _radial-gradient-parser($image); + $webkit-image: map-get($gradients, webkit-image); + $spec-image: map-get($gradients, spec-image); + } + } + + $webkit-images: append($webkit-images, $webkit-image, comma); + $spec-images: append($spec-images, $spec-image, comma); + } + + background-image: $webkit-images; + background-image: $spec-images; +} diff --git a/_sass/vendors/bourbon/css3/_background.scss b/_sass/vendors/bourbon/css3/_background.scss new file mode 100755 index 0000000000..019db0ed3d --- /dev/null +++ b/_sass/vendors/bourbon/css3/_background.scss @@ -0,0 +1,55 @@ +//************************************************************************// +// Background property for adding multiple backgrounds using shorthand +// notation. +//************************************************************************// + +@mixin background($backgrounds...) { + $webkit-backgrounds: (); + $spec-backgrounds: (); + + @each $background in $backgrounds { + $webkit-background: (); + $spec-background: (); + $background-type: type-of($background); + + @if $background-type == string or $background-type == list { + $background-str: if($background-type == list, nth($background, 1), $background); + + $url-str: str-slice($background-str, 1, 3); + $gradient-type: str-slice($background-str, 1, 6); + + @if $url-str == "url" { + $webkit-background: $background; + $spec-background: $background; + } + + @else if $gradient-type == "linear" { + $gradients: _linear-gradient-parser("#{$background}"); + $webkit-background: map-get($gradients, webkit-image); + $spec-background: map-get($gradients, spec-image); + } + + @else if $gradient-type == "radial" { + $gradients: _radial-gradient-parser("#{$background}"); + $webkit-background: map-get($gradients, webkit-image); + $spec-background: map-get($gradients, spec-image); + } + + @else { + $webkit-background: $background; + $spec-background: $background; + } + } + + @else { + $webkit-background: $background; + $spec-background: $background; + } + + $webkit-backgrounds: append($webkit-backgrounds, $webkit-background, comma); + $spec-backgrounds: append($spec-backgrounds, $spec-background, comma); + } + + background: $webkit-backgrounds; + background: $spec-backgrounds; +} diff --git a/_sass/vendors/bourbon/css3/_border-image.scss b/_sass/vendors/bourbon/css3/_border-image.scss new file mode 100755 index 0000000000..cf568ce6db --- /dev/null +++ b/_sass/vendors/bourbon/css3/_border-image.scss @@ -0,0 +1,59 @@ +@mixin border-image($borders...) { + $webkit-borders: (); + $spec-borders: (); + + @each $border in $borders { + $webkit-border: (); + $spec-border: (); + $border-type: type-of($border); + + @if $border-type == string or list { + $border-str: if($border-type == list, nth($border, 1), $border); + + $url-str: str-slice($border-str, 1, 3); + $gradient-type: str-slice($border-str, 1, 6); + + @if $url-str == "url" { + $webkit-border: $border; + $spec-border: $border; + } + + @else if $gradient-type == "linear" { + $gradients: _linear-gradient-parser("#{$border}"); + $webkit-border: map-get($gradients, webkit-image); + $spec-border: map-get($gradients, spec-image); + } + + @else if $gradient-type == "radial" { + $gradients: _radial-gradient-parser("#{$border}"); + $webkit-border: map-get($gradients, webkit-image); + $spec-border: map-get($gradients, spec-image); + } + + @else { + $webkit-border: $border; + $spec-border: $border; + } + } + + @else { + $webkit-border: $border; + $spec-border: $border; + } + + $webkit-borders: append($webkit-borders, $webkit-border, comma); + $spec-borders: append($spec-borders, $spec-border, comma); + } + + -webkit-border-image: $webkit-borders; + border-image: $spec-borders; + border-style: solid; +} + +//Examples: +// @include border-image(url("image.png")); +// @include border-image(url("image.png") 20 stretch); +// @include border-image(linear-gradient(45deg, orange, yellow)); +// @include border-image(linear-gradient(45deg, orange, yellow) stretch); +// @include border-image(linear-gradient(45deg, orange, yellow) 20 30 40 50 stretch round); +// @include border-image(radial-gradient(top, cover, orange, yellow, orange)); diff --git a/_sass/vendors/bourbon/css3/_calc.scss b/_sass/vendors/bourbon/css3/_calc.scss new file mode 100755 index 0000000000..0bfc738ddc --- /dev/null +++ b/_sass/vendors/bourbon/css3/_calc.scss @@ -0,0 +1,4 @@ +@mixin calc($property, $value) { + #{$property}: -webkit-calc(#{$value}); + #{$property}: calc(#{$value}); +} diff --git a/_sass/vendors/bourbon/css3/_columns.scss b/_sass/vendors/bourbon/css3/_columns.scss new file mode 100755 index 0000000000..96117670cc --- /dev/null +++ b/_sass/vendors/bourbon/css3/_columns.scss @@ -0,0 +1,47 @@ +@mixin columns($arg: auto) { + // || + @include prefixer(columns, $arg, webkit moz spec); +} + +@mixin column-count($int: auto) { + // auto || integer + @include prefixer(column-count, $int, webkit moz spec); +} + +@mixin column-gap($length: normal) { + // normal || length + @include prefixer(column-gap, $length, webkit moz spec); +} + +@mixin column-fill($arg: auto) { + // auto || length + @include prefixer(column-fill, $arg, webkit moz spec); +} + +@mixin column-rule($arg) { + // || || + @include prefixer(column-rule, $arg, webkit moz spec); +} + +@mixin column-rule-color($color) { + @include prefixer(column-rule-color, $color, webkit moz spec); +} + +@mixin column-rule-style($style: none) { + // none | hidden | dashed | dotted | double | groove | inset | inset | outset | ridge | solid + @include prefixer(column-rule-style, $style, webkit moz spec); +} + +@mixin column-rule-width ($width: none) { + @include prefixer(column-rule-width, $width, webkit moz spec); +} + +@mixin column-span($arg: none) { + // none || all + @include prefixer(column-span, $arg, webkit moz spec); +} + +@mixin column-width($length: auto) { + // auto || length + @include prefixer(column-width, $length, webkit moz spec); +} diff --git a/_sass/vendors/bourbon/css3/_filter.scss b/_sass/vendors/bourbon/css3/_filter.scss new file mode 100755 index 0000000000..b8f8ffb0e7 --- /dev/null +++ b/_sass/vendors/bourbon/css3/_filter.scss @@ -0,0 +1,4 @@ +@mixin filter($function: none) { + // [ + @include prefixer(perspective, $depth, webkit moz spec); +} + +@mixin perspective-origin($value: 50% 50%) { + @include prefixer(perspective-origin, $value, webkit moz spec); +} diff --git a/_sass/vendors/bourbon/css3/_placeholder.scss b/_sass/vendors/bourbon/css3/_placeholder.scss new file mode 100755 index 0000000000..5682fd097a --- /dev/null +++ b/_sass/vendors/bourbon/css3/_placeholder.scss @@ -0,0 +1,8 @@ +@mixin placeholder { + $placeholders: ":-webkit-input" ":-moz" "-moz" "-ms-input"; + @each $placeholder in $placeholders { + &:#{$placeholder}-placeholder { + @content; + } + } +} diff --git a/_sass/vendors/bourbon/css3/_radial-gradient.scss b/_sass/vendors/bourbon/css3/_radial-gradient.scss new file mode 100755 index 0000000000..18f7b5b589 --- /dev/null +++ b/_sass/vendors/bourbon/css3/_radial-gradient.scss @@ -0,0 +1,39 @@ +// Requires Sass 3.1+ +@mixin radial-gradient($g1, $g2, + $g3: null, $g4: null, + $g5: null, $g6: null, + $g7: null, $g8: null, + $g9: null, $g10: null, + $pos: null, + $shape-size: null, + $fallback: null) { + + $data: _radial-arg-parser($g1, $g2, $pos, $shape-size); + $g1: nth($data, 1); + $g2: nth($data, 2); + $pos: nth($data, 3); + $shape-size: nth($data, 4); + + $full: $g1, $g2, $g3, $g4, $g5, $g6, $g7, $g8, $g9, $g10; + + // Strip deprecated cover/contain for spec + $shape-size-spec: _shape-size-stripper($shape-size); + + // Set $g1 as the default fallback color + $first-color: nth($full, 1); + $fallback-color: nth($first-color, 1); + + @if (type-of($fallback) == color) or ($fallback == "transparent") { + $fallback-color: $fallback; + } + + // Add Commas and spaces + $shape-size: if($shape-size, "#{$shape-size}, ", null); + $pos: if($pos, "#{$pos}, ", null); + $pos-spec: if($pos, "at #{$pos}", null); + $shape-size-spec: if(($shape-size-spec != " ") and ($pos == null), "#{$shape-size-spec}, ", "#{$shape-size-spec} "); + + background-color: $fallback-color; + background-image: -webkit-radial-gradient(#{$pos}#{$shape-size}#{$full}); + background-image: radial-gradient(#{$shape-size-spec}#{$pos-spec}#{$full}); +} diff --git a/_sass/vendors/bourbon/css3/_selection.scss b/_sass/vendors/bourbon/css3/_selection.scss new file mode 100755 index 0000000000..cd71d4f534 --- /dev/null +++ b/_sass/vendors/bourbon/css3/_selection.scss @@ -0,0 +1,42 @@ +@charset "UTF-8"; + +/// Outputs the spec and prefixed versions of the `::selection` pseudo-element. +/// +/// @param {Bool} $current-selector [false] +/// If set to `true`, it takes the current element into consideration. +/// +/// @example scss - Usage +/// .element { +/// @include selection(true) { +/// background-color: #ffbb52; +/// } +/// } +/// +/// @example css - CSS Output +/// .element::-moz-selection { +/// background-color: #ffbb52; +/// } +/// +/// .element::selection { +/// background-color: #ffbb52; +/// } + +@mixin selection($current-selector: false) { + @if $current-selector { + &::-moz-selection { + @content; + } + + &::selection { + @content; + } + } @else { + ::-moz-selection { + @content; + } + + ::selection { + @content; + } + } +} diff --git a/_sass/vendors/bourbon/css3/_text-decoration.scss b/_sass/vendors/bourbon/css3/_text-decoration.scss new file mode 100755 index 0000000000..9222746ce1 --- /dev/null +++ b/_sass/vendors/bourbon/css3/_text-decoration.scss @@ -0,0 +1,19 @@ +@mixin text-decoration($value) { +// || || + @include prefixer(text-decoration, $value, moz); +} + +@mixin text-decoration-line($line: none) { +// none || underline || overline || line-through + @include prefixer(text-decoration-line, $line, moz); +} + +@mixin text-decoration-style($style: solid) { +// solid || double || dotted || dashed || wavy + @include prefixer(text-decoration-style, $style, moz webkit); +} + +@mixin text-decoration-color($color: currentColor) { +// currentColor || + @include prefixer(text-decoration-color, $color, moz); +} diff --git a/_sass/vendors/bourbon/css3/_transform.scss b/_sass/vendors/bourbon/css3/_transform.scss new file mode 100755 index 0000000000..8ee6509ff6 --- /dev/null +++ b/_sass/vendors/bourbon/css3/_transform.scss @@ -0,0 +1,15 @@ +@mixin transform($property: none) { + // none | + @include prefixer(transform, $property, webkit moz ms o spec); +} + +@mixin transform-origin($axes: 50%) { + // x-axis - left | center | right | length | % + // y-axis - top | center | bottom | length | % + // z-axis - length + @include prefixer(transform-origin, $axes, webkit moz ms o spec); +} + +@mixin transform-style($style: flat) { + @include prefixer(transform-style, $style, webkit moz ms o spec); +} diff --git a/_sass/vendors/bourbon/css3/_transition.scss b/_sass/vendors/bourbon/css3/_transition.scss new file mode 100755 index 0000000000..3c785ed527 --- /dev/null +++ b/_sass/vendors/bourbon/css3/_transition.scss @@ -0,0 +1,71 @@ +// Shorthand mixin. Supports multiple parentheses-deliminated values for each variable. +// Example: @include transition (all 2s ease-in-out); +// @include transition (opacity 1s ease-in 2s, width 2s ease-out); +// @include transition-property (transform, opacity); + +@mixin transition($properties...) { + // Fix for vendor-prefix transform property + $needs-prefixes: false; + $webkit: (); + $moz: (); + $spec: (); + + // Create lists for vendor-prefixed transform + @each $list in $properties { + @if nth($list, 1) == "transform" { + $needs-prefixes: true; + $list1: -webkit-transform; + $list2: -moz-transform; + $list3: (); + + @each $var in $list { + $list3: join($list3, $var); + + @if $var != "transform" { + $list1: join($list1, $var); + $list2: join($list2, $var); + } + } + + $webkit: append($webkit, $list1); + $moz: append($moz, $list2); + $spec: append($spec, $list3); + } @else { + $webkit: append($webkit, $list, comma); + $moz: append($moz, $list, comma); + $spec: append($spec, $list, comma); + } + } + + @if $needs-prefixes { + -webkit-transition: $webkit; + -moz-transition: $moz; + transition: $spec; + } @else { + @if length($properties) >= 1 { + @include prefixer(transition, $properties, webkit moz spec); + } @else { + $properties: all 0.15s ease-out 0s; + @include prefixer(transition, $properties, webkit moz spec); + } + } +} + +@mixin transition-property($properties...) { + -webkit-transition-property: transition-property-names($properties, "webkit"); + -moz-transition-property: transition-property-names($properties, "moz"); + transition-property: transition-property-names($properties, false); +} + +@mixin transition-duration($times...) { + @include prefixer(transition-duration, $times, webkit moz spec); +} + +@mixin transition-timing-function($motions...) { + // ease | linear | ease-in | ease-out | ease-in-out | cubic-bezier() + @include prefixer(transition-timing-function, $motions, webkit moz spec); +} + +@mixin transition-delay($times...) { + @include prefixer(transition-delay, $times, webkit moz spec); +} diff --git a/_sass/vendors/bourbon/css3/_user-select.scss b/_sass/vendors/bourbon/css3/_user-select.scss new file mode 100755 index 0000000000..d4e555100d --- /dev/null +++ b/_sass/vendors/bourbon/css3/_user-select.scss @@ -0,0 +1,3 @@ +@mixin user-select($value: none) { + @include prefixer(user-select, $value, webkit moz ms spec); +} diff --git a/_sass/vendors/bourbon/functions/_assign-inputs.scss b/_sass/vendors/bourbon/functions/_assign-inputs.scss new file mode 100755 index 0000000000..f8aba96783 --- /dev/null +++ b/_sass/vendors/bourbon/functions/_assign-inputs.scss @@ -0,0 +1,11 @@ +@function assign-inputs($inputs, $pseudo: null) { + $list: (); + + @each $input in $inputs { + $input: unquote($input); + $input: if($pseudo, $input + ":" + $pseudo, $input); + $list: append($list, $input, comma); + } + + @return $list; +} diff --git a/_sass/vendors/bourbon/functions/_contains-falsy.scss b/_sass/vendors/bourbon/functions/_contains-falsy.scss new file mode 100755 index 0000000000..c096fdb92c --- /dev/null +++ b/_sass/vendors/bourbon/functions/_contains-falsy.scss @@ -0,0 +1,20 @@ +@charset "UTF-8"; + +/// Checks if a list does not contains a value. +/// +/// @access private +/// +/// @param {List} $list +/// The list to check against. +/// +/// @return {Bool} + +@function contains-falsy($list) { + @each $item in $list { + @if not $item { + @return true; + } + } + + @return false; +} diff --git a/_sass/vendors/bourbon/functions/_contains.scss b/_sass/vendors/bourbon/functions/_contains.scss new file mode 100755 index 0000000000..3dec27db82 --- /dev/null +++ b/_sass/vendors/bourbon/functions/_contains.scss @@ -0,0 +1,26 @@ +@charset "UTF-8"; + +/// Checks if a list contains a value(s). +/// +/// @access private +/// +/// @param {List} $list +/// The list to check against. +/// +/// @param {List} $values +/// A single value or list of values to check for. +/// +/// @example scss - Usage +/// contains($list, $value) +/// +/// @return {Bool} + +@function contains($list, $values...) { + @each $value in $values { + @if type-of(index($list, $value)) != "number" { + @return false; + } + } + + @return true; +} diff --git a/_sass/vendors/bourbon/functions/_is-length.scss b/_sass/vendors/bourbon/functions/_is-length.scss new file mode 100755 index 0000000000..5826e789b7 --- /dev/null +++ b/_sass/vendors/bourbon/functions/_is-length.scss @@ -0,0 +1,11 @@ +@charset "UTF-8"; + +/// Checks for a valid CSS length. +/// +/// @param {String} $value + +@function is-length($value) { + @return type-of($value) != "null" and (str-slice($value + "", 1, 4) == "calc" + or index(auto inherit initial 0, $value) + or (type-of($value) == "number" and not(unitless($value)))); +} diff --git a/_sass/vendors/bourbon/functions/_is-light.scss b/_sass/vendors/bourbon/functions/_is-light.scss new file mode 100755 index 0000000000..92d90ac3cc --- /dev/null +++ b/_sass/vendors/bourbon/functions/_is-light.scss @@ -0,0 +1,21 @@ +@charset "UTF-8"; + +/// Programatically determines whether a color is light or dark. +/// +/// @link http://robots.thoughtbot.com/closer-look-color-lightness +/// +/// @param {Color (Hex)} $color +/// +/// @example scss - Usage +/// is-light($color) +/// +/// @return {Bool} + +@function is-light($hex-color) { + $-local-red: red(rgba($hex-color, 1)); + $-local-green: green(rgba($hex-color, 1)); + $-local-blue: blue(rgba($hex-color, 1)); + $-local-lightness: ($-local-red * 0.2126 + $-local-green * 0.7152 + $-local-blue * 0.0722) / 255; + + @return $-local-lightness > 0.6; +} diff --git a/_sass/vendors/bourbon/functions/_is-number.scss b/_sass/vendors/bourbon/functions/_is-number.scss new file mode 100755 index 0000000000..a64e0bf219 --- /dev/null +++ b/_sass/vendors/bourbon/functions/_is-number.scss @@ -0,0 +1,11 @@ +@charset "UTF-8"; + +/// Checks for a valid number. +/// +/// @param {Number} $value +/// +/// @require {function} contains + +@function is-number($value) { + @return contains("0" "1" "2" "3" "4" "5" "6" "7" "8" "9" 0 1 2 3 4 5 6 7 8 9, $value); +} diff --git a/_sass/vendors/bourbon/functions/_is-size.scss b/_sass/vendors/bourbon/functions/_is-size.scss new file mode 100755 index 0000000000..661789ab49 --- /dev/null +++ b/_sass/vendors/bourbon/functions/_is-size.scss @@ -0,0 +1,13 @@ +@charset "UTF-8"; + +/// Checks for a valid CSS size. +/// +/// @param {String} $value +/// +/// @require {function} contains +/// @require {function} is-length + +@function is-size($value) { + @return is-length($value) + or contains("fill" "fit-content" "min-content" "max-content", $value); +} diff --git a/_sass/vendors/bourbon/functions/_modular-scale.scss b/_sass/vendors/bourbon/functions/_modular-scale.scss new file mode 100755 index 0000000000..20fa38812d --- /dev/null +++ b/_sass/vendors/bourbon/functions/_modular-scale.scss @@ -0,0 +1,69 @@ +// Scaling Variables +$golden: 1.618; +$minor-second: 1.067; +$major-second: 1.125; +$minor-third: 1.2; +$major-third: 1.25; +$perfect-fourth: 1.333; +$augmented-fourth: 1.414; +$perfect-fifth: 1.5; +$minor-sixth: 1.6; +$major-sixth: 1.667; +$minor-seventh: 1.778; +$major-seventh: 1.875; +$octave: 2; +$major-tenth: 2.5; +$major-eleventh: 2.667; +$major-twelfth: 3; +$double-octave: 4; + +$modular-scale-ratio: $perfect-fourth !default; +$modular-scale-base: em($em-base) !default; + +@function modular-scale($increment, $value: $modular-scale-base, $ratio: $modular-scale-ratio) { + $v1: nth($value, 1); + $v2: nth($value, length($value)); + $value: $v1; + + // scale $v2 to just above $v1 + @while $v2 > $v1 { + $v2: ($v2 / $ratio); // will be off-by-1 + } + @while $v2 < $v1 { + $v2: ($v2 * $ratio); // will fix off-by-1 + } + + // check AFTER scaling $v2 to prevent double-counting corner-case + $double-stranded: $v2 > $v1; + + @if $increment > 0 { + @for $i from 1 through $increment { + @if $double-stranded and ($v1 * $ratio) > $v2 { + $value: $v2; + $v2: ($v2 * $ratio); + } @else { + $v1: ($v1 * $ratio); + $value: $v1; + } + } + } + + @if $increment < 0 { + // adjust $v2 to just below $v1 + @if $double-stranded { + $v2: ($v2 / $ratio); + } + + @for $i from $increment through -1 { + @if $double-stranded and ($v1 / $ratio) < $v2 { + $value: $v2; + $v2: ($v2 / $ratio); + } @else { + $v1: ($v1 / $ratio); + $value: $v1; + } + } + } + + @return $value; +} diff --git a/_sass/vendors/bourbon/functions/_px-to-em.scss b/_sass/vendors/bourbon/functions/_px-to-em.scss new file mode 100755 index 0000000000..ae81a44ada --- /dev/null +++ b/_sass/vendors/bourbon/functions/_px-to-em.scss @@ -0,0 +1,13 @@ +// Convert pixels to ems +// eg. for a relational value of 12px write em(12) when the parent is 16px +// if the parent is another value say 24px write em(12, 24) + +@function em($pxval, $base: $em-base) { + @if not unitless($pxval) { + $pxval: strip-units($pxval); + } + @if not unitless($base) { + $base: strip-units($base); + } + @return ($pxval / $base) * 1em; +} diff --git a/_sass/vendors/bourbon/functions/_px-to-rem.scss b/_sass/vendors/bourbon/functions/_px-to-rem.scss new file mode 100755 index 0000000000..0ac941e76b --- /dev/null +++ b/_sass/vendors/bourbon/functions/_px-to-rem.scss @@ -0,0 +1,15 @@ +// Convert pixels to rems +// eg. for a relational value of 12px write rem(12) +// Assumes $em-base is the font-size of + +@function rem($pxval) { + @if not unitless($pxval) { + $pxval: strip-units($pxval); + } + + $base: $em-base; + @if not unitless($base) { + $base: strip-units($base); + } + @return ($pxval / $base) * 1rem; +} diff --git a/_sass/vendors/bourbon/functions/_shade.scss b/_sass/vendors/bourbon/functions/_shade.scss new file mode 100755 index 0000000000..8aaf2c6d28 --- /dev/null +++ b/_sass/vendors/bourbon/functions/_shade.scss @@ -0,0 +1,24 @@ +@charset "UTF-8"; + +/// Mixes a color with black. +/// +/// @param {Color} $color +/// +/// @param {Number (Percentage)} $percent +/// The amount of black to be mixed in. +/// +/// @example scss - Usage +/// .element { +/// background-color: shade(#ffbb52, 60%); +/// } +/// +/// @example css - CSS Output +/// .element { +/// background-color: #664a20; +/// } +/// +/// @return {Color} + +@function shade($color, $percent) { + @return mix(#000, $color, $percent); +} diff --git a/_sass/vendors/bourbon/functions/_strip-units.scss b/_sass/vendors/bourbon/functions/_strip-units.scss new file mode 100755 index 0000000000..6c5f3e8104 --- /dev/null +++ b/_sass/vendors/bourbon/functions/_strip-units.scss @@ -0,0 +1,17 @@ +@charset "UTF-8"; + +/// Strips the unit from a number. +/// +/// @param {Number (With Unit)} $value +/// +/// @example scss - Usage +/// $dimension: strip-units(10em); +/// +/// @example css - CSS Output +/// $dimension: 10; +/// +/// @return {Number (Unitless)} + +@function strip-units($value) { + @return ($value / ($value * 0 + 1)); +} diff --git a/_sass/vendors/bourbon/functions/_tint.scss b/_sass/vendors/bourbon/functions/_tint.scss new file mode 100755 index 0000000000..2e3381488d --- /dev/null +++ b/_sass/vendors/bourbon/functions/_tint.scss @@ -0,0 +1,24 @@ +@charset "UTF-8"; + +/// Mixes a color with white. +/// +/// @param {Color} $color +/// +/// @param {Number (Percentage)} $percent +/// The amount of white to be mixed in. +/// +/// @example scss - Usage +/// .element { +/// background-color: tint(#6ecaa6, 40%); +/// } +/// +/// @example css - CSS Output +/// .element { +/// background-color: #a8dfc9; +/// } +/// +/// @return {Color} + +@function tint($color, $percent) { + @return mix(#fff, $color, $percent); +} diff --git a/_sass/vendors/bourbon/functions/_transition-property-name.scss b/_sass/vendors/bourbon/functions/_transition-property-name.scss new file mode 100755 index 0000000000..18348b93ab --- /dev/null +++ b/_sass/vendors/bourbon/functions/_transition-property-name.scss @@ -0,0 +1,22 @@ +// Return vendor-prefixed property names if appropriate +// Example: transition-property-names((transform, color, background), moz) -> -moz-transform, color, background +//************************************************************************// +@function transition-property-names($props, $vendor: false) { + $new-props: (); + + @each $prop in $props { + $new-props: append($new-props, transition-property-name($prop, $vendor), comma); + } + + @return $new-props; +} + +@function transition-property-name($prop, $vendor: false) { + // put other properties that need to be prefixed here aswell + @if $vendor and $prop == transform { + @return unquote('-'+$vendor+'-'+$prop); + } + @else { + @return $prop; + } +} diff --git a/_sass/vendors/bourbon/functions/_unpack.scss b/_sass/vendors/bourbon/functions/_unpack.scss new file mode 100755 index 0000000000..4367935d52 --- /dev/null +++ b/_sass/vendors/bourbon/functions/_unpack.scss @@ -0,0 +1,27 @@ +@charset "UTF-8"; + +/// Converts shorthand to the 4-value syntax. +/// +/// @param {List} $shorthand +/// +/// @example scss - Usage +/// .element { +/// margin: unpack(1em 2em); +/// } +/// +/// @example css - CSS Output +/// .element { +/// margin: 1em 2em 1em 2em; +/// } + +@function unpack($shorthand) { + @if length($shorthand) == 1 { + @return nth($shorthand, 1) nth($shorthand, 1) nth($shorthand, 1) nth($shorthand, 1); + } @else if length($shorthand) == 2 { + @return nth($shorthand, 1) nth($shorthand, 2) nth($shorthand, 1) nth($shorthand, 2); + } @else if length($shorthand) == 3 { + @return nth($shorthand, 1) nth($shorthand, 2) nth($shorthand, 3) nth($shorthand, 2); + } @else { + @return $shorthand; + } +} diff --git a/_sass/vendors/bourbon/helpers/_convert-units.scss b/_sass/vendors/bourbon/helpers/_convert-units.scss new file mode 100755 index 0000000000..e0a65a05c2 --- /dev/null +++ b/_sass/vendors/bourbon/helpers/_convert-units.scss @@ -0,0 +1,21 @@ +//************************************************************************// +// Helper function for str-to-num fn. +// Source: http://sassmeister.com/gist/9647408 +//************************************************************************// +@function _convert-units($number, $unit) { + $strings: "px", "cm", "mm", "%", "ch", "pica", "in", "em", "rem", "pt", "pc", "ex", "vw", "vh", "vmin", "vmax", "deg", "rad", "grad", "turn"; + $units: 1px, 1cm, 1mm, 1%, 1ch, 1pica, 1in, 1em, 1rem, 1pt, 1pc, 1ex, 1vw, 1vh, 1vmin, 1vmax, 1deg, 1rad, 1grad, 1turn; + $index: index($strings, $unit); + + @if not $index { + @warn "Unknown unit `#{$unit}`."; + @return false; + } + + @if type-of($number) != "number" { + @warn "`#{$number} is not a number`"; + @return false; + } + + @return $number * nth($units, $index); +} diff --git a/_sass/vendors/bourbon/helpers/_directional-values.scss b/_sass/vendors/bourbon/helpers/_directional-values.scss new file mode 100755 index 0000000000..6ee538db48 --- /dev/null +++ b/_sass/vendors/bourbon/helpers/_directional-values.scss @@ -0,0 +1,96 @@ +@charset "UTF-8"; + +/// Directional-property mixins are shorthands for writing properties like the following +/// +/// @ignore You can also use `false` instead of `null`. +/// +/// @param {List} $vals +/// List of directional values +/// +/// @example scss - Usage +/// .element { +/// @include border-style(dotted null); +/// @include margin(null 0 10px); +/// } +/// +/// @example css - CSS Output +/// .element { +/// border-bottom-style: dotted; +/// border-top-style: dotted; +/// margin-bottom: 10px; +/// margin-left: 0; +/// margin-right: 0; +/// } +/// +/// @require {function} contains-falsy +/// +/// @return {List} + +@function collapse-directionals($vals) { + $output: null; + + $a: nth($vals, 1); + $b: if(length($vals) < 2, $a, nth($vals, 2)); + $c: if(length($vals) < 3, $a, nth($vals, 3)); + $d: if(length($vals) < 2, $a, nth($vals, if(length($vals) < 4, 2, 4))); + + @if $a == 0 { $a: 0; } + @if $b == 0 { $b: 0; } + @if $c == 0 { $c: 0; } + @if $d == 0 { $d: 0; } + + @if $a == $b and $a == $c and $a == $d { $output: $a; } + @else if $a == $c and $b == $d { $output: $a $b; } + @else if $b == $d { $output: $a $b $c; } + @else { $output: $a $b $c $d; } + + @return $output; +} + +/// Output directional properties, for instance `margin`. +/// +/// @access private +/// +/// @param {String} $pre +/// Prefix to use +/// @param {String} $suf +/// Suffix to use +/// @param {List} $vals +/// List of values +/// +/// @require {function} collapse-directionals +/// @require {function} contains-falsy + +@mixin directional-property($pre, $suf, $vals) { + // Property Names + $top: $pre + "-top" + if($suf, "-#{$suf}", ""); + $bottom: $pre + "-bottom" + if($suf, "-#{$suf}", ""); + $left: $pre + "-left" + if($suf, "-#{$suf}", ""); + $right: $pre + "-right" + if($suf, "-#{$suf}", ""); + $all: $pre + if($suf, "-#{$suf}", ""); + + $vals: collapse-directionals($vals); + + @if contains-falsy($vals) { + @if nth($vals, 1) { #{$top}: nth($vals, 1); } + + @if length($vals) == 1 { + @if nth($vals, 1) { #{$right}: nth($vals, 1); } + } @else { + @if nth($vals, 2) { #{$right}: nth($vals, 2); } + } + + @if length($vals) == 2 { + @if nth($vals, 1) { #{$bottom}: nth($vals, 1); } + @if nth($vals, 2) { #{$left}: nth($vals, 2); } + } @else if length($vals) == 3 { + @if nth($vals, 3) { #{$bottom}: nth($vals, 3); } + @if nth($vals, 2) { #{$left}: nth($vals, 2); } + } @else if length($vals) == 4 { + @if nth($vals, 3) { #{$bottom}: nth($vals, 3); } + @if nth($vals, 4) { #{$left}: nth($vals, 4); } + } + } @else { + #{$all}: $vals; + } +} diff --git a/_sass/vendors/bourbon/helpers/_font-source-declaration.scss b/_sass/vendors/bourbon/helpers/_font-source-declaration.scss new file mode 100755 index 0000000000..7f17586c93 --- /dev/null +++ b/_sass/vendors/bourbon/helpers/_font-source-declaration.scss @@ -0,0 +1,43 @@ +// Used for creating the source string for fonts using @font-face +// Reference: http://goo.gl/Ru1bKP + +@function font-url-prefixer($asset-pipeline) { + @if $asset-pipeline == true { + @return font-url; + } @else { + @return url; + } +} + +@function font-source-declaration( + $font-family, + $file-path, + $asset-pipeline, + $file-formats, + $font-url) { + + $src: (); + + $formats-map: ( + eot: "#{$file-path}.eot?#iefix" format("embedded-opentype"), + woff2: "#{$file-path}.woff2" format("woff2"), + woff: "#{$file-path}.woff" format("woff"), + ttf: "#{$file-path}.ttf" format("truetype"), + svg: "#{$file-path}.svg##{$font-family}" format("svg") + ); + + @each $key, $values in $formats-map { + @if contains($file-formats, $key) { + $file-path: nth($values, 1); + $font-format: nth($values, 2); + + @if $asset-pipeline == true { + $src: append($src, font-url($file-path) $font-format, comma); + } @else { + $src: append($src, url($file-path) $font-format, comma); + } + } + } + + @return $src; +} diff --git a/_sass/vendors/bourbon/helpers/_gradient-positions-parser.scss b/_sass/vendors/bourbon/helpers/_gradient-positions-parser.scss new file mode 100755 index 0000000000..07d30b6cf9 --- /dev/null +++ b/_sass/vendors/bourbon/helpers/_gradient-positions-parser.scss @@ -0,0 +1,13 @@ +@function _gradient-positions-parser($gradient-type, $gradient-positions) { + @if $gradient-positions + and ($gradient-type == linear) + and (type-of($gradient-positions) != color) { + $gradient-positions: _linear-positions-parser($gradient-positions); + } + @else if $gradient-positions + and ($gradient-type == radial) + and (type-of($gradient-positions) != color) { + $gradient-positions: _radial-positions-parser($gradient-positions); + } + @return $gradient-positions; +} diff --git a/_sass/vendors/bourbon/helpers/_linear-angle-parser.scss b/_sass/vendors/bourbon/helpers/_linear-angle-parser.scss new file mode 100755 index 0000000000..e0401ed8df --- /dev/null +++ b/_sass/vendors/bourbon/helpers/_linear-angle-parser.scss @@ -0,0 +1,25 @@ +// Private function for linear-gradient-parser +@function _linear-angle-parser($image, $first-val, $prefix, $suffix) { + $offset: null; + $unit-short: str-slice($first-val, str-length($first-val) - 2, str-length($first-val)); + $unit-long: str-slice($first-val, str-length($first-val) - 3, str-length($first-val)); + + @if ($unit-long == "grad") or + ($unit-long == "turn") { + $offset: if($unit-long == "grad", -100grad * 3, -0.75turn); + } + + @else if ($unit-short == "deg") or + ($unit-short == "rad") { + $offset: if($unit-short == "deg", -90 * 3, 1.6rad); + } + + @if $offset { + $num: _str-to-num($first-val); + + @return ( + webkit-image: -webkit- + $prefix + ($offset - $num) + $suffix, + spec-image: $image + ); + } +} diff --git a/_sass/vendors/bourbon/helpers/_linear-gradient-parser.scss b/_sass/vendors/bourbon/helpers/_linear-gradient-parser.scss new file mode 100755 index 0000000000..48a8f77f91 --- /dev/null +++ b/_sass/vendors/bourbon/helpers/_linear-gradient-parser.scss @@ -0,0 +1,41 @@ +@function _linear-gradient-parser($image) { + $image: unquote($image); + $gradients: (); + $start: str-index($image, "("); + $end: str-index($image, ","); + $first-val: str-slice($image, $start + 1, $end - 1); + + $prefix: str-slice($image, 1, $start); + $suffix: str-slice($image, $end, str-length($image)); + + $has-multiple-vals: str-index($first-val, " "); + $has-single-position: unquote(_position-flipper($first-val) + ""); + $has-angle: is-number(str-slice($first-val, 1, 1)); + + @if $has-multiple-vals { + $gradients: _linear-side-corner-parser($image, $first-val, $prefix, $suffix, $has-multiple-vals); + } + + @else if $has-single-position != "" { + $pos: unquote($has-single-position + ""); + + $gradients: ( + webkit-image: -webkit- + $image, + spec-image: $prefix + "to " + $pos + $suffix + ); + } + + @else if $has-angle { + // Rotate degree for webkit + $gradients: _linear-angle-parser($image, $first-val, $prefix, $suffix); + } + + @else { + $gradients: ( + webkit-image: -webkit- + $image, + spec-image: $image + ); + } + + @return $gradients; +} diff --git a/_sass/vendors/bourbon/helpers/_linear-positions-parser.scss b/_sass/vendors/bourbon/helpers/_linear-positions-parser.scss new file mode 100755 index 0000000000..96d6a6d45b --- /dev/null +++ b/_sass/vendors/bourbon/helpers/_linear-positions-parser.scss @@ -0,0 +1,61 @@ +@function _linear-positions-parser($pos) { + $type: type-of(nth($pos, 1)); + $spec: null; + $degree: null; + $side: null; + $corner: null; + $length: length($pos); + // Parse Side and corner positions + @if ($length > 1) { + @if nth($pos, 1) == "to" { // Newer syntax + $side: nth($pos, 2); + + @if $length == 2 { // eg. to top + // Swap for backwards compatibility + $degree: _position-flipper(nth($pos, 2)); + } + @else if $length == 3 { // eg. to top left + $corner: nth($pos, 3); + } + } + @else if $length == 2 { // Older syntax ("top left") + $side: _position-flipper(nth($pos, 1)); + $corner: _position-flipper(nth($pos, 2)); + } + + @if ("#{$side} #{$corner}" == "left top") or ("#{$side} #{$corner}" == "top left") { + $degree: _position-flipper(#{$side}) _position-flipper(#{$corner}); + } + @else if ("#{$side} #{$corner}" == "right top") or ("#{$side} #{$corner}" == "top right") { + $degree: _position-flipper(#{$side}) _position-flipper(#{$corner}); + } + @else if ("#{$side} #{$corner}" == "right bottom") or ("#{$side} #{$corner}" == "bottom right") { + $degree: _position-flipper(#{$side}) _position-flipper(#{$corner}); + } + @else if ("#{$side} #{$corner}" == "left bottom") or ("#{$side} #{$corner}" == "bottom left") { + $degree: _position-flipper(#{$side}) _position-flipper(#{$corner}); + } + $spec: to $side $corner; + } + @else if $length == 1 { + // Swap for backwards compatibility + @if $type == string { + $degree: $pos; + $spec: to _position-flipper($pos); + } + @else { + $degree: -270 - $pos; //rotate the gradient opposite from spec + $spec: $pos; + } + } + $degree: unquote($degree + ","); + $spec: unquote($spec + ","); + @return $degree $spec; +} + +@function _position-flipper($pos) { + @return if($pos == left, right, null) + if($pos == right, left, null) + if($pos == top, bottom, null) + if($pos == bottom, top, null); +} diff --git a/_sass/vendors/bourbon/helpers/_linear-side-corner-parser.scss b/_sass/vendors/bourbon/helpers/_linear-side-corner-parser.scss new file mode 100755 index 0000000000..7a691253d4 --- /dev/null +++ b/_sass/vendors/bourbon/helpers/_linear-side-corner-parser.scss @@ -0,0 +1,31 @@ +// Private function for linear-gradient-parser +@function _linear-side-corner-parser($image, $first-val, $prefix, $suffix, $has-multiple-vals) { + $val-1: str-slice($first-val, 1, $has-multiple-vals - 1); + $val-2: str-slice($first-val, $has-multiple-vals + 1, str-length($first-val)); + $val-3: null; + $has-val-3: str-index($val-2, " "); + + @if $has-val-3 { + $val-3: str-slice($val-2, $has-val-3 + 1, str-length($val-2)); + $val-2: str-slice($val-2, 1, $has-val-3 - 1); + } + + $pos: _position-flipper($val-1) _position-flipper($val-2) _position-flipper($val-3); + $pos: unquote($pos + ""); + + // Use old spec for webkit + @if $val-1 == "to" { + @return ( + webkit-image: -webkit- + $prefix + $pos + $suffix, + spec-image: $image + ); + } + + // Bring the code up to spec + @else { + @return ( + webkit-image: -webkit- + $image, + spec-image: $prefix + "to " + $pos + $suffix + ); + } +} diff --git a/_sass/vendors/bourbon/helpers/_radial-arg-parser.scss b/_sass/vendors/bourbon/helpers/_radial-arg-parser.scss new file mode 100755 index 0000000000..56c6030b74 --- /dev/null +++ b/_sass/vendors/bourbon/helpers/_radial-arg-parser.scss @@ -0,0 +1,69 @@ +@function _radial-arg-parser($g1, $g2, $pos, $shape-size) { + @each $value in $g1, $g2 { + $first-val: nth($value, 1); + $pos-type: type-of($first-val); + $spec-at-index: null; + + // Determine if spec was passed to mixin + @if type-of($value) == list { + $spec-at-index: if(index($value, at), index($value, at), false); + } + @if $spec-at-index { + @if $spec-at-index > 1 { + @for $i from 1 through ($spec-at-index - 1) { + $shape-size: $shape-size nth($value, $i); + } + @for $i from ($spec-at-index + 1) through length($value) { + $pos: $pos nth($value, $i); + } + } + @else if $spec-at-index == 1 { + @for $i from ($spec-at-index + 1) through length($value) { + $pos: $pos nth($value, $i); + } + } + $g1: null; + } + + // If not spec calculate correct values + @else { + @if ($pos-type != color) or ($first-val != "transparent") { + @if ($pos-type == number) + or ($first-val == "center") + or ($first-val == "top") + or ($first-val == "right") + or ($first-val == "bottom") + or ($first-val == "left") { + + $pos: $value; + + @if $pos == $g1 { + $g1: null; + } + } + + @else if + ($first-val == "ellipse") + or ($first-val == "circle") + or ($first-val == "closest-side") + or ($first-val == "closest-corner") + or ($first-val == "farthest-side") + or ($first-val == "farthest-corner") + or ($first-val == "contain") + or ($first-val == "cover") { + + $shape-size: $value; + + @if $value == $g1 { + $g1: null; + } + + @else if $value == $g2 { + $g2: null; + } + } + } + } + } + @return $g1, $g2, $pos, $shape-size; +} diff --git a/_sass/vendors/bourbon/helpers/_radial-gradient-parser.scss b/_sass/vendors/bourbon/helpers/_radial-gradient-parser.scss new file mode 100755 index 0000000000..5444d8085f --- /dev/null +++ b/_sass/vendors/bourbon/helpers/_radial-gradient-parser.scss @@ -0,0 +1,50 @@ +@function _radial-gradient-parser($image) { + $image: unquote($image); + $gradients: (); + $start: str-index($image, "("); + $end: str-index($image, ","); + $first-val: str-slice($image, $start + 1, $end - 1); + + $prefix: str-slice($image, 1, $start); + $suffix: str-slice($image, $end, str-length($image)); + + $is-spec-syntax: str-index($first-val, "at"); + + @if $is-spec-syntax and $is-spec-syntax > 1 { + $keyword: str-slice($first-val, 1, $is-spec-syntax - 2); + $pos: str-slice($first-val, $is-spec-syntax + 3, str-length($first-val)); + $pos: append($pos, $keyword, comma); + + $gradients: ( + webkit-image: -webkit- + $prefix + $pos + $suffix, + spec-image: $image + ); + } + + @else if $is-spec-syntax == 1 { + $pos: str-slice($first-val, $is-spec-syntax + 3, str-length($first-val)); + + $gradients: ( + webkit-image: -webkit- + $prefix + $pos + $suffix, + spec-image: $image + ); + } + + @else if str-index($image, "cover") or str-index($image, "contain") { + @warn "Radial-gradient needs to be updated to conform to latest spec."; + + $gradients: ( + webkit-image: null, + spec-image: $image + ); + } + + @else { + $gradients: ( + webkit-image: -webkit- + $image, + spec-image: $image + ); + } + + @return $gradients; +} diff --git a/_sass/vendors/bourbon/helpers/_radial-positions-parser.scss b/_sass/vendors/bourbon/helpers/_radial-positions-parser.scss new file mode 100755 index 0000000000..3c552ad791 --- /dev/null +++ b/_sass/vendors/bourbon/helpers/_radial-positions-parser.scss @@ -0,0 +1,18 @@ +@function _radial-positions-parser($gradient-pos) { + $shape-size: nth($gradient-pos, 1); + $pos: nth($gradient-pos, 2); + $shape-size-spec: _shape-size-stripper($shape-size); + + $pre-spec: unquote(if($pos, "#{$pos}, ", null)) + unquote(if($shape-size, "#{$shape-size},", null)); + $pos-spec: if($pos, "at #{$pos}", null); + + $spec: "#{$shape-size-spec} #{$pos-spec}"; + + // Add comma + @if ($spec != " ") { + $spec: "#{$spec},"; + } + + @return $pre-spec $spec; +} diff --git a/_sass/vendors/bourbon/helpers/_render-gradients.scss b/_sass/vendors/bourbon/helpers/_render-gradients.scss new file mode 100755 index 0000000000..5765676838 --- /dev/null +++ b/_sass/vendors/bourbon/helpers/_render-gradients.scss @@ -0,0 +1,26 @@ +// User for linear and radial gradients within background-image or border-image properties + +@function _render-gradients($gradient-positions, $gradients, $gradient-type, $vendor: false) { + $pre-spec: null; + $spec: null; + $vendor-gradients: null; + @if $gradient-type == linear { + @if $gradient-positions { + $pre-spec: nth($gradient-positions, 1); + $spec: nth($gradient-positions, 2); + } + } + @else if $gradient-type == radial { + $pre-spec: nth($gradient-positions, 1); + $spec: nth($gradient-positions, 2); + } + + @if $vendor { + $vendor-gradients: -#{$vendor}-#{$gradient-type}-gradient(#{$pre-spec} $gradients); + } + @else if $vendor == false { + $vendor-gradients: "#{$gradient-type}-gradient(#{$spec} #{$gradients})"; + $vendor-gradients: unquote($vendor-gradients); + } + @return $vendor-gradients; +} diff --git a/_sass/vendors/bourbon/helpers/_shape-size-stripper.scss b/_sass/vendors/bourbon/helpers/_shape-size-stripper.scss new file mode 100755 index 0000000000..ee5eda4220 --- /dev/null +++ b/_sass/vendors/bourbon/helpers/_shape-size-stripper.scss @@ -0,0 +1,10 @@ +@function _shape-size-stripper($shape-size) { + $shape-size-spec: null; + @each $value in $shape-size { + @if ($value == "cover") or ($value == "contain") { + $value: null; + } + $shape-size-spec: "#{$shape-size-spec} #{$value}"; + } + @return $shape-size-spec; +} diff --git a/_sass/vendors/bourbon/helpers/_str-to-num.scss b/_sass/vendors/bourbon/helpers/_str-to-num.scss new file mode 100755 index 0000000000..3ef1d873b4 --- /dev/null +++ b/_sass/vendors/bourbon/helpers/_str-to-num.scss @@ -0,0 +1,50 @@ +//************************************************************************// +// Helper function for linear/radial-gradient-parsers. +// Source: http://sassmeister.com/gist/9647408 +//************************************************************************// +@function _str-to-num($string) { + // Matrices + $strings: "0" "1" "2" "3" "4" "5" "6" "7" "8" "9"; + $numbers: 0 1 2 3 4 5 6 7 8 9; + + // Result + $result: 0; + $divider: 0; + $minus: false; + + // Looping through all characters + @for $i from 1 through str-length($string) { + $character: str-slice($string, $i, $i); + $index: index($strings, $character); + + @if $character == "-" { + $minus: true; + } + + @else if $character == "." { + $divider: 1; + } + + @else { + @if not $index { + $result: if($minus, $result * -1, $result); + @return _convert-units($result, str-slice($string, $i)); + } + + $number: nth($numbers, $index); + + @if $divider == 0 { + $result: $result * 10; + } + + @else { + // Move the decimal dot to the left + $divider: $divider * 10; + $number: $number / $divider; + } + + $result: $result + $number; + } + } + @return if($minus, $result * -1, $result); +} diff --git a/_sass/vendors/bourbon/settings/_asset-pipeline.scss b/_sass/vendors/bourbon/settings/_asset-pipeline.scss new file mode 100755 index 0000000000..4c6afc5bb3 --- /dev/null +++ b/_sass/vendors/bourbon/settings/_asset-pipeline.scss @@ -0,0 +1,7 @@ +@charset "UTF-8"; + +/// A global setting to enable or disable the `$asset-pipeline` variable for all functions that accept it. +/// +/// @type Bool + +$asset-pipeline: false !default; diff --git a/_sass/vendors/bourbon/settings/_prefixer.scss b/_sass/vendors/bourbon/settings/_prefixer.scss new file mode 100755 index 0000000000..8c390514d4 --- /dev/null +++ b/_sass/vendors/bourbon/settings/_prefixer.scss @@ -0,0 +1,9 @@ +@charset "UTF-8"; + +/// Global variables to enable or disable vendor prefixes + +$prefix-for-webkit: true !default; +$prefix-for-mozilla: true !default; +$prefix-for-microsoft: true !default; +$prefix-for-opera: true !default; +$prefix-for-spec: true !default; diff --git a/_sass/vendors/bourbon/settings/_px-to-em.scss b/_sass/vendors/bourbon/settings/_px-to-em.scss new file mode 100755 index 0000000000..f2f9a3e8de --- /dev/null +++ b/_sass/vendors/bourbon/settings/_px-to-em.scss @@ -0,0 +1 @@ +$em-base: 16px !default; diff --git a/_sass/vendors/neat/_neat-helpers.scss b/_sass/vendors/neat/_neat-helpers.scss new file mode 100755 index 0000000000..2d6d808ae1 --- /dev/null +++ b/_sass/vendors/neat/_neat-helpers.scss @@ -0,0 +1,11 @@ +// Mixins +@import "mixins/clearfix"; + +// Functions +@import "functions/private"; +@import "functions/new-breakpoint"; + +// Settings +@import "settings/grid"; +@import "settings/visual-grid"; +@import "settings/disable-warnings"; diff --git a/_sass/vendors/neat/_neat.scss b/_sass/vendors/neat/_neat.scss new file mode 100755 index 0000000000..e17217122d --- /dev/null +++ b/_sass/vendors/neat/_neat.scss @@ -0,0 +1,23 @@ +// Neat 1.8.0 +// http://neat.bourbon.io +// Copyright 2012-2015 thoughtbot, inc. +// MIT License + +// Helpers +@import "neat-helpers"; + +// Grid +@import "grid/private"; +@import "grid/box-sizing"; +@import "grid/omega"; +@import "grid/outer-container"; +@import "grid/span-columns"; +@import "grid/row"; +@import "grid/shift"; +@import "grid/pad"; +@import "grid/fill-parent"; +@import "grid/media"; +@import "grid/to-deprecate"; +@import "grid/visual-grid"; +@import "grid/display-context"; +@import "grid/direction-context"; diff --git a/_sass/vendors/neat/functions/_new-breakpoint.scss b/_sass/vendors/neat/functions/_new-breakpoint.scss new file mode 100755 index 0000000000..41ab955643 --- /dev/null +++ b/_sass/vendors/neat/functions/_new-breakpoint.scss @@ -0,0 +1,49 @@ +@charset "UTF-8"; + +/// Returns a media context (media query / grid context) that can be stored in a variable and passed to `media()` as a single-keyword argument. Media contexts defined using `new-breakpoint` are used by the visual grid, as long as they are defined before importing Neat. +/// +/// @param {List} $query +/// A list of media query features and values. Each `$feature` should have a corresponding `$value`. +/// +/// If there is only a single `$value` in `$query`, `$default-feature` is going to be used. +/// +/// The number of total columns in the grid can be set by passing `$columns` at the end of the list (overrides `$total-columns`). For a list of valid values for `$feature`, click [here](http://www.w3.org/TR/css3-mediaqueries/#media1). +/// +/// @param {Number (unitless)} $total-columns [$grid-columns] +/// - Number of columns to use in the new grid context. Can be set as a shorthand in the first parameter. +/// +/// @example scss - Usage +/// $mobile: new-breakpoint(max-width 480px 4); +/// +/// .element { +/// @include media($mobile) { +/// @include span-columns(4); +/// } +/// } +/// +/// @example css - CSS Output +/// @media screen and (max-width: 480px) { +/// .element { +/// display: block; +/// float: left; +/// margin-right: 7.42297%; +/// width: 100%; +/// } +/// .element:last-child { +/// margin-right: 0; +/// } +/// } + +@function new-breakpoint($query: $feature $value $columns, $total-columns: $grid-columns) { + @if length($query) == 1 { + $query: $default-feature nth($query, 1) $total-columns; + } @else if is-even(length($query)) { + $query: append($query, $total-columns); + } + + @if is-not(belongs-to($query, $visual-grid-breakpoints)) { + $visual-grid-breakpoints: append($visual-grid-breakpoints, $query, comma) !global; + } + + @return $query; +} diff --git a/_sass/vendors/neat/functions/_private.scss b/_sass/vendors/neat/functions/_private.scss new file mode 100755 index 0000000000..872d4dc58d --- /dev/null +++ b/_sass/vendors/neat/functions/_private.scss @@ -0,0 +1,114 @@ +// Not function for Libsass compatibility +// https://github.com/sass/libsass/issues/368 +@function is-not($value) { + @return if($value, false, true); +} + +// Checks if a number is even +@function is-even($int) { + @return $int % 2 == 0; +} + +// Checks if an element belongs to a list or not +@function belongs-to($tested-item, $list) { + @return is-not(not-belongs-to($tested-item, $list)); +} + +@function not-belongs-to($tested-item, $list) { + @return is-not(index($list, $tested-item)); +} + +// Contains display value +@function contains-display-value($query) { + @return belongs-to(table, $query) + or belongs-to(block, $query) + or belongs-to(inline-block, $query) + or belongs-to(inline, $query); +} + +// Parses the first argument of span-columns() +@function container-span($span: $span) { + @if length($span) == 3 { + $container-columns: nth($span, 3); + @return $container-columns; + } @else if length($span) == 2 { + $container-columns: nth($span, 2); + @return $container-columns; + } + + @return $grid-columns; +} + +@function container-shift($shift: $shift) { + $parent-columns: $grid-columns !default !global; + + @if length($shift) == 3 { + $container-columns: nth($shift, 3); + @return $container-columns; + } @else if length($shift) == 2 { + $container-columns: nth($shift, 2); + @return $container-columns; + } + + @return $parent-columns; +} + +// Generates a striped background +@function gradient-stops($grid-columns, $color: $visual-grid-color) { + $transparent: transparent; + + $column-width: flex-grid(1, $grid-columns); + $gutter-width: flex-gutter($grid-columns); + $column-offset: $column-width; + + $values: ($transparent 0, $color 0); + + @for $i from 1 to $grid-columns*2 { + @if is-even($i) { + $values: append($values, $transparent $column-offset, comma); + $values: append($values, $color $column-offset, comma); + $column-offset: $column-offset + $column-width; + } @else { + $values: append($values, $color $column-offset, comma); + $values: append($values, $transparent $column-offset, comma); + $column-offset: $column-offset + $gutter-width; + } + } + + @return $values; +} + +// Layout direction +@function get-direction($layout, $default) { + $direction: null; + + @if to-upper-case($layout) == "LTR" or to-upper-case($layout) == "RTL" { + $direction: direction-from-layout($layout); + } @else { + $direction: direction-from-layout($default); + } + + @return $direction; +} + +@function direction-from-layout($layout) { + $direction: null; + + @if to-upper-case($layout) == "LTR" { + $direction: right; + } @else { + $direction: left; + } + + @return $direction; +} + +@function get-opposite-direction($direction) { + $opposite-direction: left; + + @if $direction == "left" { + $opposite-direction: right; + } + + @return $opposite-direction; +} diff --git a/_sass/vendors/neat/grid/_box-sizing.scss b/_sass/vendors/neat/grid/_box-sizing.scss new file mode 100755 index 0000000000..b6d3fec334 --- /dev/null +++ b/_sass/vendors/neat/grid/_box-sizing.scss @@ -0,0 +1,15 @@ +@charset "UTF-8"; + +@if $border-box-sizing == true { + html { // http://bit.ly/1qk2tVR + box-sizing: border-box; + } + + * { + &, + &::after, + &::before { + box-sizing: inherit; + } + } +} diff --git a/_sass/vendors/neat/grid/_direction-context.scss b/_sass/vendors/neat/grid/_direction-context.scss new file mode 100755 index 0000000000..7b0d46e8b3 --- /dev/null +++ b/_sass/vendors/neat/grid/_direction-context.scss @@ -0,0 +1,33 @@ +@charset "UTF-8"; + +/// Changes the direction property used by other mixins called in the code block argument. +/// +/// @param {String} $direction [left-to-right] +/// Layout direction to be used within the block. Can be `left-to-right` or `right-to-left`. +/// +/// @example scss - Usage +/// @include direction-context(right-to-left) { +/// .right-to-left-block { +/// @include span-columns(6); +/// } +/// } +/// +/// @example css - CSS Output +/// .right-to-left-block { +/// float: right; +/// ... +/// } + +@mixin direction-context($direction: left-to-right) { + $scope-direction: $layout-direction; + + @if to-lower-case($direction) == "left-to-right" { + $layout-direction: LTR !global; + } @else if to-lower-case($direction) == "right-to-left" { + $layout-direction: RTL !global; + } + + @content; + + $layout-direction: $scope-direction !global; +} diff --git a/_sass/vendors/neat/grid/_display-context.scss b/_sass/vendors/neat/grid/_display-context.scss new file mode 100755 index 0000000000..ed9b0637d3 --- /dev/null +++ b/_sass/vendors/neat/grid/_display-context.scss @@ -0,0 +1,28 @@ +@charset "UTF-8"; + +/// Changes the display property used by other mixins called in the code block argument. +/// +/// @param {String} $display [block] +/// Display value to be used within the block. Can be `table` or `block`. +/// +/// @example scss +/// @include display-context(table) { +/// .display-table { +/// @include span-columns(6); +/// } +/// } +/// +/// @example css +/// .display-table { +/// display: table-cell; +/// ... +/// } + +@mixin display-context($display: block) { + $scope-display: $container-display-table; + $container-display-table: $display == table !global; + + @content; + + $container-display-table: $scope-display !global; +} diff --git a/_sass/vendors/neat/grid/_fill-parent.scss b/_sass/vendors/neat/grid/_fill-parent.scss new file mode 100755 index 0000000000..415f0b1e54 --- /dev/null +++ b/_sass/vendors/neat/grid/_fill-parent.scss @@ -0,0 +1,22 @@ +@charset "UTF-8"; + +/// Forces the element to fill its parent container. +/// +/// @example scss - Usage +/// .element { +/// @include fill-parent; +/// } +/// +/// @example css - CSS Output +/// .element { +/// width: 100%; +/// box-sizing: border-box; +/// } + +@mixin fill-parent() { + width: 100%; + + @if $border-box-sizing == false { + box-sizing: border-box; + } +} diff --git a/_sass/vendors/neat/grid/_media.scss b/_sass/vendors/neat/grid/_media.scss new file mode 100755 index 0000000000..bd516e99ae --- /dev/null +++ b/_sass/vendors/neat/grid/_media.scss @@ -0,0 +1,92 @@ +@charset "UTF-8"; + +/// Outputs a media-query block with an optional grid context (the total number of columns used in the grid). +/// +/// @param {List} $query +/// A list of media query features and values, where each `$feature` should have a corresponding `$value`. +/// For a list of valid values for `$feature`, click [here](http://www.w3.org/TR/css3-mediaqueries/#media1). +/// +/// If there is only a single `$value` in `$query`, `$default-feature` is going to be used. +/// +/// The number of total columns in the grid can be set by passing `$columns` at the end of the list (overrides `$total-columns`). +/// +/// +/// @param {Number (unitless)} $total-columns [$grid-columns] +/// - Number of columns to use in the new grid context. Can be set as a shorthand in the first parameter. +/// +/// @example scss - Usage +/// .responsive-element { +/// @include media(769px) { +/// @include span-columns(6); +/// } +/// } +/// +/// .new-context-element { +/// @include media(min-width 320px max-width 480px, 6) { +/// @include span-columns(6); +/// } +/// } +/// +/// @example css - CSS Output +/// @media screen and (min-width: 769px) { +/// .responsive-element { +/// display: block; +/// float: left; +/// margin-right: 2.35765%; +/// width: 48.82117%; +/// } +/// +/// .responsive-element:last-child { +/// margin-right: 0; +/// } +/// } +/// +/// @media screen and (min-width: 320px) and (max-width: 480px) { +/// .new-context-element { +/// display: block; +/// float: left; +/// margin-right: 4.82916%; +/// width: 100%; +/// } +/// +/// .new-context-element:last-child { +/// margin-right: 0; +/// } +/// } + +@mixin media($query: $feature $value $columns, $total-columns: $grid-columns) { + @if length($query) == 1 { + @media screen and ($default-feature: nth($query, 1)) { + $default-grid-columns: $grid-columns; + $grid-columns: $total-columns !global; + @content; + $grid-columns: $default-grid-columns !global; + } + } @else { + $loop-to: length($query); + $media-query: "screen and "; + $default-grid-columns: $grid-columns; + $grid-columns: $total-columns !global; + + @if is-not(is-even(length($query))) { + $grid-columns: nth($query, $loop-to) !global; + $loop-to: $loop-to - 1; + } + + $i: 1; + @while $i <= $loop-to { + $media-query: $media-query + "(" + nth($query, $i) + ": " + nth($query, $i + 1) + ") "; + + @if ($i + 1) != $loop-to { + $media-query: $media-query + "and "; + } + + $i: $i + 2; + } + + @media #{$media-query} { + @content; + $grid-columns: $default-grid-columns !global; + } + } +} diff --git a/_sass/vendors/neat/grid/_omega.scss b/_sass/vendors/neat/grid/_omega.scss new file mode 100755 index 0000000000..80f918ab7e --- /dev/null +++ b/_sass/vendors/neat/grid/_omega.scss @@ -0,0 +1,87 @@ +@charset "UTF-8"; + +/// Removes the element's gutter margin, regardless of its position in the grid hierarchy or display property. It can target a specific element, or every `nth-child` occurrence. Works only with `block` layouts. +/// +/// @param {List} $query [block] +/// List of arguments. Supported arguments are `nth-child` selectors (targets a specific pseudo element) and `auto` (targets `last-child`). +/// +/// When passed an `nth-child` argument of type `*n` with `block` display, the omega mixin automatically adds a clear to the `*n+1` th element. Note that composite arguments such as `2n+1` do not support this feature. +/// +/// **Deprecation warning**: The omega mixin will no longer take a `$direction` argument. To change the layout direction, use `row($direction)` or set `$default-layout-direction` instead. +/// +/// @example scss - Usage +/// .element { +/// @include omega; +/// } +/// +/// .nth-element { +/// @include omega(4n); +/// } +/// +/// @example css - CSS Output +/// .element { +/// margin-right: 0; +/// } +/// +/// .nth-element:nth-child(4n) { +/// margin-right: 0; +/// } +/// +/// .nth-element:nth-child(4n+1) { +/// clear: left; +/// } + +@mixin omega($query: block, $direction: default) { + $table: belongs-to(table, $query); + $auto: belongs-to(auto, $query); + + @if $direction != default { + @include -neat-warn("The omega mixin will no longer take a $direction argument. To change the layout direction, use the direction(){...} mixin."); + } @else { + $direction: get-direction($layout-direction, $default-layout-direction); + } + + @if $table { + @include -neat-warn("The omega mixin no longer removes padding in table layouts."); + } + + @if length($query) == 1 { + @if $auto { + &:last-child { + margin-#{$direction}: 0; + } + } + + @else if contains-display-value($query) and $table == false { + margin-#{$direction}: 0; + } + + @else { + @include nth-child($query, $direction); + } + } @else if length($query) == 2 { + @if $auto { + &:last-child { + margin-#{$direction}: 0; + } + } @else { + @include nth-child(nth($query, 1), $direction); + } + } @else { + @include -neat-warn("Too many arguments passed to the omega() mixin."); + } +} + +@mixin nth-child($query, $direction) { + $opposite-direction: get-opposite-direction($direction); + + &:nth-child(#{$query}) { + margin-#{$direction}: 0; + } + + @if type-of($query) == number and unit($query) == "n" { + &:nth-child(#{$query}+1) { + clear: $opposite-direction; + } + } +} diff --git a/_sass/vendors/neat/grid/_outer-container.scss b/_sass/vendors/neat/grid/_outer-container.scss new file mode 100755 index 0000000000..d3f6267430 --- /dev/null +++ b/_sass/vendors/neat/grid/_outer-container.scss @@ -0,0 +1,34 @@ +@charset "UTF-8"; + +/// Makes an element a outer container by centering it in the viewport, clearing its floats, and setting its `max-width`. +/// Although optional, using `outer-container` is recommended. The mixin can be called on more than one element per page, as long as they are not nested. +/// +/// @param {Number [unit]} $local-max-width [$max-width] +/// Max width to be applied to the element. Can be a percentage or a measure. +/// +/// @example scss - Usage +/// .element { +/// @include outer-container(100%); +/// } +/// +/// @example css - CSS Output +/// .element { +/// max-width: 100%; +/// margin-left: auto; +/// margin-right: auto; +/// } +/// +/// .element::after { +/// clear: both; +/// content: ""; +/// display: table; +/// } + +@mixin outer-container($local-max-width: $max-width) { + @include clearfix; + max-width: $local-max-width; + margin: { + left: auto; + right: auto; + } +} diff --git a/_sass/vendors/neat/grid/_pad.scss b/_sass/vendors/neat/grid/_pad.scss new file mode 100755 index 0000000000..d697e1b992 --- /dev/null +++ b/_sass/vendors/neat/grid/_pad.scss @@ -0,0 +1,25 @@ +@charset "UTF-8"; + +/// Adds padding to the element. +/// +/// @param {List} $padding [flex-gutter()] +/// A list of padding value(s) to use. Passing `default` in the list will result in using the gutter width as a padding value. +/// +/// @example scss - Usage +/// .element { +/// @include pad(30px -20px 10px default); +/// } +/// +/// @example css - CSS Output +/// .element { +/// padding: 30px -20px 10px 2.35765%; +/// } + +@mixin pad($padding: flex-gutter()) { + $padding-list: null; + @each $value in $padding { + $value: if($value == 'default', flex-gutter(), $value); + $padding-list: join($padding-list, $value); + } + padding: $padding-list; +} diff --git a/_sass/vendors/neat/grid/_private.scss b/_sass/vendors/neat/grid/_private.scss new file mode 100755 index 0000000000..4c4e18c177 --- /dev/null +++ b/_sass/vendors/neat/grid/_private.scss @@ -0,0 +1,35 @@ +$parent-columns: $grid-columns !default; +$fg-column: $column; +$fg-gutter: $gutter; +$fg-max-columns: $grid-columns; +$container-display-table: false !default; +$layout-direction: LTR !default; + +@function flex-grid($columns, $container-columns: $fg-max-columns) { + $width: $columns * $fg-column + ($columns - 1) * $fg-gutter; + $container-width: $container-columns * $fg-column + ($container-columns - 1) * $fg-gutter; + @return percentage($width / $container-width); +} + +@function flex-gutter($container-columns: $fg-max-columns, $gutter: $fg-gutter) { + $container-width: $container-columns * $fg-column + ($container-columns - 1) * $fg-gutter; + @return percentage($gutter / $container-width); +} + +@function grid-width($n) { + @return $n * $gw-column + ($n - 1) * $gw-gutter; +} + +@function get-parent-columns($columns) { + @if $columns != $grid-columns { + $parent-columns: $columns !global; + } @else { + $parent-columns: $grid-columns !global; + } + + @return $parent-columns; +} + +@function is-display-table($container-is-display-table, $display) { + @return $container-is-display-table == true or $display == table; +} diff --git a/_sass/vendors/neat/grid/_row.scss b/_sass/vendors/neat/grid/_row.scss new file mode 100755 index 0000000000..4d913a9259 --- /dev/null +++ b/_sass/vendors/neat/grid/_row.scss @@ -0,0 +1,52 @@ +@charset "UTF-8"; + +/// Designates the element as a row of columns in the grid layout. It clears the floats on the element and sets its display property. Rows can't be nested, but there can be more than one row element—with different display properties—per layout. +/// +/// @param {String} $display [default] +/// Sets the display property of the element and the display context that will be used by its children. Can be `block` or `table`. +/// +/// @param {String} $direction [$default-layout-direction] +/// Sets the layout direction. Can be `LTR` (left-to-right) or `RTL` (right-to-left). +/// +/// @example scss - Usage +/// .element { +/// @include row(); +/// } +/// +/// @example css - CSS Output +/// .element { +/// *zoom: 1; +/// display: block; +/// } +/// +/// .element:before, .element:after { +/// content: " "; +/// display: table; +/// } +/// +/// .element:after { +/// clear: both; +/// } + +@mixin row($display: default, $direction: $default-layout-direction) { + @if $direction != $default-layout-direction { + @include -neat-warn("The $direction argument will be deprecated in future versions in favor of the direction(){...} mixin."); + } + + $layout-direction: $direction !global; + + @if $display != default { + @include -neat-warn("The $display argument will be deprecated in future versions in favor of the display(){...} mixin."); + } + + @if $display == table { + display: table; + @include fill-parent; + table-layout: fixed; + $container-display-table: true !global; + } @else { + @include clearfix; + display: block; + $container-display-table: false !global; + } +} diff --git a/_sass/vendors/neat/grid/_shift.scss b/_sass/vendors/neat/grid/_shift.scss new file mode 100755 index 0000000000..c0f24cd8e4 --- /dev/null +++ b/_sass/vendors/neat/grid/_shift.scss @@ -0,0 +1,50 @@ +@charset "UTF-8"; + +/// Translates an element horizontally by a number of columns. Positive arguments shift the element to the active layout direction, while negative ones shift it to the opposite direction. +/// +/// @param {Number (unitless)} $n-columns [1] +/// Number of columns by which the element shifts. +/// +/// @example scss - Usage +/// .element { +/// @include shift(-3); +/// } +/// +/// @example css - CSS output +/// .element { +/// margin-left: -25.58941%; +/// } + +@mixin shift($n-columns: 1) { + @include shift-in-context($n-columns); +} + +/// Translates an element horizontally by a number of columns, in a specific nesting context. +/// +/// @param {List} $shift +/// A list containing the number of columns to shift (`$columns`) and the number of columns of the parent element (`$container-columns`). +/// +/// The two values can be separated with any string such as `of`, `/`, etc. +/// +/// @example scss - Usage +/// .element { +/// @include shift(-3 of 6); +/// } +/// +/// @example css - CSS output +/// .element { +/// margin-left: -52.41458%; +/// } + +@mixin shift-in-context($shift: $columns of $container-columns) { + $n-columns: nth($shift, 1); + $parent-columns: container-shift($shift) !global; + + $direction: get-direction($layout-direction, $default-layout-direction); + $opposite-direction: get-opposite-direction($direction); + + margin-#{$opposite-direction}: $n-columns * flex-grid(1, $parent-columns) + $n-columns * flex-gutter($parent-columns); + + // Reset nesting context + $parent-columns: $grid-columns !global; +} diff --git a/_sass/vendors/neat/grid/_span-columns.scss b/_sass/vendors/neat/grid/_span-columns.scss new file mode 100755 index 0000000000..a7f9b00317 --- /dev/null +++ b/_sass/vendors/neat/grid/_span-columns.scss @@ -0,0 +1,94 @@ +@charset "UTF-8"; + +/// Specifies the number of columns an element should span. If the selector is nested the number of columns of its parent element should be passed as an argument as well. +/// +/// @param {List} $span +/// A list containing `$columns`, the unitless number of columns the element spans (required), and `$container-columns`, the number of columns the parent element spans (optional). +/// +/// If only one value is passed, it is assumed that it's `$columns` and that that `$container-columns` is equal to `$grid-columns`, the total number of columns in the grid. +/// +/// The values can be separated with any string such as `of`, `/`, etc. +/// +/// `$columns` also accepts decimals for when it's necessary to break out of the standard grid. E.g. Passing `2.4` in a standard 12 column grid will divide the row into 5 columns. +/// +/// @param {String} $display [block] +/// Sets the display property of the element. By default it sets the display property of the element to `block`. +/// +/// If passed `block-collapse`, it also removes the margin gutter by adding it to the element width. +/// +/// If passed `table`, it sets the display property to `table-cell` and calculates the width of the element without taking gutters into consideration. The result does not align with the block-based grid. +/// +/// @example scss - Usage +/// .element { +/// @include span-columns(6); +/// +/// .nested-element { +/// @include span-columns(2 of 6); +/// } +/// } +/// +/// @example css - CSS Output +/// .element { +/// display: block; +/// float: left; +/// margin-right: 2.35765%; +/// width: 48.82117%; +/// } +/// +/// .element:last-child { +/// margin-right: 0; +/// } +/// +/// .element .nested-element { +/// display: block; +/// float: left; +/// margin-right: 4.82916%; +/// width: 30.11389%; +/// } +/// +/// .element .nested-element:last-child { +/// margin-right: 0; +/// } + +@mixin span-columns($span: $columns of $container-columns, $display: block) { + $columns: nth($span, 1); + $container-columns: container-span($span); + + $parent-columns: get-parent-columns($container-columns) !global; + + $direction: get-direction($layout-direction, $default-layout-direction); + $opposite-direction: get-opposite-direction($direction); + + $display-table: is-display-table($container-display-table, $display); + + @if $display-table { + display: table-cell; + width: percentage($columns / $container-columns); + } @else { + float: #{$opposite-direction}; + + @if $display != no-display { + display: block; + } + + @if $display == collapse { + @include -neat-warn("The 'collapse' argument will be deprecated. Use 'block-collapse' instead."); + } + + @if $display == collapse or $display == block-collapse { + width: flex-grid($columns, $container-columns) + flex-gutter($container-columns); + + &:last-child { + width: flex-grid($columns, $container-columns); + } + + } @else { + margin-#{$direction}: flex-gutter($container-columns); + width: flex-grid($columns, $container-columns); + + &:last-child { + margin-#{$direction}: 0; + } + } + } +} diff --git a/_sass/vendors/neat/grid/_to-deprecate.scss b/_sass/vendors/neat/grid/_to-deprecate.scss new file mode 100755 index 0000000000..aeea0795b8 --- /dev/null +++ b/_sass/vendors/neat/grid/_to-deprecate.scss @@ -0,0 +1,97 @@ +@charset "UTF-8"; + +@mixin breakpoint($query:$feature $value $columns, $total-columns: $grid-columns) { + @include -neat-warn("The breakpoint() mixin was renamed to media() in Neat 1.0. Please update your project with the new syntax before the next version bump."); + + @if length($query) == 1 { + @media screen and ($default-feature: nth($query, 1)) { + $default-grid-columns: $grid-columns; + $grid-columns: $total-columns; + @content; + $grid-columns: $default-grid-columns; + } + } @else if length($query) == 2 { + @media screen and (nth($query, 1): nth($query, 2)) { + $default-grid-columns: $grid-columns; + $grid-columns: $total-columns; + @content; + $grid-columns: $default-grid-columns; + } + } @else if length($query) == 3 { + @media screen and (nth($query, 1): nth($query, 2)) { + $default-grid-columns: $grid-columns; + $grid-columns: nth($query, 3); + @content; + $grid-columns: $default-grid-columns; + } + } @else if length($query) == 4 { + @media screen and (nth($query, 1): nth($query, 2)) and (nth($query, 3): nth($query, 4)) { + $default-grid-columns: $grid-columns; + $grid-columns: $total-columns; + @content; + $grid-columns: $default-grid-columns; + } + } @else if length($query) == 5 { + @media screen and (nth($query, 1): nth($query, 2)) and (nth($query, 3): nth($query, 4)) { + $default-grid-columns: $grid-columns; + $grid-columns: nth($query, 5); + @content; + $grid-columns: $default-grid-columns; + } + } @else { + @include -neat-warn("Wrong number of arguments for breakpoint(). Read the documentation for more details."); + } +} + +@mixin nth-omega($nth, $display: block, $direction: default) { + @include -neat-warn("The nth-omega() mixin is deprecated. Please use omega() instead."); + @include omega($nth $display, $direction); +} + +/// Resets the active display property to `block`. Particularly useful when changing the display property in a single row. +/// +/// @example scss - Usage +/// .element { +/// @include row(table); +/// // Context changed to table display +/// } +/// +/// @include reset-display; +/// // Context is reset to block display + +@mixin reset-display { + $container-display-table: false !global; + @include -neat-warn("Resetting $display will be deprecated in future versions in favor of the display(){...} mixin."); +} + +/// Resets the active layout direction to the default value set in `$default-layout-direction`. Particularly useful when changing the layout direction in a single row. +/// +/// @example scss - Usage +/// .element { +/// @include row($direction: RTL); +/// // Context changed to right-to-left +/// } +/// +/// @include reset-layout-direction; +/// // Context is reset to left-to-right + +@mixin reset-layout-direction { + $layout-direction: $default-layout-direction !global; + @include -neat-warn("Resetting $direction will be deprecated in future versions in favor of the direction(){...} mixin."); +} + +/// Resets both the active layout direction and the active display property. +/// +/// @example scss - Usage +/// .element { +/// @include row(table, RTL); +/// // Context changed to table table and right-to-left +/// } +/// +/// @include reset-all; +/// // Context is reset to block display and left-to-right + +@mixin reset-all { + @include reset-display; + @include reset-layout-direction; +} diff --git a/_sass/vendors/neat/grid/_visual-grid.scss b/_sass/vendors/neat/grid/_visual-grid.scss new file mode 100755 index 0000000000..1192d82888 --- /dev/null +++ b/_sass/vendors/neat/grid/_visual-grid.scss @@ -0,0 +1,42 @@ +@charset "UTF-8"; + +@mixin grid-column-gradient($values...) { + background-image: -webkit-linear-gradient(left, $values); + background-image: -moz-linear-gradient(left, $values); + background-image: -ms-linear-gradient(left, $values); + background-image: -o-linear-gradient(left, $values); + background-image: unquote("linear-gradient(to left, #{$values})"); +} + +@if $visual-grid == true or $visual-grid == yes { + body:before { + @include grid-column-gradient(gradient-stops($grid-columns)); + content: ""; + display: inline-block; + height: 100%; + left: 0; + margin: 0 auto; + max-width: $max-width; + opacity: $visual-grid-opacity; + pointer-events: none; + position: fixed; + right: 0; + width: 100%; + + @if $visual-grid-index == back { + z-index: -1; + } + + @else if $visual-grid-index == front { + z-index: 9999; + } + + @each $breakpoint in $visual-grid-breakpoints { + @if $breakpoint { + @include media($breakpoint) { + @include grid-column-gradient(gradient-stops($grid-columns)); + } + } + } + } +} diff --git a/_sass/vendors/neat/mixins/_clearfix.scss b/_sass/vendors/neat/mixins/_clearfix.scss new file mode 100755 index 0000000000..e68efc4407 --- /dev/null +++ b/_sass/vendors/neat/mixins/_clearfix.scss @@ -0,0 +1,25 @@ +@charset "UTF-8"; + +/// Provides an easy way to include a clearfix for containing floats. +/// +/// @link http://goo.gl/yP5hiZ +/// +/// @example scss +/// .element { +/// @include clearfix; +/// } +/// +/// @example css +/// .element::after { +/// clear: both; +/// content: ""; +/// display: block; +/// } + +@mixin clearfix { + &::after { + clear: both; + content: ""; + display: block; + } +} diff --git a/_sass/vendors/neat/settings/_disable-warnings.scss b/_sass/vendors/neat/settings/_disable-warnings.scss new file mode 100755 index 0000000000..3f9b92a6a0 --- /dev/null +++ b/_sass/vendors/neat/settings/_disable-warnings.scss @@ -0,0 +1,13 @@ +@charset "UTF-8"; + +/// Disable all deprecation warnings. Defaults to `false`. Set with a `!global` flag. +/// +/// @type Bool + +$disable-warnings: false !default; + +@mixin -neat-warn($message) { + @if $disable-warnings == false { + @warn "#{$message}"; + } +} diff --git a/_sass/vendors/neat/settings/_grid.scss b/_sass/vendors/neat/settings/_grid.scss new file mode 100755 index 0000000000..fad1e9a445 --- /dev/null +++ b/_sass/vendors/neat/settings/_grid.scss @@ -0,0 +1,51 @@ +@charset "UTF-8"; + +/// Sets the relative width of a single grid column. The unit used should be the same one used to define `$gutter`. Set with a `!global` flag. +/// +/// @type Number (Unit) + +$column: 4.2358em !default; + +/// Sets the relative width of a single grid gutter. The unit used should be the same one used to define `$column`. Set with the `!global` flag. +/// +/// @type Number (Unit) + +$gutter: 1.618em !default; + +/// Sets the total number of columns in the grid. Its value can be overridden inside a media query using the `media()` mixin. Set with the `!global` flag. +/// +/// @type Number (Unitless) + +$grid-columns: 12 !default; + +/// Sets the max-width property of the element that includes `outer-container()`. Set with the `!global` flag. +/// +/// @type Number (Unit) +/// +$max-width: 1000px !default; + +/// When set to true, it sets the box-sizing property of all elements to `border-box`. Set with a `!global` flag. +/// +/// @type Bool +/// +/// @example css - CSS Output +/// html { +/// box-sizing: border-box; } +/// +/// *, *::after, *::before { +/// box-sizing: inherit; +/// } + +$border-box-sizing: true !default; + +/// Sets the default [media feature](http://www.w3.org/TR/css3-mediaqueries/#media) that `media()` and `new-breakpoint()` revert to when only a breakpoint value is passed. Set with a `!global` flag. +/// +/// @type String + +$default-feature: min-width; // Default @media feature for the breakpoint() mixin + +///Sets the default layout direction of the grid. Can be `LTR` or `RTL`. Set with a `!global` flag. +/// +///@type String + +$default-layout-direction: LTR !default; diff --git a/_sass/vendors/neat/settings/_visual-grid.scss b/_sass/vendors/neat/settings/_visual-grid.scss new file mode 100755 index 0000000000..9cd1815a2b --- /dev/null +++ b/_sass/vendors/neat/settings/_visual-grid.scss @@ -0,0 +1,27 @@ +@charset "UTF-8"; + +/// Displays the visual grid when set to true. The overlaid grid may be few pixels off depending on the browser's rendering engine and pixel rounding algorithm. Set with the `!global` flag. +/// +/// @type Bool + +$visual-grid: false !default; + +/// Sets the visual grid color. Set with `!global` flag. +/// +/// @type Color + +$visual-grid-color: #eee !default; + +/// Sets the `z-index` property of the visual grid. Can be `back` (behind content) or `front` (in front of content). Set with `!global` flag. +/// +/// @type String + +$visual-grid-index: back !default; + +/// Sets the opacity property of the visual grid. Set with `!global` flag. +/// +/// @type Number (unitless) + +$visual-grid-opacity: 0.4 !default; + +$visual-grid-breakpoints: () !default; diff --git a/_sass/vendors/unslider/unslider.scss b/_sass/vendors/unslider/unslider.scss new file mode 100755 index 0000000000..b800e11433 --- /dev/null +++ b/_sass/vendors/unslider/unslider.scss @@ -0,0 +1,8 @@ +/** + * Here's where everything gets included. You don't need + * to change anything here, and doing so might break + * stuff. Here be dragons and all that. + */ +@import 'variables'; +@import 'unslider/reset'; +@import 'unslider/dots'; \ No newline at end of file diff --git a/_sass/vendors/unslider/unslider/dots.scss b/_sass/vendors/unslider/unslider/dots.scss new file mode 100755 index 0000000000..d7f96b476f --- /dev/null +++ b/_sass/vendors/unslider/unslider/dots.scss @@ -0,0 +1,30 @@ +@if($unslider-dot-navigation){ + .#{$unslider-namespace}-nav, %#{$unslider-namespace}-nav { + ol { + list-style: none; + text-align: center; + + li { + display: inline-block; + width: 6px; + height: 6px; + margin: 0 4px; + + background: transparent; + border-radius: 5px; + + overflow: hidden; + text-indent: -999em; + + border: 2px solid $unslider-dot-colour; + + cursor: pointer; + + &.unslider-active { + background: $unslider-dot-colour; + cursor: default; + } + } + } + } +} \ No newline at end of file diff --git a/_sass/vendors/unslider/unslider/reset.scss b/_sass/vendors/unslider/unslider/reset.scss new file mode 100755 index 0000000000..1f3cbb8af2 --- /dev/null +++ b/_sass/vendors/unslider/unslider/reset.scss @@ -0,0 +1,70 @@ +.#{$unslider-namespace}, %#{$unslider-namespace} { + // Should either be relative or absolute + // as long as it's not static, but we'll + // set it using jQuery + // position: relative; + overflow: auto; + margin: 0; + padding: 0; + + &-wrap { + position: relative; + + &.unslider-carousel > li { + float: left; + } + } + + // Vertical sliders don't float left + &-vertical { + > ul { + height: 100%; + } + + li { + float: none; + width: 100%; + } + } + + // Fading needs everything to appear on top of + // each other + &-fade { + position: relative; + + .unslider-wrap li { + position: absolute; + left: 0; + top: 0; + right: 0; + z-index: 8; + + &.unslider-active { + z-index: 10; + } + } + } + + ul, ol, li { + list-style: none; + + /* Reset any weird spacing */ + margin: 0; + padding: 0; + + border: none; + } + + &-arrow { + position: absolute; + left: 20px; + z-index: 2; + + cursor: pointer; + + &.next { + left: auto; + right: 20px; + } + } +} \ No newline at end of file diff --git a/_sass/vendors/unslider/variables.scss b/_sass/vendors/unslider/variables.scss new file mode 100755 index 0000000000..cfd102a293 --- /dev/null +++ b/_sass/vendors/unslider/variables.scss @@ -0,0 +1,18 @@ +/** + * Default variables + * + * While these can be set with JavaScript, it's probably + * better and faster to just set them here, compile to + * CSS and include that instead to use some of that + * hardware-accelerated goodness. + */ + +// Unslider 2 has navigation styles pre-designed. You can turn it off here. +$unslider-dot-navigation: false; +$unslider-dot-colour: #fff; + +// Unslider 2 has navigation styles pre-designed. You can turn it off here. +$unslider-transition-function: cubic-bezier(.42,0,.58,1); + +// Set a namespace for Unslider +$unslider-namespace: 'unslider'; \ No newline at end of file diff --git a/_sips/README.md b/_sips/README.md new file mode 100644 index 0000000000..72244f693c --- /dev/null +++ b/_sips/README.md @@ -0,0 +1,3 @@ +# Scala Improvement Process Documents + +This directory contains the source of the SIP website, including all pending/finished/rejected SIPs. diff --git a/_sips/all.md b/_sips/all.md new file mode 100644 index 0000000000..d04931bd6d --- /dev/null +++ b/_sips/all.md @@ -0,0 +1,55 @@ +--- +layout: sips +title: List of All SIPs + +redirect_from: "/sips/sip-list.html" +--- + + +## Pending SIPs + +
      +
        + {% assign sips = site.sips | sort: date | reverse %} + {% for sip in sips %} + {% if sip.vote-status == "under-review" or sip.vote-status == "pending" or sip.vote-status == "dormant" or sip.vote-status == "under-revision" %} +
      • + {{ sip.title }} +
        {{ sip.date | date: '%B %Y' }}
        +
        {{ site.data.sip-data[sip.vote-status].text }}
        +
      • + {% endif %} + {% endfor %} +
      +
      + +
      +
      +

      Completed

      +
        + {% for sip in sips %} + {% if sip.vote-status == "complete" or sip.vote-status == "accepted" %} +
      • + {{ sip.title }} +
        {{ sip.date | date: '%B %Y' }}
        +
        {{ site.data.sip-data[sip.vote-status].text }}
        +
      • + {% endif %} + {% endfor %} +
      +
      +
      +

      Rejected

      +
        + {% for sip in sips %} + {% if sip.vote-status == "rejected" %} +
      • + {{ sip.title }} +
        {{ sip.date | date: '%B %Y' }}
        +
        {{ site.data.sip-data[sip.vote-status].text }}
        +
      • + {% endif %} + {% endfor %} +
      +
      +
      diff --git a/_sips/index.md b/_sips/index.md new file mode 100644 index 0000000000..13b20ffbf9 --- /dev/null +++ b/_sips/index.md @@ -0,0 +1,49 @@ +--- +layout: sips +title: Scala Improvement Process +--- + + +
      There are two ways to make changes to Scala.
      + +
        +
      1. Library changes, typically to the Scala standard library and other central libraries.
      2. +
      3. Compiler/language changes.
      4. +
      + +The Scala Platform Process (SPP) is intended for library changes, and the Scala +Improvement Process (SIP) is intended for changes to the Scala compiler or +language. + + +## Scala Platform Process (SPP) + +The Scala Platform aims to be a stable collection of libraries with widespread +use and a low barrier to entry for beginners and intermediate users. The +Platform consists of several independent modules that solve specific problems. +The Scala community sets the overall direction of the Platform. + +Learn more + + + +## Scala Improvement Process (SIP) + +The **SIP** (_Scala Improvement Process_) is a process for submitting changes to +the Scala language. All changes to the language go through design documents, +called Scala Improvement Proposals (SIPs), which are openly discussed by a +committee and only upon reaching a consensus are accepted to be merged into the +Scala compiler. + +The aim of the Scala Improvement Process is to apply openness and collaboration +to the process of evolving the language. SIPs are for changes to the Scala +language and/or compiler and are subject to a [rigorous review process](./sip-submission.html) +and are usually accompanied by changes to the +[Scala language specification](http://www.scala-lang.org/files/archive/spec/2.12/), lots of +review and discussion on the [Scala Contributors](https://contributors.scala-lang.org/) mailing list and +voting/approval milestones. Please read [Submitting a SIP](./sip-submission.html) and our +[SIP tutorial](./sip-tutorial.html) for more information. + +> Note: the SIP process replaced the older SID (Scala Improvement Document) process, +however the old completed SID documents are still available to review in the +[completed section of the SIP list](sip-list.html). diff --git a/_sips/minutes-list.md b/_sips/minutes-list.md new file mode 100644 index 0000000000..ff651a8a46 --- /dev/null +++ b/_sips/minutes-list.md @@ -0,0 +1,17 @@ +--- +layout: sips +title: SIP Meeting Minutes +--- + +This page hosts all the meeting notes (minutes) of the SIP meetings, starting +from July 2016. + +### Minutes ### +
        + {% assign sips = site.sips | sort: 'date' | reverse %} + {% for pg in sips %} + {% if pg.partof == 'minutes' %} +
      • {{ pg.title }}
      • + {% endif %} + {% endfor %} +
      diff --git a/sips/minutes/_posts/2016-07-15-sip-minutes.md b/_sips/minutes/2016-07-15-sip-minutes.md similarity index 99% rename from sips/minutes/_posts/2016-07-15-sip-minutes.md rename to _sips/minutes/2016-07-15-sip-minutes.md index 56c4f507e8..88e49e338d 100644 --- a/sips/minutes/_posts/2016-07-15-sip-minutes.md +++ b/_sips/minutes/2016-07-15-sip-minutes.md @@ -1,6 +1,8 @@ --- -layout: sip-landing +layout: sips title: SIP Meeting Minutes - 13th July 2016 + +partof: minutes --- # Minutes diff --git a/sips/minutes/_posts/2016-08-16-sip-10th-august-minutes.md b/_sips/minutes/2016-08-16-sip-10th-august-minutes.md similarity index 99% rename from sips/minutes/_posts/2016-08-16-sip-10th-august-minutes.md rename to _sips/minutes/2016-08-16-sip-10th-august-minutes.md index 4bd82b8425..35f3b61ac6 100644 --- a/sips/minutes/_posts/2016-08-16-sip-10th-august-minutes.md +++ b/_sips/minutes/2016-08-16-sip-10th-august-minutes.md @@ -1,6 +1,8 @@ --- -layout: sip-landing +layout: sips title: SIP Meeting Minutes - 10th August 2016 + +partof: minutes --- # Minutes diff --git a/sips/minutes/_posts/2016-09-20-sip-20th-september-minutes.md b/_sips/minutes/2016-09-20-sip-20th-september-minutes.md similarity index 99% rename from sips/minutes/_posts/2016-09-20-sip-20th-september-minutes.md rename to _sips/minutes/2016-09-20-sip-20th-september-minutes.md index 19179f5128..c6ce822242 100644 --- a/sips/minutes/_posts/2016-09-20-sip-20th-september-minutes.md +++ b/_sips/minutes/2016-09-20-sip-20th-september-minutes.md @@ -1,6 +1,8 @@ --- -layout: sip-landing +layout: sips title: SIP Meeting Minutes - 20th September 2016 + +partof: minutes --- # Minutes @@ -144,7 +146,7 @@ Sébastien introduces his updates on the proposal. He's tried hard to remove the performance regression in his implementation, but he hasn't found a way. He describes that the implementation is still 6% slower because of performance hits in the hashCode and equals implementation of a case class. To work around this -issue, two things are required: +issue, two things are required: * Changing the underlying implementation of byte and short ints. * Making the super class of unsigned integer extend *java.lang.Number* diff --git a/sips/minutes/_posts/2016-10-25-sip-minutes.md b/_sips/minutes/2016-10-25-sip-minutes.md similarity index 99% rename from sips/minutes/_posts/2016-10-25-sip-minutes.md rename to _sips/minutes/2016-10-25-sip-minutes.md index 635652fb99..7a1100cbb0 100644 --- a/sips/minutes/_posts/2016-10-25-sip-minutes.md +++ b/_sips/minutes/2016-10-25-sip-minutes.md @@ -1,6 +1,8 @@ --- -layout: sip-landing +layout: sips title: SIP Meeting Minutes - 25th October 2016 + +partof: minutes --- # Minutes @@ -103,7 +105,7 @@ Committee finally decides to not consider abstentions as no's. Adriaan, however, suggests that we should penalize people that abstain too often. Martin proposes a new system that helps survive the abstention issue mentioned -before. For a proposal to pass, at least 50% of all the Committee +before. For a proposal to pass, at least 50% of all the Committee and two-thirds of all the people voting (not counting abstentions) must accept it. The quorum rule is kept. After some discussion, the Committee unanimously votes in favor of Martin's new system, along with not considering abstentions as diff --git a/sips/minutes/_posts/2016-11-29-sip-minutes.md b/_sips/minutes/2016-11-29-sip-minutes.md similarity index 99% rename from sips/minutes/_posts/2016-11-29-sip-minutes.md rename to _sips/minutes/2016-11-29-sip-minutes.md index 44a6e12676..ef5111ab2c 100644 --- a/sips/minutes/_posts/2016-11-29-sip-minutes.md +++ b/_sips/minutes/2016-11-29-sip-minutes.md @@ -1,6 +1,8 @@ -SIP-25:Static--- -layout: sip-landing +--- +layout: sips title: SIP Meeting Minutes - 29th November 2016 + +partof: minutes --- # Minutes diff --git a/_sips/minutes/2017-02-14-sip-minutes.md b/_sips/minutes/2017-02-14-sip-minutes.md new file mode 100644 index 0000000000..1e67d9a433 --- /dev/null +++ b/_sips/minutes/2017-02-14-sip-minutes.md @@ -0,0 +1,262 @@ +--- +layout: sips +title: SIP Meeting Minutes - 14th February 2017 + +partof: minutes +--- + +# Minutes + +The following agenda was distributed to attendees: + +| Topic | Reviewer | +| --- | --- | +| [SIP-XX: Improving binary compatibility with @stableABI](http://docs.scala-lang.org/sips/pending/binary-compatibility.html) | Dmitry Petrashko | +| [SIP-NN - Allow referring to other arguments in default parameters](http://docs.scala-lang.org/sips/pending/refer-other-arguments-in-args.html) | Pathikrit Bhowmick | +| [SIP 25 - @static fields and methods in Scala objects(SI-4581)](http://docs.scala-lang.org/sips/pending/static-members.html) | Dmitry Petrashko, Sébastien Doeraene and Martin Odersky | +| [SIP-33 - Match infix & prefix types to meet expression rules](http://docs.scala-lang.org/sips/pending/make-types-behave-like-expressions.html) | Oron Port | + +Jorge Vicente Cantero was the Process Lead. Travis (@travissarles) was acting secretary of the meeting. + +## Date, Time and Location + +The meeting took place at 5:00pm Central European Time / 8:00am Pacific Daylight +Time on Tuesday, 14th February 2017 via Google Hangouts. + +Minutes were taken by Travis Lee, acting secretary. + +## Attendees + +Attendees Present: + +* Martin Odersky ([@odersky](https://github.com/odersky)), EPFL +* Dmitry Petrashko ([@DarkDimius](https://github.com/DarkDimius)), EPFL +* Lukas Rytz (Taking Adriaan Moore's place) ([@lrytz](https://github.com/lrytz)), Lightbend +* Seth Tisue ([@SethTisue](https://github.com/SethTisue)), Lightbend +* Iulian Dragos ([@dragos](https://github.com/dragos)), Independent +* Sébastien Doeraene ([@sjrd](https://github.com/sjrd)), EPFL +* Eugene Burmako ([@xeno-by](https://github.com/xeno-by)), EPFL +* Jorge Vicente Cantero ([@jvican](https://github.com/jvican)), Process Lead + +Absent: +* Heather Miller ([@heathermiller](https://github.com/heathermiller)), Scala Center +* Josh Suereth ([jsuereth](https://github.com/jsuereth)), Independent + +## Proceedings + +### Opening Remarks + +### SIP 25 - @static fields and methods in Scala objects(SI-4581) +Jorge asks Dmitry to explain the biggest changes since the last proposal. +The biggest changes were addressing the feedback of the reader. +**Dmitry** First, he covers whether the static annotation can behave like the tail recursive annotation, which doesn't actually impact compilation but only warns if it isn't possible to make something static. Dmitry doesn't think the static annotation should have the same semantics because it affects binary compatibility. + +Also, it depends on the superclass. If the superclass defined a field with the same name, the subclass can't have it. If we decide to make something static, we only be able to do it if the superclass doesn't have the fields with the same name. If static is done automatically and not triggered by the user we will enforce very strong requirements on superclass objects which is no-go. + +I proposed a scheme (which I elaborate more on the details frictions in the SIP). I present several examples of possible issues in this case. + +The other question was clarify how does the static effect initialization order and also describe if it effects binary compatibility. In general, emitting fields as static changes when they get initialized. They get initialized when the class is being class-loaded. The SIP in the current state requires static fields to be the first field defined in the object which means that these fields will be initialized before the other fields (indepenently of whether they are marked static or not). This leads to the fact that inside the object itself it will be possible to observe if something is static or not. Unless you rely on some class loader magic or reflection. At the same time, it is still possible to observe whether a field is static or not using multiple classes. Let's say you have a superclass of the object. If the superclass tries to see if the static field is initialized. In the case the field is static, it will be initialized before the superclass constructor executes. In case it's not static, it won't be initialized. + +In short, it can make initlization happen earlier but never later. By enforcing syntax restriction would make it harder to observe this. But still it is possible to observe initialization requirements. This is a sweet spot because static should be independent. The point is you want some fields to be initialized without the initialization of an entire object. There is a side-effect that an object is initialized and there are multiple ways to observe it somehow. + +The current SIP tries to make it behave as expected by the users in common cases. + +**Lukas** Let's take a simple example, you have an object with a static field and a non-static field. Now the user code, the program access the non=static field first. That means the module gets initialized, but the static field lives in the companion class, right? So the static field is not initialized necessary even though you access the module. Assuming the field initializers have side effects then you can observe the differences. Then the static field will only be initialized at some point later when you use the class and not when you access the module. + +**Dmitry** The idea is that the module should force execution of static initializers of the fields of the companion class. Which means that you won't be able to observe the difference in the order. It will look like all of them initialized at the same time. + +**Martin** How can that be done? + +**Dmitry** Just refer to the class in bytecode. Just mention the signature. + +**Martin** Does that mean we have to change the immediate code to enforce that? + +**Dmitry** Yeah you need to make one reference. It's very easy to write an expression in Scala which returns a null but ____ (7:45) + +**Martin** Sure but we can't do that right now. + +**Dmitry** I wouldn't do it. + +**Martin** Are there good reasons to do that anyway? + +**Dmitry** Maintaining semantics. If we don't do it, you can observe the fact that some fields are initialized, some aren't. + +**Martin** But we only have to do it for those companion objects that have both statics and fields. It doesn't leak into binary compatibility. + +**Jorge** You mentioned syntactic restrictions. Which kind? + +**Dmitry** Static fields should proceed any non-static fields. the fact that static fields can be initialized earlier but not later means that you won't be able to observe the fact ____ (8:40). If we were to allow non static fields to proceed, because static fields can be initialized earlier, you can see ____. + +**Sébastien** What about non-field statements? + +**Dmitry** They should also be after all the statics. + +**Sébastien** That's not written in the SIP. + +**Dmitry** Will update. The last question is whether it will effect binary compatibility. YES! The point is you should be able to call and access stuff which is static through a more efficient way on the JVM. So removing @static annotation will be a binary incompatible change. If we add forwarders, adding static won't be a binary incompatible change because you'll still have fowarders which forward to the static thing. Both methods and fields. + +**Lukas** But static fields are emitted as just fields and there's not getters and setters? + +**Dmitry** Yes, but all the static stuff is emitted in the class. The question would be whether we emit anything in the object. We can still emit getter and setter in the object which will allow us to maintian binary compatibility with stuff which was compiled before static annotation was added. By following this idea, adding static is binary compatible,removing isn't. + +**Sébastien** There's also an accessory to implement abstract definitions coming from a superclass or even overriding. + +**Dmitry** With current the current proposal static fields don't implement and don't override stuff. It's forbidden to define a static field or method if there is a thing with the same name defined in any of the superclasses. + +**Jorge** That would mean that you can't add static fields without hurting binary compatibility because if you cannot override, then you want to emit a static field which has a concrete name but that name is inherited from a super trait. + +**Dmitry** It won't compile then. It doesn't follow the requirements. If it compiles, it will be binary compatible. + +**Sébastien** What if the superclass of the object defines something with the same name? There's nothing that prevents me from implemnting or overriding something from the superclass of the object. I think that's fine. + +**Dmitry** The question would be about the initialization order + +**Sébastien** It's not a problem because if you call it via the super class it means you haven't initialized the object completely anyway already. + +**Dmitry** First of all it will disallow us to emit the final flag simply because final static fields can only be initialized from the static constructor. So if you have a final static field it can't implement setters. + +**Sébastien** But you can't have a getter and a setter that read and write the static field. + +**Dmitry** You can't write a _static final_ field again. Static fields are fine. If the static initialization is final, the only place where instructions which are writing it are allowed by hotspot are static initializer. So a possible restriction would be to say static final fields can't implement super class signatures. It would be very good to emit real static final fields because hotspot includes optimizations of those. Let's say if you were to discover some configuration statically and then use this to decide whether your implementation will take one branch or another, JIT will be able to ____ (14:32) eliminate this stuff. If the field isn't static, isn't final you don't have this guarantee. + +**Iulian** And do you need the setter even for vars? If you've marked it final only for vars would that work? + +**Dmitry** If it implements a super-trait vals, traits still have setters, even for vals. + +**Sébastien** Yes, but that's only if it's mixed in. If it's overridden in the object, you don't need the setter. And if it's mixed in from the interface it doesn't have the static annotation anyway because that's now how ____ (15:20). + +**Martin** Does it even have setters anymore? In dotty it definitely doesn't. I thought in 2.12 they changed the scheme now as well for trait vals. I don't think they require setters anymore. + +**Dmitry** We don't require them but I'm not sure about Scalac. Does Scalac use initializers or trait setters? + +**Sébastien** Trait setters. + +**Dmitry** For us, we can easily say that we support final but we're trying to take into account scalac. So is there some important use case where you want to have static fields implement not-static signatures. + +**Sébastien** No. + +**Dmitry** Would you be willing to lean on a conservative way in this case? + +**Sébastien** I was just trying to understand why there is a restriction. There doesn't seem to be any reason to but if there is a reason to, I don't see a use case. + +**Dmitry** I would propose to record this but not change the SIP. To summarize in short, static is an annotation which does affect the compilation scheme which enforces some requirements where it can be which try to hide the fact that semantic would have changed by making it hard to observe this. The assumption is that most common users won't be able to observe the difference in semantics because we've restricted the syntax so that it would be hard to observe it. At the same time, for advanced users, it would allow to emit static fields and methods which will help if someone wants to write highly optimized code or interact with Java. I think the SIP is good as is. Given the fact ____ proposed some suggestions and clarifications about actual changes. + +**Jorge** The question is whether this should be accepted or not. The problem is we don't have any implementation for Scalac. Does someone at Lightbend plan to work on this sometime soon? + +**Seth** I don't think that's something we've discussed as a team yet. + +**Jorge** We have to pass here both on this proposal as is right now but I think this could be dangerous in the case where we don't have an implementation for Scalac because maybe the details change and assume something in Scalac that the SIP is not able to predict or guard against it. Let's wait until next month and I will double check whether this is possible or not. Then I will get in touch with the Lightbend team to see whether this can be implemented or not. We'll decide in a month whether it should be accepted. + +**Sébastien** ScalaJS already implemented it under another name but it's supposed to be conservative with respect to the aesthetic SIP in the sense that things that are allowed now with @jsstatic will also be allowed with @static. @static might open up a little bit more. + +**Conclusion** The static SIP proposal has to be implemented in Scala, as it's already present in Dotty. Triplequote (Iulian Dragos and Mirco Dotta) has offered to provide an implementation targeting 2.12.3. + +### SIP-NN - [Allow referring to other arguments in default parameters](https://github.com/scala/scala.github.com/pull/653) (22:30) +Sébastien is the reviewer of this proposal. + +**Sébastien** The SIP is a generalization of why we can use in default values of parameters. Especially referring to other arguments. In current Scala we can refer to arguments in previous parameter lists. This SIP wants to open that up. The way it's currently written, any parameter whether it's in the same parameter list or a previous one, it's also allowed to refer to argument on the right. The text needs to be elaborated on use cases. Doesn't address implementation concerns. Jorge answered on the PR with analysis of feasibility. I'm convinced that the version where we can also refer to a parameter on the right is infeasible because you can have arbitrary cycles and you don't know _______ ( 24:55) and it's completely impossible. + +Other than that, in principle the SIP looks reasonable. It's possible to implement but it will cause more bytecode because now the third parameter will always need to receive the first two parameters to decide its value. We cannot decide that whether based on the default value actually refers to the previous parameters because that would be unstable in respect to binary compatibility. You need to always give to the default accessor all the prior parameters and that means it can potentially increase bytecode size. That needs to be analyzed maybe with a prototype, compare with Scala library. + +**Jorge** I implemented this. I did a study and analysis of whether referring to parameters on the right is visible and I've explained in a comment in the PR why it's not. Basically this is a change that would require breaking binary compatibility and this would be targeting 2.13 so we are not gonna see it any time soon. I think that it would be very useful to have a look at the numbers to see how it affects bytecode size. I'll run some benchmarks and [report the results in the PR](https://github.com/scala/scala/pull/5641). + +**Sébastien** SIP addendum, Type Members: In current Scala in the same way that you can only refer to parameters on terms previous parameter lists you can also only refer to path-dependent types of parameters in previous parameter lists and there is a small section in the SIP that says in the same vein we should allow to refer to path-dependent types of parameters in the same parameter list probably on the left. But for that one I don't have a good intuition of what effects it would have on type inference because type inference works parameter list per parameter list. The fact that you cannot refer to path-dependent types from the same parameter list means you can complete type inference from one parameter list without juggling path-dependent types within the same thing. Then when you move to the next one it's already inferred from the previous parameters. So it seems simpler but it's just a guest. Martin, would that be problematic? + +**Martin** It would be a completely sweeping change. It's one of the key types that suddenly becomes recursive. So you can imagine what that means. Every time we construct such a type we can't do it inductively anymore. So basically it's the difference between polytypes and method types. I'm not saying it's impossible but it would be a huge change the compiler to do that. It's probably beyond what we can do for Scalac and just for Dotty we could think about it but it would be a very big change. + +**Conclusion** The proposal has been numbered as SIP-32. The reference to type members seems tricky in implementation and interaction and it may be removed as the analysis of this SIP continues. The reference to other arguments in the same parameter list has been implemented by Jorge in [scala/scala#5641](https://github.com/scala/scala/pull/5641). + +### SIP-XX: Improving binary compatibility with @stableABI (33:30) +**Dmitry** This proposes annotations which does not change the compilation scheme. A bit of background, Scala is being released with versions which can be either minor or major. So 2.11 is major version compared to 2.10. 2.11.1 and 2.11.2 are minor versions. Scala currently guarantees binary-compatibility between minor versions. At the same time, big parts of the scala community live in different major versions of the compiler which require them to publish artifacts multiple times because the same artifact will be incompatible if used in a different compiler. + +**Martin** Or write it in Java + +**Dmitry** The current situations has several solutions. The first is write it in Java, the second is make it a source dependency, download the source, and compile it in runtime and the third one use your best judgement is to try to write Scala which you assume will be safe. At the same time there is a tool which is called MiMa which helps you to see whether you did it right. MiMa allows you to compare two already-compiled artifacts and say whether they're compatible or not. This SIP proposes something which will complement MiMa in indicating whether the thing will be compatible with the next version. So currently if you were to write a file in Scala compiled with 2.10 and then compile it to 2.11, MiMa can after-the-fact say that it's incompatible and previous version should have been more conservative with the features it used. It does it after the fact when 2.11 was already released. Your artifacts are already on Bintray and it's too late. stableABI augments this use case by allowing you to get a guarantee that this artifact will be reliably compiled by all the compilers which call themselves Scala, across all the major versions, and can be used by the code compiled by those compilers. The idea would be that stableABI classes can be either used for projects which need to survive multiple Scala major versions or for other languages which don't have such a strong binary API guarantee such as Java and Kotlin. And additionally it has a very strong use case of allowing to use features of future compilers and future language releases in libraries which try to support users who are still on the old versions. + +There are multiple use cases covered by this SIP. I think the two most important ones which are coming now are the migration from Dotty to Scala and the fact that we'll have two major releases existing at the same time. It would be nice if there was a common language for two compilers where people can reliably be in the safe situation publishing wise. If they publish an artifact compiled by Dotty, it can be safely used by Scalac, even if internally they use DOT advanced features. At the same time they won't be sure that they can use some features of Scalac that Dotty doesn't support and they will be able to use them inside the classes as long as they don't leak. So stableABI adds a check to the compiler which more or less ensures that there is no leakage of advanced features being used which could affect binaryABI. The guarantee which is assumed to be provided is if the same class is compiled with stableABI and it succeeds compilation it can be a replacement for the previous class if compiled by a different compiler. If the class has been changed by the user, they should use MiMa to find that the change was binary incompatible. + +**Eugene** The migration to Dotty is something that is highly anticipated in the community. Concrete proposals are hard to facilitate this change. It's gonna be a big change. Very welcome. How do you write stuff that's going to be used from Java reliably? + +**Martin** What I didn't see in the proposal was, so to move this forward you need to specify a minimum set of features that will be under stableABI. So if I write stableABI, you have to specify at least which sort of features will be accepted by the compiler. + +**Dmitry** Instead of specify which features will be accepted, I said that the ground truth will be the source code. So the rule that is currently written is if compiler changes the signature from the one written by the user, it shouldn't be stableABI. The current specification more or less says if something isn't de-sugared in a way that affects stableABI, it's supported. We can additionally list features which aren't affected by this. But I think that the actual implementation, true, should be a strong overestimation. + +**Martin** But in the end because it's something that binds not just the current compilers but all future compilers, once you guarantee a feature of stableABI you have to keep it. You can't change it anymore. So it needs to have a very strong specification what this is and the minimal one too. We don't want to overcommit ourselves. + +**Dmitry** stableABI says how do you consume the class if it's successfully compiled. So let's say a future compiler fails to compile it, it's perfectly fine. We're talking about stableABI, not stable source code. Similarly, one of the motivations is there is some use cases which are compiled by Scalac... + +**Martin** You say I have stableABI and Scala 2.12 accepts it and then Scala 2.13 comes up and says now I changed this thing so I won't compile this anymore. + +**Dmitry** But you can still use the artifact compiled by the previous one. + +**Martin** So you can break it, but you have to tell the user that you broke it. + +**Dmitry** There will be a compilation error that says doesn't compile. + +**Martin** I guess there would be a strong normative thing to say, well once one compiler guarantees certain things are okay under stableABI, future compilers will try not to break that. Otherwise, it wouldn't be that useful. + +**Dmitry** It would be a nice guarantee to have. But so far the SIP tries to ensure more or less safe publication on the maven so the artifact can be consumed reliably by the users. It's more providing safety for the users, not for the creator of the library. + +**Martin** In the end the compiler will have to check it and I think we have to give guidance to compilers what they should accept under stableABI. + +**Dmitry** Do you think there should be a minimal set of features which is accepted and there should be a warning if there is a feature used outside of this set? + +**Martin** Yes. We want to start now. Because I use something that a future compiler will break and it's not very useful to find out I can't upgrade my stuff because it's no longer stableABI. It will be useful that the compiler tells me now, look this thing is not guaranteed to be maintained in all future versions. Don't use it if you want to have an abstraction that for interchange. + +**Dmitry** The binary compatibility is if you've already compiled it, you can just give the compiled artifact to the future compiler which will safely consume it. It doesn't mean that the future compiler can compile it. But you can use the already compiled artifact. + +**Martin** I would think it would be much more interesting if it were source as well. That the future compiler would guarantee to compile it to the same bytecodes. Why do people write stuff in Java instead of Scala? Because you can't recompile this thing in a future compiler. We don't know whether the layout is the same or not so that was the thing where we need an antidote and say no if you write stableABI then even a Scala compiler will guarantee that it will be the same in future versions. If the thing succeeds in source, then it will be mapped to the same binary signatures as previously. We need to define a feature set now where that will be the case. Otherwise how are you going to implement that? + +**Dmitry** The current proposed criteria is it's the same signature written by the user. No de-sugarings for users. Let's say a user uses repeated arguments. Repeated arguments changes the binary signature. If the compiler can't add new members to stableABI classes and the compiler can't change the binary ____ for existing members. For vals, we synthesize a getter. You have a member of the class which was written by the user. Similarly for vars you also get a setter. Lazy vals gets the accessor which lazy vals synthesize two members which have funny signatures and they have funny names. Default methods is a thing which is checked explicitly here. It's the only thing. + +**We need this both in spec and doc**. + +**Dmitry** If you have a stableABI class, can it's arguments take a Scala Option or not? Currently Option is not a stableABI class which means you can be in a funny situation in which you succeed to call a method which takes an option, during the execution it fails _____ (50:13). The current proposal says that if you take non-stableABI classes as arguments or return types it gives you a warning. There isn't an all-or nothing approach that gets implemented. The proposal is that one day the library may decide to have some superclasses which it promises are stableABI for collections, for options, for all the types that are stable. This will be API to use those classes from stableABI classes and from other languages. + +**Sébastien** The goal is to be able to reuse artifacts from another compiler version or from a different compiler entirely but what happens to the @scala annotations? Is the classfile API might be the same between the same between 2.11 and 2.12 but it doesn't mean that the serialized form of the Scala signature notation is the same and can be read by the other compiler. So if you compile you source code against the binary artifacts that was published on maven your compilation will fail potentially with a crash or something like that because it cannot read the Scala-specific information from the class. + +**Dmitry** The proposal is not to emit stable Scala signatures. + +**Sébastien** So it really looks like a Java class file. Needs to be mentioned in the proposal. + +**Martin** If you don't emit a Scala signature then you can't have a co- or contravariant type parameter because they are only expressed in Scala signatures, in Java it's not there. I don't see how that follows from the current proposal. Also, isn't it platform dependent? + +**Sébastien** We do have a Java signature. Scala-JS doesn't disable classfile emission. When you say quickly compile, it uses the classfiles to quickly compile. when you use macros, it will extend from those classfiles. When you use an IDE it reduces the classfiles to identify things. When you use sbt, it uses classfiles to detect the changes. However, they aren't used by the ScalaJS linker. + +**Seth** Does this need to be part of the compiler or can it move forward as a plugin or just as a check performed in MiMa? MiMa just compares two different APIs. Can it have this other job as well: seeing if it does anything outside of the boundaries. + +**Dmitry** It could be a plugin, but it's not the right responsibility. Whoever develops the pluin does not have a way to enforce its rules by future compilers. Even though it provides guarantees to users, people providing these guarantees should be the people building the future versions. MiMa would need to be come half compiler. It's possible but not practical. If we say we emit Scala signatures, it's a strong promise and we allow users more. If everyone agrees, I would be glad. + +**Martin** Five years from now, do we even know whether Scala compilers will emit Java signatures? To put that in the spec seems too pre-implemention-oriented. We might need a minimum Scala signature, even if we don't emit a Java one. The way the signatures are treated should be an implemenation aspect which should be exactly orthogonal to what we do with stableABI that we want to have something that is stable across lots of implementations. + +**Jorge** We could make an exception that if we change the platform, then this annotation wouldn't apply. + +**Dmitry** The current proposal proposes only top-level classes by _____ (1:01:43). + +**Martin** It just has to be the guarantee of the whole package. The compiler has to translate this somehow so that future compilers will be able to read it in all eternity. That's the contract of stableABI. + +**Dmitry** Does this automatically mean that all future compilers should emit Scala signatures? + +**Martin** No, they just have to read whatever the previous one produced that had this thing. + +**Dmitry** So it means that the artifact compiled by Dotty that doesn't emit Scala signatures won't be able to consume it from Scalac. + +**Martin** That is true. You want to make a rule that newer compilers can ____ (1:02:54) the older ones but not the other way around. + +**Conclusion**: This proposal has been numbered as SIP-34. This is a complicated proposal that needs synchronization between the Scala and Dotty team to decide which encodings are good enough to make binary compatible. When Dmitry, the author of this proposal, figures out which features should be binary compatible and has more information on the future implementation, the SIP Committee will start the review period. + +### SIP-33 - Match infix & prefix types to meet expression rules (1:04:00) + +**Jorge** Making a change to the parser to make types behave as expressions. The other part of the proposal is about prefix types. Just like unary operators, he wants to have unary prefixes for type. So you can create a unary operator for types. + +**Iulian** Covariant and contravariant operators can cause confusion. + +**Eugene** But at least it's not ambiguous. + +**Sébastien** At least as long as we don't have Covariant type alias or abstract type members. If I could define `type +A`, what does that mean? + +**Eugene** If it's on the lefthand side of the equals signs type member, then it's covariant. As long as it's in a binding position, unary infix, should work. + +**Jorge** We will vote later. + +**Conclusion** The vote took place outside the meeting and the proposal was numbered. All of the committee members (including those absent) have accepted the change. diff --git a/_sips/minutes/_posts/2017-02-14-sip-minutes.md b/_sips/minutes/_posts/2017-02-14-sip-minutes.md new file mode 100644 index 0000000000..69a5831407 --- /dev/null +++ b/_sips/minutes/_posts/2017-02-14-sip-minutes.md @@ -0,0 +1,260 @@ +--- +layout: sips +title: SIP Meeting Minutes - 14th February 2017 +--- + +# Minutes + +The following agenda was distributed to attendees: + +| Topic | Reviewer | +| --- | --- | +| [SIP-XX: Improving binary compatibility with @stableABI](http://docs.scala-lang.org/sips/pending/binary-compatibility.html) | Dmitry Petrashko | +| [SIP-NN - Allow referring to other arguments in default parameters](http://docs.scala-lang.org/sips/pending/refer-other-arguments-in-args.html) | Pathikrit Bhowmick | +| [SIP 25 - @static fields and methods in Scala objects(SI-4581)](http://docs.scala-lang.org/sips/pending/static-members.html) | Dmitry Petrashko, Sébastien Doeraene and Martin Odersky | +| [SIP-33 - Match infix & prefix types to meet expression rules](http://docs.scala-lang.org/sips/pending/make-types-behave-like-expressions.html) | Oron Port | + +Jorge Vicente Cantero was the Process Lead. Travis (@travissarles) was acting secretary of the meeting. + +## Date, Time and Location + +The meeting took place at 5:00pm Central European Time / 8:00am Pacific Daylight +Time on Tuesday, 14th February 2017 via Google Hangouts. + +Minutes were taken by Travis Lee, acting secretary. + +## Attendees + +Attendees Present: + +* Martin Odersky ([@odersky](https://github.com/odersky)), EPFL +* Dmitry Petrashko ([@DarkDimius](https://github.com/DarkDimius)), EPFL +* Lukas Rytz (Taking Adriaan Moore's place) ([@lrytz](https://github.com/lrytz)), Lightbend +* Seth Tisue ([@SethTisue](https://github.com/SethTisue)), Lightbend +* Iulian Dragos ([@dragos](https://github.com/dragos)), Independent +* Sébastien Doeraene ([@sjrd](https://github.com/sjrd)), EPFL +* Eugene Burmako ([@xeno-by](https://github.com/xeno-by)), EPFL +* Jorge Vicente Cantero ([@jvican](https://github.com/jvican)), Process Lead + +Absent: +* Heather Miller ([@heathermiller](https://github.com/heathermiller)), Scala Center +* Josh Suereth ([jsuereth](https://github.com/jsuereth)), Independent + +## Proceedings + +### Opening Remarks + +### SIP 25 - @static fields and methods in Scala objects(SI-4581) +Jorge asks Dmitry to explain the biggest changes since the last proposal. +The biggest changes were addressing the feedback of the reader. +**Dmitry** First, he covers whether the static annotation can behave like the tail recursive annotation, which doesn't actually impact compilation but only warns if it isn't possible to make something static. Dmitry doesn't think the static annotation should have the same semantics because it affects binary compatibility. + +Also, it depends on the superclass. If the superclass defined a field with the same name, the subclass can't have it. If we decide to make something static, we only be able to do it if the superclass doesn't have the fields with the same name. If static is done automatically and not triggered by the user we will enforce very strong requirements on superclass objects which is no-go. + +I proposed a scheme (which I elaborate more on the details frictions in the SIP). I present several examples of possible issues in this case. + +The other question was clarify how does the static effect initialization order and also describe if it effects binary compatibility. In general, emitting fields as static changes when they get initialized. They get initialized when the class is being class-loaded. The SIP in the current state requires static fields to be the first field defined in the object which means that these fields will be initialized before the other fields (indepenently of whether they are marked static or not). This leads to the fact that inside the object itself it will be possible to observe if something is static or not. Unless you rely on some class loader magic or reflection. At the same time, it is still possible to observe whether a field is static or not using multiple classes. Let's say you have a superclass of the object. If the superclass tries to see if the static field is initialized. In the case the field is static, it will be initialized before the superclass constructor executes. In case it's not static, it won't be initialized. + +In short, it can make initlization happen earlier but never later. By enforcing syntax restriction would make it harder to observe this. But still it is possible to observe initialization requirements. This is a sweet spot because static should be independent. The point is you want some fields to be initialized without the initialization of an entire object. There is a side-effect that an object is initialized and there are multiple ways to observe it somehow. + +The current SIP tries to make it behave as expected by the users in common cases. + +**Lukas** Let's take a simple example, you have an object with a static field and a non-static field. Now the user code, the program access the non=static field first. That means the module gets initialized, but the static field lives in the companion class, right? So the static field is not initialized necessary even though you access the module. Assuming the field initializers have side effects then you can observe the differences. Then the static field will only be initialized at some point later when you use the class and not when you access the module. + +**Dmitry** The idea is that the module should force execution of static initializers of the fields of the companion class. Which means that you won't be able to observe the difference in the order. It will look like all of them initialized at the same time. + +**Martin** How can that be done? + +**Dmitry** Just refer to the class in bytecode. Just mention the signature. + +**Martin** Does that mean we have to change the immediate code to enforce that? + +**Dmitry** Yeah you need to make one reference. It's very easy to write an expression in Scala which returns a null but ____ (7:45) + +**Martin** Sure but we can't do that right now. + +**Dmitry** I wouldn't do it. + +**Martin** Are there good reasons to do that anyway? + +**Dmitry** Maintaining semantics. If we don't do it, you can observe the fact that some fields are initialized, some aren't. + +**Martin** But we only have to do it for those companion objects that have both statics and fields. It doesn't leak into binary compatibility. + +**Jorge** You mentioned syntactic restrictions. Which kind? + +**Dmitry** Static fields should proceed any non-static fields. the fact that static fields can be initialized earlier but not later means that you won't be able to observe the fact ____ (8:40). If we were to allow non static fields to proceed, because static fields can be initialized earlier, you can see ____. + +**Sébastien** What about non-field statements? + +**Dmitry** They should also be after all the statics. + +**Sébastien** That's not written in the SIP. + +**Dmitry** Will update. The last question is whether it will effect binary compatibility. YES! The point is you should be able to call and access stuff which is static through a more efficient way on the JVM. So removing @static annotation will be a binary incompatible change. If we add forwarders, adding static won't be a binary incompatible change because you'll still have fowarders which forward to the static thing. Both methods and fields. + +**Lukas** But static fields are emitted as just fields and there's not getters and setters? + +**Dmitry** Yes, but all the static stuff is emitted in the class. The question would be whether we emit anything in the object. We can still emit getter and setter in the object which will allow us to maintian binary compatibility with stuff which was compiled before static annotation was added. By following this idea, adding static is binary compatible,removing isn't. + +**Sébastien** There's also an accessory to implement abstract definitions coming from a superclass or even overriding. + +**Dmitry** With current the current proposal static fields don't implement and don't override stuff. It's forbidden to define a static field or method if there is a thing with the same name defined in any of the superclasses. + +**Jorge** That would mean that you can't add static fields without hurting binary compatibility because if you cannot override, then you want to emit a static field which has a concrete name but that name is inherited from a super trait. + +**Dmitry** It won't compile then. It doesn't follow the requirements. If it compiles, it will be binary compatible. + +**Sébastien** What if the superclass of the object defines something with the same name? There's nothing that prevents me from implemnting or overriding something from the superclass of the object. I think that's fine. + +**Dmitry** The question would be about the initialization order + +**Sébastien** It's not a problem because if you call it via the super class it means you haven't initialized the object completely anyway already. + +**Dmitry** First of all it will disallow us to emit the final flag simply because final static fields can only be initialized from the static constructor. So if you have a final static field it can't implement setters. + +**Sébastien** But you can't have a getter and a setter that read and write the static field. + +**Dmitry** You can't write a _static final_ field again. Static fields are fine. If the static initialization is final, the only place where instructions which are writing it are allowed by hotspot are static initializer. So a possible restriction would be to say static final fields can't implement super class signatures. It would be very good to emit real static final fields because hotspot includes optimizations of those. Let's say if you were to discover some configuration statically and then use this to decide whether your implementation will take one branch or another, JIT will be able to ____ (14:32) eliminate this stuff. If the field isn't static, isn't final you don't have this guarantee. + +**Iulian** And do you need the setter even for vars? If you've marked it final only for vars would that work? + +**Dmitry** If it implements a super-trait vals, traits still have setters, even for vals. + +**Sébastien** Yes, but that's only if it's mixed in. If it's overridden in the object, you don't need the setter. And if it's mixed in from the interface it doesn't have the static annotation anyway because that's now how ____ (15:20). + +**Martin** Does it even have setters anymore? In dotty it definitely doesn't. I thought in 2.12 they changed the scheme now as well for trait vals. I don't think they require setters anymore. + +**Dmitry** We don't require them but I'm not sure about Scalac. Does Scalac use initializers or trait setters? + +**Sébastien** Trait setters. + +**Dmitry** For us, we can easily say that we support final but we're trying to take into account scalac. So is there some important use case where you want to have static fields implement not-static signatures. + +**Sébastien** No. + +**Dmitry** Would you be willing to lean on a conservative way in this case? + +**Sébastien** I was just trying to understand why there is a restriction. There doesn't seem to be any reason to but if there is a reason to, I don't see a use case. + +**Dmitry** I would propose to record this but not change the SIP. To summarize in short, static is an annotation which does affect the compilation scheme which enforces some requirements where it can be which try to hide the fact that semantic would have changed by making it hard to observe this. The assumption is that most common users won't be able to observe the difference in semantics because we've restricted the syntax so that it would be hard to observe it. At the same time, for advanced users, it would allow to emit static fields and methods which will help if someone wants to write highly optimized code or interact with Java. I think the SIP is good as is. Given the fact ____ proposed some suggestions and clarifications about actual changes. + +**Jorge** The question is whether this should be accepted or not. The problem is we don't have any implementation for Scalac. Does someone at Lightbend plan to work on this sometime soon? + +**Seth** I don't think that's something we've discussed as a team yet. + +**Jorge** We have to pass here both on this proposal as is right now but I think this could be dangerous in the case where we don't have an implementation for Scalac because maybe the details change and assume something in Scalac that the SIP is not able to predict or guard against it. Let's wait until next month and I will double check whether this is possible or not. Then I will get in touch with the Lightbend team to see whether this can be implemented or not. We'll decide in a month whether it should be accepted. + +**Sébastien** ScalaJS already implemented it under another name but it's supposed to be conservative with respect to the aesthetic SIP in the sense that things that are allowed now with @jsstatic will also be allowed with @static. @static might open up a little bit more. + +**Conclusion** The static SIP proposal has to be implemented in Scala, as it's already present in Dotty. Triplequote (Iulian Dragos and Mirco Dotta) has offered to provide an implementation targeting 2.12.3. + +### SIP-NN - [Allow referring to other arguments in default parameters](https://github.com/scala/scala.github.com/pull/653) (22:30) +Sébastien is the reviewer of this proposal. + +**Sébastien** The SIP is a generalization of why we can use in default values of parameters. Especially referring to other arguments. In current Scala we can refer to arguments in previous parameter lists. This SIP wants to open that up. The way it's currently written, any parameter whether it's in the same parameter list or a previous one, it's also allowed to refer to argument on the right. The text needs to be elaborated on use cases. Doesn't address implementation concerns. Jorge answered on the PR with analysis of feasibility. I'm convinced that the version where we can also refer to a parameter on the right is infeasible because you can have arbitrary cycles and you don't know _______ ( 24:55) and it's completely impossible. + +Other than that, in principle the SIP looks reasonable. It's possible to implement but it will cause more bytecode because now the third parameter will always need to receive the first two parameters to decide its value. We cannot decide that whether based on the default value actually refers to the previous parameters because that would be unstable in respect to binary compatibility. You need to always give to the default accessor all the prior parameters and that means it can potentially increase bytecode size. That needs to be analyzed maybe with a prototype, compare with Scala library. + +**Jorge** I implemented this. I did a study and analysis of whether referring to parameters on the right is visible and I've explained in a comment in the PR why it's not. Basically this is a change that would require breaking binary compatibility and this would be targeting 2.13 so we are not gonna see it any time soon. I think that it would be very useful to have a look at the numbers to see how it affects bytecode size. I'll run some benchmarks and [report the results in the PR](https://github.com/scala/scala/pull/5641). + +**Sébastien** SIP addendum, Type Members: In current Scala in the same way that you can only refer to parameters on terms previous parameter lists you can also only refer to path-dependent types of parameters in previous parameter lists and there is a small section in the SIP that says in the same vein we should allow to refer to path-dependent types of parameters in the same parameter list probably on the left. But for that one I don't have a good intuition of what effects it would have on type inference because type inference works parameter list per parameter list. The fact that you cannot refer to path-dependent types from the same parameter list means you can complete type inference from one parameter list without juggling path-dependent types within the same thing. Then when you move to the next one it's already inferred from the previous parameters. So it seems simpler but it's just a guest. Martin, would that be problematic? + +**Martin** It would be a completely sweeping change. It's one of the key types that suddenly becomes recursive. So you can imagine what that means. Every time we construct such a type we can't do it inductively anymore. So basically it's the difference between polytypes and method types. I'm not saying it's impossible but it would be a huge change the compiler to do that. It's probably beyond what we can do for Scalac and just for Dotty we could think about it but it would be a very big change. + +**Conclusion** The proposal has been numbered as SIP-32. The reference to type members seems tricky in implementation and interaction and it may be removed as the analysis of this SIP continues. The reference to other arguments in the same parameter list has been implemented by Jorge in [scala/scala#5641](https://github.com/scala/scala/pull/5641). + +### SIP-XX: Improving binary compatibility with @stableABI (33:30) +**Dmitry** This proposes annotations which does not change the compilation scheme. A bit of background, Scala is being released with versions which can be either minor or major. So 2.11 is major version compared to 2.10. 2.11.1 and 2.11.2 are minor versions. Scala currently guarantees binary-compatibility between minor versions. At the same time, big parts of the scala community live in different major versions of the compiler which require them to publish artifacts multiple times because the same artifact will be incompatible if used in a different compiler. + +**Martin** Or write it in Java + +**Dmitry** The current situations has several solutions. The first is write it in Java, the second is make it a source dependency, download the source, and compile it in runtime and the third one use your best judgement is to try to write Scala which you assume will be safe. At the same time there is a tool which is called MiMa which helps you to see whether you did it right. MiMa allows you to compare two already-compiled artifacts and say whether they're compatible or not. This SIP proposes something which will complement MiMa in indicating whether the thing will be compatible with the next version. So currently if you were to write a file in Scala compiled with 2.10 and then compile it to 2.11, MiMa can after-the-fact say that it's incompatible and previous version should have been more conservative with the features it used. It does it after the fact when 2.11 was already released. Your artifacts are already on Bintray and it's too late. stableABI augments this use case by allowing you to get a guarantee that this artifact will be reliably compiled by all the compilers which call themselves Scala, across all the major versions, and can be used by the code compiled by those compilers. The idea would be that stableABI classes can be either used for projects which need to survive multiple Scala major versions or for other languages which don't have such a strong binary API guarantee such as Java and Kotlin. And additionally it has a very strong use case of allowing to use features of future compilers and future language releases in libraries which try to support users who are still on the old versions. + +There are multiple use cases covered by this SIP. I think the two most important ones which are coming now are the migration from Dotty to Scala and the fact that we'll have two major releases existing at the same time. It would be nice if there was a common language for two compilers where people can reliably be in the safe situation publishing wise. If they publish an artifact compiled by Dotty, it can be safely used by Scalac, even if internally they use DOT advanced features. At the same time they won't be sure that they can use some features of Scalac that Dotty doesn't support and they will be able to use them inside the classes as long as they don't leak. So stableABI adds a check to the compiler which more or less ensures that there is no leakage of advanced features being used which could affect binaryABI. The guarantee which is assumed to be provided is if the same class is compiled with stableABI and it succeeds compilation it can be a replacement for the previous class if compiled by a different compiler. If the class has been changed by the user, they should use MiMa to find that the change was binary incompatible. + +**Eugene** The migration to Dotty is something that is highly anticipated in the community. Concrete proposals are hard to facilitate this change. It's gonna be a big change. Very welcome. How do you write stuff that's going to be used from Java reliably? + +**Martin** What I didn't see in the proposal was, so to move this forward you need to specify a minimum set of features that will be under stableABI. So if I write stableABI, you have to specify at least which sort of features will be accepted by the compiler. + +**Dmitry** Instead of specify which features will be accepted, I said that the ground truth will be the source code. So the rule that is currently written is if compiler changes the signature from the one written by the user, it shouldn't be stableABI. The current specification more or less says if something isn't de-sugared in a way that affects stableABI, it's supported. We can additionally list features which aren't affected by this. But I think that the actual implementation, true, should be a strong overestimation. + +**Martin** But in the end because it's something that binds not just the current compilers but all future compilers, once you guarantee a feature of stableABI you have to keep it. You can't change it anymore. So it needs to have a very strong specification what this is and the minimal one too. We don't want to overcommit ourselves. + +**Dmitry** stableABI says how do you consume the class if it's successfully compiled. So let's say a future compiler fails to compile it, it's perfectly fine. We're talking about stableABI, not stable source code. Similarly, one of the motivations is there is some use cases which are compiled by Scalac... + +**Martin** You say I have stableABI and Scala 2.12 accepts it and then Scala 2.13 comes up and says now I changed this thing so I won't compile this anymore. + +**Dmitry** But you can still use the artifact compiled by the previous one. + +**Martin** So you can break it, but you have to tell the user that you broke it. + +**Dmitry** There will be a compilation error that says doesn't compile. + +**Martin** I guess there would be a strong normative thing to say, well once one compiler guarantees certain things are okay under stableABI, future compilers will try not to break that. Otherwise, it wouldn't be that useful. + +**Dmitry** It would be a nice guarantee to have. But so far the SIP tries to ensure more or less safe publication on the maven so the artifact can be consumed reliably by the users. It's more providing safety for the users, not for the creator of the library. + +**Martin** In the end the compiler will have to check it and I think we have to give guidance to compilers what they should accept under stableABI. + +**Dmitry** Do you think there should be a minimal set of features which is accepted and there should be a warning if there is a feature used outside of this set? + +**Martin** Yes. We want to start now. Because I use something that a future compiler will break and it's not very useful to find out I can't upgrade my stuff because it's no longer stableABI. It will be useful that the compiler tells me now, look this thing is not guaranteed to be maintained in all future versions. Don't use it if you want to have an abstraction that for interchange. + +**Dmitry** The binary compatibility is if you've already compiled it, you can just give the compiled artifact to the future compiler which will safely consume it. It doesn't mean that the future compiler can compile it. But you can use the already compiled artifact. + +**Martin** I would think it would be much more interesting if it were source as well. That the future compiler would guarantee to compile it to the same bytecodes. Why do people write stuff in Java instead of Scala? Because you can't recompile this thing in a future compiler. We don't know whether the layout is the same or not so that was the thing where we need an antidote and say no if you write stableABI then even a Scala compiler will guarantee that it will be the same in future versions. If the thing succeeds in source, then it will be mapped to the same binary signatures as previously. We need to define a feature set now where that will be the case. Otherwise how are you going to implement that? + +**Dmitry** The current proposed criteria is it's the same signature written by the user. No de-sugarings for users. Let's say a user uses repeated arguments. Repeated arguments changes the binary signature. If the compiler can't add new members to stableABI classes and the compiler can't change the binary ____ for existing members. For vals, we synthesize a getter. You have a member of the class which was written by the user. Similarly for vars you also get a setter. Lazy vals gets the accessor which lazy vals synthesize two members which have funny signatures and they have funny names. Default methods is a thing which is checked explicitly here. It's the only thing. + +**We need this both in spec and doc**. + +**Dmitry** If you have a stableABI class, can it's arguments take a Scala Option or not? Currently Option is not a stableABI class which means you can be in a funny situation in which you succeed to call a method which takes an option, during the execution it fails _____ (50:13). The current proposal says that if you take non-stableABI classes as arguments or return types it gives you a warning. There isn't an all-or nothing approach that gets implemented. The proposal is that one day the library may decide to have some superclasses which it promises are stableABI for collections, for options, for all the types that are stable. This will be API to use those classes from stableABI classes and from other languages. + +**Sébastien** The goal is to be able to reuse artifacts from another compiler version or from a different compiler entirely but what happens to the @scala annotations? Is the classfile API might be the same between the same between 2.11 and 2.12 but it doesn't mean that the serialized form of the Scala signature notation is the same and can be read by the other compiler. So if you compile you source code against the binary artifacts that was published on maven your compilation will fail potentially with a crash or something like that because it cannot read the Scala-specific information from the class. + +**Dmitry** The proposal is not to emit stable Scala signatures. + +**Sébastien** So it really looks like a Java class file. Needs to be mentioned in the proposal. + +**Martin** If you don't emit a Scala signature then you can't have a co- or contravariant type parameter because they are only expressed in Scala signatures, in Java it's not there. I don't see how that follows from the current proposal. Also, isn't it platform dependent? + +**Sébastien** We do have a Java signature. Scala-JS doesn't disable classfile emission. When you say quickly compile, it uses the classfiles to quickly compile. when you use macros, it will extend from those classfiles. When you use an IDE it reduces the classfiles to identify things. When you use sbt, it uses classfiles to detect the changes. However, they aren't used by the ScalaJS linker. + +**Seth** Does this need to be part of the compiler or can it move forward as a plugin or just as a check performed in MiMa? MiMa just compares two different APIs. Can it have this other job as well: seeing if it does anything outside of the boundaries. + +**Dmitry** It could be a plugin, but it's not the right responsibility. Whoever develops the pluin does not have a way to enforce its rules by future compilers. Even though it provides guarantees to users, people providing these guarantees should be the people building the future versions. MiMa would need to be come half compiler. It's possible but not practical. If we say we emit Scala signatures, it's a strong promise and we allow users more. If everyone agrees, I would be glad. + +**Martin** Five years from now, do we even know whether Scala compilers will emit Java signatures? To put that in the spec seems too pre-implemention-oriented. We might need a minimum Scala signature, even if we don't emit a Java one. The way the signatures are treated should be an implemenation aspect which should be exactly orthogonal to what we do with stableABI that we want to have something that is stable across lots of implementations. + +**Jorge** We could make an exception that if we change the platform, then this annotation wouldn't apply. + +**Dmitry** The current proposal proposes only top-level classes by _____ (1:01:43). + +**Martin** It just has to be the guarantee of the whole package. The compiler has to translate this somehow so that future compilers will be able to read it in all eternity. That's the contract of stableABI. + +**Dmitry** Does this automatically mean that all future compilers should emit Scala signatures? + +**Martin** No, they just have to read whatever the previous one produced that had this thing. + +**Dmitry** So it means that the artifact compiled by Dotty that doesn't emit Scala signatures won't be able to consume it from Scalac. + +**Martin** That is true. You want to make a rule that newer compilers can ____ (1:02:54) the older ones but not the other way around. + +**Conclusion**: This proposal has been numbered as SIP-34. This is a complicated proposal that needs synchronization between the Scala and Dotty team to decide which encodings are good enough to make binary compatible. When Dmitry, the author of this proposal, figures out which features should be binary compatible and has more information on the future implementation, the SIP Committee will start the review period. + +### SIP-33 - Match infix & prefix types to meet expression rules (1:04:00) + +**Jorge** Making a change to the parser to make types behave as expressions. The other part of the proposal is about prefix types. Just like unary operators, he wants to have unary prefixes for type. So you can create a unary operator for types. + +**Iulian** Covariant and contravariant operators can cause confusion. + +**Eugene** But at least it's not ambiguous. + +**Sébastien** At least as long as we don't have Covariant type alias or abstract type members. If I could define `type +A`, what does that mean? + +**Eugene** If it's on the lefthand side of the equals signs type member, then it's covariant. As long as it's in a binding position, unary infix, should work. + +**Jorge** We will vote later. + +**Conclusion** The vote took place outside the meeting and the proposal was numbered. All of the committee members (including those absent) have accepted the change. diff --git a/sips/sip-submission.md b/_sips/sip-submission.md similarity index 99% rename from sips/sip-submission.md rename to _sips/sip-submission.md index b838d81417..63774e880c 100644 --- a/sips/sip-submission.md +++ b/_sips/sip-submission.md @@ -1,5 +1,5 @@ --- -layout: sip-landing +layout: sips title: SIP Specification and Submission --- diff --git a/sips/sip-template.md b/_sips/sip-template.md similarity index 99% rename from sips/sip-template.md rename to _sips/sip-template.md index 3db4d817cb..0781e987b3 100644 --- a/sips/sip-template.md +++ b/_sips/sip-template.md @@ -1,5 +1,5 @@ --- -layout: sip +layout: sips discourse: true title: SIP-NN - SIP Title --- diff --git a/sips/sip-tutorial.md b/_sips/sip-tutorial.md similarity index 99% rename from sips/sip-tutorial.md rename to _sips/sip-tutorial.md index 9c05c6ff56..9467cc3e59 100644 --- a/sips/sip-tutorial.md +++ b/_sips/sip-tutorial.md @@ -1,5 +1,5 @@ --- -layout: sip-landing +layout: sips title: Writing a new SIP --- diff --git a/_sips/sips/2009-05-28-scala-compiler-phase-plugin-in.md b/_sips/sips/2009-05-28-scala-compiler-phase-plugin-in.md new file mode 100644 index 0000000000..f66a14dae3 --- /dev/null +++ b/_sips/sips/2009-05-28-scala-compiler-phase-plugin-in.md @@ -0,0 +1,9 @@ +--- +layout: sip +title: SID-2 Scala Compiler Phase and Plug-In Initialization for Scala 2.8 +vote-status: complete +vote-text: This SIP has already been accepted and completed. +permalink: /sips/:title.html +--- + +This was an older SID that can be found [here](http://www.scala-lang.org/sid/2) diff --git a/_sips/sips/2009-06-02-early-member-definitions.md b/_sips/sips/2009-06-02-early-member-definitions.md new file mode 100644 index 0000000000..62d75cd00f --- /dev/null +++ b/_sips/sips/2009-06-02-early-member-definitions.md @@ -0,0 +1,9 @@ +--- +layout: sip +title: SID-4 - Early Member Definitions +vote-status: complete +vote-text: This SIP has already been accepted and completed. +permalink: /sips/:title.html +--- + +This was an older SID that can be found [here](http://www.scala-lang.org/sid/4) diff --git a/_sips/sips/2009-11-02-scala-swing-overview.md b/_sips/sips/2009-11-02-scala-swing-overview.md new file mode 100644 index 0000000000..512bef381f --- /dev/null +++ b/_sips/sips/2009-11-02-scala-swing-overview.md @@ -0,0 +1,9 @@ +--- +layout: sip +title: SID-8 - Scala Swing Overview +vote-status: complete +vote-text: This SIP has already been accepted and completed. +permalink: /sips/:title.html +--- + +This was an older SID that can be found [here](http://www.scala-lang.org/sid/8) diff --git a/sips/completed/_posts/2010-01-22-named-and-default-arguments.md b/_sips/sips/2010-01-22-named-and-default-arguments.md similarity index 98% rename from sips/completed/_posts/2010-01-22-named-and-default-arguments.md rename to _sips/sips/2010-01-22-named-and-default-arguments.md index c2a118060d..f023434109 100644 --- a/sips/completed/_posts/2010-01-22-named-and-default-arguments.md +++ b/_sips/sips/2010-01-22-named-and-default-arguments.md @@ -1,6 +1,9 @@ --- layout: sip title: SID-1 Named and Default Arguments +vote-status: complete +vote-text: This SIP has already been accepted and completed. +permalink: /sips/:title.html --- **Lukas Rytz** @@ -18,7 +21,7 @@ In Scala 2.8, method arguments can be specified in _named style_ using the same f(b = getT(), a = getInt()) -The argument expressions are evaluated in call-site order, so in the above example `getT()` is executed before `getInt()`f. Mixing named and positional arguments is allowed as long as the positional part forms a prefix of the argument list: +The argument expressions are evaluated in call-site order, so in the above example `getT()` is executed before `getInt()`f. Mixing named and positional arguments is allowed as long as the positional part forms a prefix of the argument list: f(0, b = "1") // valid f(b = "1", a = 0) // valid @@ -198,7 +201,7 @@ For every default argument expression the compiler generates a method computing // def f$default$1[T]: Int = 1 // def f$default$2[T](a: Int): Int = a + 1 // def f$default$3[T](a: Int)(b: T): T = b - + For constructor defaults, these methods are added to the companion object of the class (which is created if it does not exist). For other methods, the default methods are generated at the same location as the original method. Method calls which use default arguments are transformed into a block of the same form as described above for named arguments: f()("str")() diff --git a/sips/completed/_posts/2010-01-22-scala-2-8-arrays.md b/_sips/sips/2010-01-22-scala-2-8-arrays.md similarity index 98% rename from sips/completed/_posts/2010-01-22-scala-2-8-arrays.md rename to _sips/sips/2010-01-22-scala-2-8-arrays.md index 0d00016b67..ba5b9d72da 100644 --- a/sips/completed/_posts/2010-01-22-scala-2-8-arrays.md +++ b/_sips/sips/2010-01-22-scala-2-8-arrays.md @@ -1,6 +1,9 @@ --- layout: sip title: SID-7 - Scala 2.8 Arrays +vote-status: complete +vote-text: This SIP has already been accepted and completed. +permalink: /sips/:title.html --- *(This is an older SID, its original PDF can be found [here](http://www.scala-lang.org/sid/7))* @@ -179,7 +182,7 @@ methods to class `String`. The second, which goes from `String` to ## Generic Array Creation and Manifests That’s almost everything. The only remaining question is how to implement -generic array creation. Unlike Java, Scala allows an instance creation +generic array creation. Unlike Java, Scala allows an instance creation `new Array[T]` where `T` is a type parameter. How can this be implemented, given the fact that there does not exist a uniform array representation in Java? The only way to do this is to require additional runtime information which @@ -188,7 +191,7 @@ called a `Manifest`. An object of type `Manifest[T]` provides complete information about the type `T`. Manifest values are typically passed in implicit parameters; and the compiler knows how to construct them for statically known types `T`. There exists also a weaker form named `ClassManifest` which can -be constructed from knowing just the top-level class of a type, without +be constructed from knowing just the top-level class of a type, without necessarily knowing all its argument types. It is this type of runtime information that’s required for array creation. @@ -196,10 +199,10 @@ Here’s an example. Consider the method `tabulate` which forms an array from the results of applying a given function `f` on a range of numbers from 0 until a given length. Up to Scala 2.7, `tabulate` could be written as follows: - def tabulate[T](len: Int, f: Int => T) = { + def tabulate[T](len: Int, f: Int => T) = { val xs = new Array[T](len) for (i <- 0 until len) xs(i) = f(i) - xs + xs } In Scala 2.8 this is no longer possible, because runtime information is @@ -207,7 +210,7 @@ necessary to create the right representation of `Array[T]`. One needs to provide this information by passing a `ClassManifest[T]` into the method as an implicit parameter: - def tabulate[T](len: Int, f: Int => T)(implicit m: ClassManifest[T]) = { + def tabulate[T](len: Int, f: Int => T)(implicit m: ClassManifest[T]) = { val xs = new Array[T](len) for (i <- 0 until len) xs(i) = f(i) xs @@ -233,7 +236,7 @@ not feasible, Scala 2.8 offers an alternative version of arrays in the `GenericArray` class. This class is defined in package `scala.collection.mutable` along the following lines. - class GenericArray[T](length: Int) extends Vector[T] { + class GenericArray[T](length: Int) extends Vector[T] { val array: Array[AnyRef] = new Array[AnyRef](length) ... // all vector operations defined in terms of ‘array’ @@ -258,7 +261,7 @@ detail of `sortWith`, we felt that it was unreasonable to demand a class manifest for the element type of the sequence. Hence the choice of a `GenericArray`. -## Conclusion +## Conclusion In summary, the new Scala collection framework resolves some long-standing problems with arrays and with strings. It removes a considerable amount of @@ -280,37 +283,3 @@ three language features will be described in more detail in separate notes. [1]: http://www.drmaciver.com/2008/06/scala- arrays [2]: http://oldfashionedsoftware.com/2009/08/05/the-mystery-of-the-parameterized-array [3]: http://www.drmaciver.com/repos/scala-arrays/sip-arrays.xhtml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/_sips/sips/2010-01-27-internals-of-scala-annotations.md b/_sips/sips/2010-01-27-internals-of-scala-annotations.md new file mode 100644 index 0000000000..06dbb7381e --- /dev/null +++ b/_sips/sips/2010-01-27-internals-of-scala-annotations.md @@ -0,0 +1,9 @@ +--- +layout: sip +title: SID-5 - Internals of Scala Annotations +vote-status: complete +vote-text: This SIP has already been accepted and completed. +permalink: /sips/:title.html +--- + +This was an older SID that can be found [here](http://www.scala-lang.org/sid/5) diff --git a/_sips/sips/2010-05-06-scala-specialization.md b/_sips/sips/2010-05-06-scala-specialization.md new file mode 100644 index 0000000000..b34ebde14c --- /dev/null +++ b/_sips/sips/2010-05-06-scala-specialization.md @@ -0,0 +1,9 @@ +--- +layout: sip +title: SID-9 - Scala Specialization +vote-status: complete +vote-text: This SIP has already been accepted and completed. +permalink: /sips/:title.html +--- + +This was an older SID that can be found [here](http://www.scala-lang.org/sid/9) diff --git a/_sips/sips/2010-06-01-picked-signatures.md b/_sips/sips/2010-06-01-picked-signatures.md new file mode 100644 index 0000000000..cdcef3c707 --- /dev/null +++ b/_sips/sips/2010-06-01-picked-signatures.md @@ -0,0 +1,9 @@ +--- +layout: sip +title: SID-10 - Storage of pickled Scala signatures in class files +vote-status: complete +vote-text: This SIP has already been accepted and completed. +permalink: /sips/:title.html +--- + +This was an older SID that can be found [here](http://www.scala-lang.org/sid/10) diff --git a/_sips/sips/2010-07-20-new-collection-classes.md b/_sips/sips/2010-07-20-new-collection-classes.md new file mode 100644 index 0000000000..25bff50646 --- /dev/null +++ b/_sips/sips/2010-07-20-new-collection-classes.md @@ -0,0 +1,9 @@ +--- +layout: sip +title: SID-3 - New Collection classes +vote-status: complete +vote-text: This SIP has already been accepted and completed. +permalink: /sips/:title.html +--- + +This was an older SID that can be found [here](http://www.scala-lang.org/sid/3) diff --git a/sips/completed/_posts/2011-10-12-implicit-classes.md b/_sips/sips/2011-10-12-implicit-classes.md similarity index 96% rename from sips/completed/_posts/2011-10-12-implicit-classes.md rename to _sips/sips/2011-10-12-implicit-classes.md index df61e2a619..61558987aa 100644 --- a/sips/completed/_posts/2011-10-12-implicit-classes.md +++ b/_sips/sips/2011-10-12-implicit-classes.md @@ -3,8 +3,9 @@ layout: sip discourse: true title: SIP-13 - Implicit classes -vote-status: accepted -vote-text: This SIP has already been accepted. +vote-status: complete +vote-text: This SIP has already been accepted and completed. +permalink: /sips/:title.html --- **By: Josh Suereth** @@ -90,5 +91,3 @@ Classes would describe the behavior of the construct. The new syntax should not break existing code, and so remain source compatible with existing techniques. - - diff --git a/_sips/sips/2011-10-13-string-interpolation.md b/_sips/sips/2011-10-13-string-interpolation.md new file mode 100644 index 0000000000..de12564ff7 --- /dev/null +++ b/_sips/sips/2011-10-13-string-interpolation.md @@ -0,0 +1,17 @@ +--- +layout: sip +title: SIP-11 - String Interpolation + +vote-status: complete +vote-text: This SIP has already been accepted and completed. We expect a SIP for 2.11 that will allow the desugared form of interpolated strings in the pattern matcher to become valid syntax. This SIP only allows the sugared interpolated strings to work. +permalink: /sips/:title.html +--- + +**By: Martin Odersky** + +This SIP is an embedded google document. If you have trouble with this embedded document, you can [visit the +document on Google Docs](https://docs.google.com/document/d/1NdxNxZYodPA-c4MLr33KzwzKFkzm9iW9POexT9PkJsU/edit?hl=en_US). + + diff --git a/sips/rejected/_posts/2011-10-13-uncluttering-control.md b/_sips/sips/2011-10-13-uncluttering-control.md similarity index 98% rename from sips/rejected/_posts/2011-10-13-uncluttering-control.md rename to _sips/sips/2011-10-13-uncluttering-control.md index 1b5e166b92..8170c53c70 100644 --- a/sips/rejected/_posts/2011-10-13-uncluttering-control.md +++ b/_sips/sips/2011-10-13-uncluttering-control.md @@ -5,6 +5,7 @@ redirect_from: "/sips/pending/uncluttering-control.html" vote-status: rejected vote-text: The committee votes unanimously to reject the change. The conclusion is that there is not a clear benefit for it and the required invested time and efforts would be too high. For more explanation, read the minutes. +permalink: /sips/:title.html --- **By: Martin Odersky** @@ -44,7 +45,7 @@ do-while is easy. Simply do the following: as syntax (i.e. drop the required parentheses around the condition). -While loops and for loops are more tricky. +While loops and for loops are more tricky. ## Part 3: while ## @@ -69,7 +70,7 @@ For while loops: while (expression1) do expression2 while expression3 To write a `do-while` inside a `while` loop you will need braces, like this: - + while (expression1) { do expression2 while epression3 } 3. In Scala 2.11: Disallow @@ -120,7 +121,3 @@ Here are some examples of expressions enabled by the changes. ## Discussion ## The new syntax removes more cases than it introduces. It also removes several hard to remember and non-orthogonal rules where you need parentheses, where you can have braces, and what the difference is. It thus makes the language simpler, more regular, and more pleasant to use. Some tricky situations with migration can be dealt with; and should apply anyway only in rare cases. - - - - diff --git a/sips/completed/_posts/2012-01-21-futures-promises.md b/_sips/sips/2012-01-21-futures-promises.md similarity index 98% rename from sips/completed/_posts/2012-01-21-futures-promises.md rename to _sips/sips/2012-01-21-futures-promises.md index 47e245b8e1..8158cfe598 100644 --- a/sips/completed/_posts/2012-01-21-futures-promises.md +++ b/_sips/sips/2012-01-21-futures-promises.md @@ -3,8 +3,9 @@ layout: sip discourse: true title: SIP-14 - Futures and Promises -vote-status: accepted -vote-text: This SIP has already been accepted. +vote-status: complete +vote-text: This SIP has already been accepted and completed. +permalink: /sips/:title.html --- **By: Philipp Haller, Aleksandar Prokopec, Heather Miller, Viktor Klang, Roland Kuhn, and Vojin Jovanovic** @@ -44,9 +45,9 @@ promises are created through such `ExecutionContext`s. For example, this makes i A future is an abstraction which represents a value which may become available at some point. A `Future` object either holds a result of a -computation or an exception in the case that the computation failed. -An important property of a future is that it is in effect immutable-- -it can never be written to or failed by the holder of the `Future` object. +computation or an exception in the case that the computation failed. +An important property of a future is that it is in effect immutable-- +it can never be written to or failed by the holder of the `Future` object. The simplest way to create a future object is to invoke the `future` method which starts an asynchronous computation and returns a @@ -59,7 +60,7 @@ After opening a new session we want to create an asynchronous request to the server for this list: import scala.concurrent.Future - + val session = socialNetwork.createSessionFor("user", credentials) val f: Future[List[Friend]] = Future { session.getFriends @@ -71,7 +72,7 @@ responds. An unsuccessful attempt may result in an exception. In the following example, the `session` value is incorrectly initialized, so the future will hold a `NullPointerException` instead of the value: - + val session = null val f: Future[List[Friend]] = Future { session.getFriends @@ -80,13 +81,13 @@ initialized, so the future will hold a `NullPointerException` instead of the val ### Callbacks We are generally interested in the result value of the computation. To -obtain the future's result, a client of the future would have to block -until the future is completed. Although this is allowed by the `Future` -API as we will show later in this document, a better way to do it is in a -completely non-blocking way, by registering a callback on the future. This -callback is called asynchronously once the future is completed. If the -future has already been completed when registering the callback, then -the callback may either be executed asynchronously, or sequentially on +obtain the future's result, a client of the future would have to block +until the future is completed. Although this is allowed by the `Future` +API as we will show later in this document, a better way to do it is in a +completely non-blocking way, by registering a callback on the future. This +callback is called asynchronously once the future is completed. If the +future has already been completed when registering the callback, then +the callback may either be executed asynchronously, or sequentially on the same thread. The most general form of registering a callback is by using the `onComplete` @@ -104,7 +105,7 @@ We do so by calling the method `getRecentPosts` which returns a `List[String]`: val f: Future[List[String]] = Future { session.getRecentPosts } - + f onComplete { case Right(posts) => for (post <- posts) render(post) case Left(t) => render("An error has occured: " + t.getMessage) @@ -118,7 +119,7 @@ callback is used (which takes a partial function): val f: Future[List[String]] = Future { session.getRecentPosts } - + f onSuccess { case posts => for (post <- posts) render(post) } @@ -128,7 +129,7 @@ To handle failed results, the `onFailure` callback is used: val f: Future[List[String]] = Future { session.getRecentPosts } - + f onFailure { case t => render("An error has occured: " + t.getMessage) } @@ -151,7 +152,7 @@ particular `Throwable`. In the following example the registered callback is neve val f = Future { 2 / 0 } - + f onFailure { case npe: NullPointerException => println("I'd be amazed if this printed out.") @@ -218,13 +219,13 @@ be done using callbacks: val rateQuote = Future { connection.getCurrentValue(USD) } - + rateQuote onSuccess { case quote => val purchase = Future { if (isProfitable(quote)) connection.buy(amount, quote) else throw new Exception("not profitable") } - + purchase onSuccess { case _ => println("Purchased " + amount + " USD") } @@ -250,12 +251,12 @@ rewrite the previous example using the `map` combinator: val rateQuote = Future { connection.getCurrentValue(USD) } - + val purchase = rateQuote map { quote => if (isProfitable(quote)) connection.buy(amount, quote) else throw new Exception("not profitable") } - + purchase onSuccess { case _ => println("Purchased " + amount + " USD") } @@ -280,13 +281,13 @@ Here is an example of `flatMap` usage within for-comprehensions: val usdQuote = Future { connection.getCurrentValue(USD) } val chfQuote = Future { connection.getCurrentValue(CHF) } - + val purchase = for { usd <- usdQuote chf <- chfQuote if isProfitable(usd, chf) } yield connection.buy(amount, chf) - + purchase onSuccess { case _ => println("Purchased " + amount + " CHF") } @@ -350,9 +351,9 @@ the case it fails to obtain the dollar value: } map { chf => "Value: " + chf + "CHF" } - + val anyQuote = usdQuote fallbackTo chfQuote - + anyQuote onSuccess { println(_) } The `either` combinator creates a new future which either holds @@ -370,9 +371,9 @@ the quote which is returned first gets printed: } map { chf => "Value: " + chf + "CHF" } - + val anyQuote = usdQuote either chfQuote - + anyQuote onSuccess { println(_) } The `andThen` combinator is used purely for side-effecting purposes. @@ -460,17 +461,17 @@ end of the application to make sure that all of the futures have been completed. Here is an example of how to block on the result of a future: import scala.concurrent._ - + def main(args: Array[String]) { val rateQuote = Future { connection.getCurrentValue(USD) } - + val purchase = rateQuote map { quote => if (isProfitable(quote)) connection.buy(amount, quote) else throw new Exception("not profitable") } - + blocking(purchase, 0 ns) } @@ -555,16 +556,16 @@ may be the case that `p.future == p`. Consider the following producer-consumer example: import scala.concurrent.{ Future, Promise } - + val p = Promise[T]() val f = p.future - + val producer = Future { val r = produceSomething() p success r continueDoingSomethingUnrelated() } - + val consumer = Future { startDoingSomething() f onSuccess { @@ -586,11 +587,11 @@ such, they can be completed only once. Calling `success` on a promise that has already been completed (or failed) will throw an `IllegalStateException`. -The following example shows how to fail a promise. +The following example shows how to fail a promise. val p = Promise[T]() val f = p.future - + val producer = Future { val r = someComputation if (isInvalid(r)) @@ -638,9 +639,9 @@ the result of that future as well. The following program prints `1`: val f = Future { 1 } val p = Promise[Int]() - + p completeWith f - + p.future onSuccess { case x => println(x) } @@ -682,7 +683,7 @@ To simplify handling of time in concurrent applications `scala.concurrent` Abstract `Duration` contains methods that allow : -1. Conversion to different time units (`toNanos`, `toMicros`, `toMillis`, +1. Conversion to different time units (`toNanos`, `toMicros`, `toMillis`, `toSeconds`, `toMinutes`, `toHours`, `toDays` and `toUnit(unit: TimeUnit)`). 2. Comparison of durations (`<`, `<=`, `>` and `>=`). 3. Arithmetic operations (`+`, `-`, `*`, `/` and `unary_-`). @@ -692,17 +693,17 @@ Abstract `Duration` contains methods that allow : `Duration` can be instantiated in the following ways: 1. Implicitly from types `Int` and `Long`. For example `val d = 100 millis`. -2. By passing a `Long` length and a `java.util.concurrent.TimeUnit`. +2. By passing a `Long` length and a `java.util.concurrent.TimeUnit`. For example `val d = Duration(100, MILLISECONDS)`. 3. By parsing a string that represent a time period. For example `val d = Duration("1.2 µs")`. - + Duration also provides `unapply` methods so it can be used in pattern matching constructs. Examples: import scala.concurrent.util.Duration import scala.concurrent.util.duration._ import java.util.concurrent.TimeUnit._ - + // instantiation val d1 = Duration(100, MILLISECONDS) // from Long and TimeUnit val d2 = Duration(100, "millis") // from Long and String @@ -730,4 +731,3 @@ Examples: ## Appendix A: API Traits An implementation is available at [http://github.com/phaller/scala](https://github.com/phaller/scala/tree/execution-context/src/library/scala/concurrent). (Reasonably stable implementation, though possibility of flux.) - diff --git a/sips/completed/_posts/2012-01-30-value-classes.md b/_sips/sips/2012-01-30-value-classes.md similarity index 94% rename from sips/completed/_posts/2012-01-30-value-classes.md rename to _sips/sips/2012-01-30-value-classes.md index 5c7f89c448..4b6a202abd 100644 --- a/sips/completed/_posts/2012-01-30-value-classes.md +++ b/_sips/sips/2012-01-30-value-classes.md @@ -2,8 +2,9 @@ layout: sip title: SIP-15 - Value Classes -vote-status: accepted -vote-text: This SIP has already been accepted. There has been concern for numerical computing. We think future SIP(s), using work from SIP-15, can provide more benefit to numerical computing users. The SIP as it exists benefits all users of implicit enrichment classes, and takes us much further to unboxed high performance code. This SIP does not exclude further work towards improving numerical computing in Scala. +vote-status: complete +vote-text: This SIP has already been accepted and completed. There has been concern for numerical computing. We think future SIP(s), using work from SIP-15, can provide more benefit to numerical computing users. The SIP as it exists benefits all users of implicit enrichment classes, and takes us much further to unboxed high performance code. This SIP does not exclude further work towards improving numerical computing in Scala. +permalink: /sips/:title.html --- **By: Martin Odersky and Jeff Olson and Paul Phillips and Joshua Suereth** @@ -192,7 +193,7 @@ Generally, a call p.m(args) where `m` is an extractable method declared in a value class `C` gets rewritten to - + C.extension$m(p, args) For instance the two calls in the following code fragment @@ -320,31 +321,31 @@ follows. After all 4 steps the `Meter` class is translated to the following code. class Meter(val underlying: Double) extends AnyVal with Printable { - def plus (other: Meter): Meter = + def plus (other: Meter): Meter = new Meter(Meter.extension$plus(this.underlying, other.underlying)) - def divide (other: Meter): Double = + def divide (other: Meter): Double = Meter.extension1$divide(this.underlying, other) - def divide (factor: Double): Meter = + def divide (factor: Double): Meter = new Meter(Meter.extension2$divide(this.underlying, factor)) - def less (other: Meter): Boolean = + def less (other: Meter): Boolean = Meter.extension$less(this.underlying, other) - override def toString: String = + override def toString: String = Meter.extension$toString(this.underlying) - override def equals(other: Any) = + override def equals(other: Any) = Meter.extension$equals(this) - override def hashCode = + override def hashCode = Meter.extension$hashCode(this) } object Meter { - def extension$plus($this: Double, other: Double) = + def extension$plus($this: Double, other: Double) = $this + other - def extension1$divide($this: Double, other: Double): Double = + def extension1$divide($this: Double, other: Double): Double = $this / other - def extension2$divide($this: Double, factor: Double): Double = + def extension2$divide($this: Double, factor: Double): Double = $this / factor) def extension$less($this: Double, other: Double): Boolean = $this < other - def extension$toString($this: Double): String = + def extension$toString($this: Double): String = $this.toString + “m” def extension$equals($this: Double, other: Object) = other match { case that: Meter => $this == that.underlying diff --git a/sips/rejected/_posts/2012-03-09-self-cleaning-macros.md b/_sips/sips/2012-03-09-self-cleaning-macros.md similarity index 84% rename from sips/rejected/_posts/2012-03-09-self-cleaning-macros.md rename to _sips/sips/2012-03-09-self-cleaning-macros.md index af5e8c6369..1ceccd3806 100644 --- a/sips/rejected/_posts/2012-03-09-self-cleaning-macros.md +++ b/_sips/sips/2012-03-09-self-cleaning-macros.md @@ -4,11 +4,12 @@ title: SIP-16 - Self-cleaning Macros vote-status: rejected vote-text: The proposal is rejected unanimously because Scala Meta, the successor metaprogramming tool, is coming soon. For more explanation, read the minutes. +permalink: /sips/:title.html --- This SIP is an embedded google document. If you have trouble with this embedded document, you can visit the [document on Google Docs](https://docs.google.com/document/d/1O879Iz-567FzVb8kw6N5OBpei9dnbW0ZaT7-XNSa6Cs/edit?hl=en_US). + src="https://docs.google.com/document/d/1O879Iz-567FzVb8kw6N5OBpei9dnbW0ZaT7-XNSa6Cs/preview?" + style="width:572px;height:800px;"> diff --git a/_sips/sips/2012-03-13-type-dynamic.md b/_sips/sips/2012-03-13-type-dynamic.md new file mode 100644 index 0000000000..16ff7799ac --- /dev/null +++ b/_sips/sips/2012-03-13-type-dynamic.md @@ -0,0 +1,15 @@ +--- +layout: sip +title: SIP-17 - Type Dynamic + +vote-status: complete +vote-text: This SIP has already been accepted and completed. +permalink: /sips/:title.html +--- + + +This SIP is an embedded google document. If you have trouble with this embedded document, you can visit the [document on Google Docs](https://docs.google.com/document/d/1XaNgZ06AR7bXJA9-jHrAiBVUwqReqG4-av6beoLaf3U/edit). + + diff --git a/sips/completed/_posts/2012-03-17-modularizing-language-features.md b/_sips/sips/2012-03-17-modularizing-language-features.md similarity index 76% rename from sips/completed/_posts/2012-03-17-modularizing-language-features.md rename to _sips/sips/2012-03-17-modularizing-language-features.md index 651b3e0044..420aedf4d1 100644 --- a/sips/completed/_posts/2012-03-17-modularizing-language-features.md +++ b/_sips/sips/2012-03-17-modularizing-language-features.md @@ -2,8 +2,9 @@ layout: sip title: SIP-18 - Modularizing Language Features -vote-status: accepted -vote-text: This is in Accept status, pending implementation. Let the record show Paul is against it. +vote-status: complete +vote-text: This SIP has already been accepted and completed. Let the record show Paul is against it. +permalink: /sips/:title.html --- @@ -11,6 +12,6 @@ This SIP is an embedded google document. If you have trouble with this embedded To see or contribute to the discussion on this topic, please see the [thread](https://groups.google.com/forum/?fromgroups#!topic/scala-sips/W5CGmauii8A) on the [scala-sips](https://groups.google.com/forum/?fromgroups#!forum/scala-sips) mailing list. - + diff --git a/sips/rejected/_posts/2012-03-30-source-locations.md b/_sips/sips/2012-03-30-source-locations.md similarity index 99% rename from sips/rejected/_posts/2012-03-30-source-locations.md rename to _sips/sips/2012-03-30-source-locations.md index d6a228382a..255f719d4b 100644 --- a/sips/rejected/_posts/2012-03-30-source-locations.md +++ b/_sips/sips/2012-03-30-source-locations.md @@ -5,6 +5,7 @@ title: SIP-19 - Implicit Source Locations vote-status: rejected vote-text: The proposal is rejected. We expect this to be easily implemented using macros without going through a full SIP. A modern implementation can be found here. +permalink: /sips/:title.html --- **Philipp Haller** @@ -26,7 +27,7 @@ To obtain the source location for each invocation of a method `debug`, say, one def debug(message: String)(implicit loc: SourceLocation): Unit = { println("@" + loc.fileName + ":" + loc.line + ": " + message) } - + This means that inside the body of the `debug` method, we can access the source location of its current invocation through the `loc` parameter. For example, suppose we are invoking `debug` on line `34` in a file `"MyApp.scala"`: @@ -43,10 +44,10 @@ The `SourceLocation` trait is a new type member of the `scala.reflect` package. trait SourceLocation { /** The name of the source file */ def fileName: String - + /** The line number */ def line: Int - + /** The character offset */ def charOffset: Int } @@ -64,4 +65,3 @@ First, if there is already an implicit argument of type `SourceLocation`, this a An implementation of this proposal can be found at: [https://github.com/phaller/scala/tree/topic/source-location](https://github.com/phaller/scala/tree/topic/source-location) An extension of this proposal is also part of Scala-Virtualized. The extension adds a subtrait `SourceContext` which in addition provides access to information, such as variable names in the context of a method invocation. More information can be found at: [https://github.com/TiarkRompf/scala-virtualized/wiki/SourceLocation-and-SourceContext](https://github.com/TiarkRompf/scala-virtualized/wiki/SourceLocation-and-SourceContext) - diff --git a/sips/pending/_posts/2013-05-31-improved-lazy-val-initialization.md b/_sips/sips/2013-05-31-improved-lazy-val-initialization.md similarity index 99% rename from sips/pending/_posts/2013-05-31-improved-lazy-val-initialization.md rename to _sips/sips/2013-05-31-improved-lazy-val-initialization.md index 3478c00e04..4d5b8643f1 100644 --- a/sips/pending/_posts/2013-05-31-improved-lazy-val-initialization.md +++ b/_sips/sips/2013-05-31-improved-lazy-val-initialization.md @@ -3,8 +3,9 @@ layout: sip discourse: true title: SIP-20 - Improved Lazy Vals Initialization -vote-status: under review -vote-text: Dormant. This proposal lacks an implementation for Scalac and is looking for a new owner. +vote-status: dormant +vote-text: This proposal lacks an implementation for Scalac and is looking for a new owner. +permalink: /sips/:title.html --- **By: Aleksandar Prokopec, Dmitry Petrashko, Miguel Garcia, Jason Zaugg, Hubert Plociniczak, Viktor Klang, Martin Odersky** @@ -675,11 +676,11 @@ The local lazy vals implementation is around 6x faster than the current version, The concrete micro-benchmark code is available as a GitHub repo \[[6][6]\]. It additionally benchmarks many other implementations that are not covered in the text of this SIP, in particular it tests versions based on MethodHandles and runtime code generation as well as versions that use additional spinning before synchronizing on the monitor. For those wishing to reproduce the results, the benchmarking suite takes 90 minutes to run on contemporary CPUs. Enabling all the disabled benchmarks, in particular those that evaluate the `invokeDynamic` based implementation, will make the benchmarks take around 5 hours. -The final result of those benchmarks is that amount proposed versions, the two that worth considering are (V4-general) and (V6). +The final result of those benchmarks is that amount proposed versions, the two that worth considering are (V4-general) and (V6). They both perform better than the current implementation in all the contended case. Specifically, in the contended case, V6 is 2 times fater than V1, while V4-general is 4 times faster. Unfortunately V4-general is 30% slower in the uncontended case than current implemetation(V1), while V6 is in the same ballpark, being up to 5% slower or faster depending on the setup of the benchmark. - + Based on this, we propose V6 to be used as default in future versions of Scala. ### Code size ### @@ -696,7 +697,7 @@ Dotty implementation internally uses `@static` proposed in \[[16][16]\]. Both Dotty and released Scala 2.12 already implement "Elegant Local lazy vals". This was incorporated in the 2.12 release before this SIP was considered, as it was fixing a bug that blocked release\[[14][14]\]. ### Unsafe ### -The proposed version, V6 relies on `sun.misc.Unsafe` in order to implement it's behaviour. +The proposed version, V6 relies on `sun.misc.Unsafe` in order to implement it's behaviour. While `sun.misc.Unsafe` will remain availabe in Java9 there's an intention to deprecate it and replace it with VarHandles.\[[20][20]\]. The proposed version V6 can be implemented with using functionality present in Var Handles. diff --git a/sips/pending/_posts/2013-06-10-spores.md b/_sips/sips/2013-06-10-spores.md similarity index 99% rename from sips/pending/_posts/2013-06-10-spores.md rename to _sips/sips/2013-06-10-spores.md index ce0da774a5..f1f595b621 100644 --- a/sips/pending/_posts/2013-06-10-spores.md +++ b/_sips/sips/2013-06-10-spores.md @@ -3,8 +3,9 @@ layout: sip discourse: true title: SIP-21 - Spores -vote-status: under review +vote-status: "under-review" vote-text: Next iteration takes place in January/February 2017 by request of the authors. +permalink: /sips/:title.html --- **By: Heather Miller, Martin Odersky, and Philipp Haller** @@ -457,5 +458,3 @@ Similar to example 1, the problematic capturing of `this` is avoided, since used inside the spore's closure. As a result, `fun` can now be serialized without runtime errors, since `h` refers to a serializable object (a case class instance). - - diff --git a/sips/pending/_posts/2013-06-30-async.md b/_sips/sips/2013-06-30-async.md similarity index 98% rename from sips/pending/_posts/2013-06-30-async.md rename to _sips/sips/2013-06-30-async.md index becd6ecf36..f051bfe986 100644 --- a/sips/pending/_posts/2013-06-30-async.md +++ b/_sips/sips/2013-06-30-async.md @@ -3,8 +3,9 @@ layout: sip discourse: true title: SIP-22 - Async -vote-status: postponed -vote-text: Authors have marked this proposal as dormant. Details in the implementation need to be figured out. Check July's minutes. +vote-status: dormant +vote-text: Authors have marked this proposal as dormant. Details in the implementation need to be figured out. Check July 2016's minutes. +permalink: /sips/:title.html --- **By: Philipp Haller and Jason Zaugg** @@ -296,6 +297,3 @@ simplifications). [1]: http://docs.scala-lang.org/overviews/core/futures.html "ScalaFutures" [2]: http://www.playframework.com/ "Play" [3]: https://github.com/scala/async "ScalaAsync" - - - diff --git a/sips/pending/_posts/2014-06-27-42.type.md b/_sips/sips/2014-06-27-42.type.md similarity index 99% rename from sips/pending/_posts/2014-06-27-42.type.md rename to _sips/sips/2014-06-27-42.type.md index 9207a4a4ce..e03c3a97c1 100644 --- a/sips/pending/_posts/2014-06-27-42.type.md +++ b/_sips/sips/2014-06-27-42.type.md @@ -4,12 +4,13 @@ discourse: true title: SIP-23 - Literal-based singleton types vote-status: pending +permalink: /sips/:title.html --- **By: George Leontiev, Eugene Burmako, Jason Zaugg, Adriaan Moors, Paul Phillips, Oron Port** ## History - + | Date | Version | | ---------------|--------------------------------------------------------------------| | Jun 27th 2014 | Initial SIP | @@ -104,14 +105,14 @@ There are quite a few use cases we’ve collected on the [mailing list](https:// ### Spire -Singleton types will be useful for defining a type like `Residue[13.type]` (Z/13Z, the group of integers modulo 13). We could then mandate that residues can be added only when they are parameterized on the same number. Possibly something like: +Singleton types will be useful for defining a type like `Residue[13.type]` (Z/13Z, the group of integers modulo 13). We could then mandate that residues can be added only when they are parameterized on the same number. Possibly something like: case class Residue[N <: Int : SingleInhabitant](n: Long) { lhs => def +(rhs: Residue[N]): Residue[N] = Residue((lhs.n + rhs.n) % inhabitant[N]) } -Another use is to help with property based testing. In Spire, there is a Ranged type that makes it easy to ask for numbers in a particular range in ScalaCheck: +Another use is to help with property based testing. In Spire, there is a Ranged type that makes it easy to ask for numbers in a particular range in ScalaCheck: forAll { x: Ranged[Int, 1, 100] => val n: Int = x.value // guaranteed to be 1 to 100 diff --git a/sips/pending/_posts/2015-6-18-repeated-byname.md b/_sips/sips/2015-6-18-repeated-byname.md similarity index 97% rename from sips/pending/_posts/2015-6-18-repeated-byname.md rename to _sips/sips/2015-6-18-repeated-byname.md index 4921cdaaec..019bed8f99 100644 --- a/sips/pending/_posts/2015-6-18-repeated-byname.md +++ b/_sips/sips/2015-6-18-repeated-byname.md @@ -5,6 +5,8 @@ discourse: true vote-status: dormant vote-text: Looking for a new owner. This proposal needs to be updated according to the SIP meeting in November 2016. +permalink: /sips/:title.html + --- **By: Martin Odersky** diff --git a/sips/pending/_posts/2015-6-18-trait-parameters.md b/_sips/sips/2015-6-18-trait-parameters.md similarity index 98% rename from sips/pending/_posts/2015-6-18-trait-parameters.md rename to _sips/sips/2015-6-18-trait-parameters.md index 8cf6de0bd0..7460c0bd3d 100644 --- a/sips/pending/_posts/2015-6-18-trait-parameters.md +++ b/_sips/sips/2015-6-18-trait-parameters.md @@ -3,8 +3,9 @@ layout: sip title: SIP 25 - Trait Parameters discourse: true -vote-status: under review +vote-status: "under-review" vote-text: The board agreed to schedule the next iteration of the evaluation process in 6 months, since there’s no implementation yet and the authors need time to produce one. +permalink: /sips/:title.html --- __Martin Odersky__ diff --git a/sips/pending/_posts/2016-01-11-static-members.md b/_sips/sips/2016-01-11-static-members.md similarity index 97% rename from sips/pending/_posts/2016-01-11-static-members.md rename to _sips/sips/2016-01-11-static-members.md index 089961e231..23b0d3e4e9 100644 --- a/sips/pending/_posts/2016-01-11-static-members.md +++ b/_sips/sips/2016-01-11-static-members.md @@ -3,8 +3,9 @@ layout: sip title: SIP 25 - @static fields and methods in Scala objects(SI-4581) discourse: true -vote-status: under review +vote-status: "under-review" vote-text: Authors need to update the proposal before the next review. +permalink: /sips/:title.html --- __Dmitry Petrashko, Sébastien Doeraene and Martin Odersky__ @@ -88,14 +89,14 @@ Scalac currently generates static forwarders for fields and methods in top-level {% highlight scala %} object O { - val d = 1 + val d = 1 object I { val f = 1 } } {% endhighlight %} -Under the proposed scheme users will be able to opt-in to have the field `f` defined in the inner object `I` emited as a static field. +Under the proposed scheme users will be able to opt-in to have the field `f` defined in the inner object `I` emited as a static field. In case `O.d` is annotated with `@static` the field will be created as a static field `d` in `class O`. If not annotated, it will be created in the companion module with a static forwarder `d` in `class O`. @@ -109,7 +110,7 @@ The following rules ensure that methods can be correctly compiled into static me 3. The right hand side of a method or field annotated with `@static` can only refer to top-level classes, members of globally accessible objects and `@static` members. In particular, for non-static objects `this` is not accesible. `super` is never accessible. -4. If a member `foo` of an `object C` is annotated with `@static`, the companion class `C` is not allowed to define term members with name `foo`. +4. If a member `foo` of an `object C` is annotated with `@static`, the companion class `C` is not allowed to define term members with name `foo`. 5. If a member `foo` of an `object C` is annotated with `@static`, the companion class `C` is not allowed to inherit classes that define a term member with name `foo`. @@ -143,32 +144,32 @@ It also does not address the question of `@static` members in inner objects and ## Open questions ## - @static lazy val - + ## Initialization order discussion ## In general, emission of static fields could affect the initialization order and change semantics. This SIP solves this by enforcing (rule `2`) that `@static` fields and expressions preceed non-static fields. -This means that no code precedes the `@static` field initialization which makes it hard to observe the difference between if the field is initialized statically or not, +This means that no code precedes the `@static` field initialization which makes it hard to observe the difference between if the field is initialized statically or not, since fields are initialized in the order `as written`, similar to how normal fields are initialized. -The `@static` proposal is similar to `@tailrec` in a sense that it fails compilation in the case where the user did not write code that follows the aforementioned rules. +The `@static` proposal is similar to `@tailrec` in a sense that it fails compilation in the case where the user did not write code that follows the aforementioned rules. These rules exist to enforce the unlikelyhood of an observable difference in semantics if `@static` annotations are dropped; The restrictions in this SIP make it hard to observe changes in initialization within the same object. It is still possible to observe those changes using multiple classes and side effects within initializers: {% highlight scala %} -class C { - val x = {println("x"); 1 } +class C { + val x = {println("x"); 1 } } -object O extends C { - val y = { println("y"); 2 } +object O extends C { + val y = { println("y"); 2 } // prints: // x // y } - -object Os extends C { + +object Os extends C { @static val y = { println("y"); 2 } // prints: // y @@ -178,7 +179,7 @@ object Os extends C { Static fields can be initialized earlier than they used to be initialized while being non-static, but never later. -By requiring `@static` first to be defined first inside the object, +By requiring `@static` first to be defined first inside the object, we guarantee that you can't observe the changes in initialization withing the same object without resorting to code which either uses `Unsafe` or exhibits undefined behaviour under the JVM. ## Could `@static` be a `@tailrec`-like annotation that doesn't affect code generation but only checks ## @@ -203,7 +204,7 @@ Let's consider possible options: - if the field `c` is emitted as `static` on the bytecode level, it will be initialized before the `c` in superclass is initialized, reordering side-effects in initializers; - if the field `c` is _not_ emitted as `static` but the field `d` is, then the order of initialization would also be affected, reordering side-effects. - + Based on the previous study done in preparation for this SIP, the authors believe that the only reasonable way to maintain current sematics would be to say that such alternative would require these rules: - only the fields which were not declared by parents of the object can be emitted as static; diff --git a/sips/completed/_posts/2016-06-25-trailing-commas.md b/_sips/sips/2016-06-25-trailing-commas.md similarity index 97% rename from sips/completed/_posts/2016-06-25-trailing-commas.md rename to _sips/sips/2016-06-25-trailing-commas.md index 2e447ccbeb..4df27f9cf9 100644 --- a/sips/completed/_posts/2016-06-25-trailing-commas.md +++ b/_sips/sips/2016-06-25-trailing-commas.md @@ -1,11 +1,12 @@ --- -layout: sip +layout: inner-page-no-masthead discourse: true title: SIP-27 - Trailing Commas redirect_from: "/sips/pending/trailing-commas.html" -vote-status: accepted -vote-text: This SIP has been Accepted, and is a part of Scala 2.12.2. +vote-status: complete +vote-text: This SIP has already been accepted and completed, and is a part of Scala 2.12.2. +permalink: /sips/:title.html --- **By: Dale Wijnand** diff --git a/sips/rejected/_posts/2016-07-25-unsigned-integers.md b/_sips/sips/2016-07-25-unsigned-integers.md similarity index 99% rename from sips/rejected/_posts/2016-07-25-unsigned-integers.md rename to _sips/sips/2016-07-25-unsigned-integers.md index 92d4090fdd..1a0428c246 100644 --- a/sips/rejected/_posts/2016-07-25-unsigned-integers.md +++ b/_sips/sips/2016-07-25-unsigned-integers.md @@ -4,6 +4,7 @@ title: SIP-26 - Unsigned Integers vote-status: rejected vote-text: The committee votes to reject the proposal because of a 6% performance hit on the provided implementation by the authors. +permalink: /sips/:title.html --- __Sébastien Doeraene and Denys Shabalin__ diff --git a/sips/pending/_posts/2016-09-09-inline-meta.md b/_sips/sips/2016-09-09-inline-meta.md similarity index 99% rename from sips/pending/_posts/2016-09-09-inline-meta.md rename to _sips/sips/2016-09-09-inline-meta.md index 21c51c0d66..1ef66a2fdb 100644 --- a/sips/pending/_posts/2016-09-09-inline-meta.md +++ b/_sips/sips/2016-09-09-inline-meta.md @@ -3,8 +3,9 @@ layout: sip discourse: true title: SIP-28 and SIP-29 - Inline meta -vote-status: under revision +vote-status: "under-revision" vote-text: The following proposal has been split and numbered as SIP-28 (Inline) and SIP-29 (Meta). For more information on this decision, check the minutes. The authors need to split the proposal and update it. +permalink: /sips/:title.html --- **By: Eugene Burmako, Sébastien Doeraene, Vojin Jovanovic, Martin Odersky, Dmitry Petrashko, Denys Shabalin** diff --git a/sips/pending/_posts/2017-01-11-refer-other-arguments-in-args.md b/_sips/sips/2017-01-11-refer-other-arguments-in-args.md similarity index 98% rename from sips/pending/_posts/2017-01-11-refer-other-arguments-in-args.md rename to _sips/sips/2017-01-11-refer-other-arguments-in-args.md index cc0a191573..822ed9928c 100644 --- a/sips/pending/_posts/2017-01-11-refer-other-arguments-in-args.md +++ b/_sips/sips/2017-01-11-refer-other-arguments-in-args.md @@ -2,6 +2,9 @@ layout: sip discourse: true title: SIP-NN - Allow referring to other arguments in default parameters + +vote-status: pending +permalink: /sips/:title.html --- **By: Pathikrit Bhowmick** diff --git a/sips/pending/_posts/2017-01-13-binary-compatibility.md b/_sips/sips/2017-01-13-binary-compatibility.md similarity index 91% rename from sips/pending/_posts/2017-01-13-binary-compatibility.md rename to _sips/sips/2017-01-13-binary-compatibility.md index c0b22b3fc4..90720c1beb 100644 --- a/sips/pending/_posts/2017-01-13-binary-compatibility.md +++ b/_sips/sips/2017-01-13-binary-compatibility.md @@ -2,6 +2,9 @@ layout: sip title: SIP-NN - Improving binary compatibility with @stableABI discourse: true + +vote-status: pending +permalink: /sips/:title.html --- __Dmitry Petrashko__ @@ -16,42 +19,42 @@ This SIP introduces an annotation `@stableABI` that checks that `what you write `@stableABI` is a linter, that does not change binary output, but will fail compilation if Public API of a class uses features of Scala that are desugared by compiler and may be binary incompatible across major releases. - -As long as declarations in source have not changed, `@stableABI` annotated classes will be compatible across major versions of Scala. + +As long as declarations in source have not changed, `@stableABI` annotated classes will be compatible across major versions of Scala. It complements MiMa\[[2]\] in indicating if a class will remain binary compatible across major Scala releases. ## Term definitions * ##### Binary descriptors -As defined by the JVM spec\[[4]\]: +As defined by the JVM spec\[[4]\]: > A descriptor is a string representing the type of a field or method. Descriptors are represented in the class file format using modified UTF-8 strings (§4.4.7) > and thus may be drawn, where not further constrained, from the entire Unicode codespace. > - > A method descriptor contains zero or more parameter descriptors, representing the types of parameters that the method takes, and a return descriptor, representing the type of the value (if any) that the method returns. - + > A method descriptor contains zero or more parameter descriptors, representing the types of parameters that the method takes, and a return descriptor, representing the type of the value (if any) that the method returns. + Binary descriptors are used in the bytecode to indicate what fields and methods are accessed or invoked. If a method or field has its descriptor changed, previously compiled classes that used different descriptor will fail in runtime as they no longer link to the changed field. - + In this document we use the term `binary descriptor` to refer to both method and field descriptors used by the JVM. - + * ##### Public API - - Methods and fields marked with `ACC_PUBLIC`\[[5]\] may be accessed from any class and package. + + Methods and fields marked with `ACC_PUBLIC`\[[5]\] may be accessed from any class and package. This loosely corresponds to absence of AccessModifier\[[6]\] in Scala source. - Changing a binary descriptor of a method or a field marked with `ACC_PUBLIC` is a binary incompatible change + Changing a binary descriptor of a method or a field marked with `ACC_PUBLIC` is a binary incompatible change which may affect all classes in all packages leading to a runtime linkage failure. - + Methods and fields marked with `ACC_PROTECTED`\[[5]\] may be accessed within subclasses. - This loosely corresponds to presence of `protected` AccessModifier\[[6]\] in Scala source. - Changing a binary descriptor of a method or a field marked with `ACC_PROTECTED` is a binary incompatible change + This loosely corresponds to presence of `protected` AccessModifier\[[6]\] in Scala source. + Changing a binary descriptor of a method or a field marked with `ACC_PROTECTED` is a binary incompatible change which may affect all subclasses of this class leading to a runtime linkage failure. - - In this document we use the term `Public API` to refer both to methods and fields defined as `ACC_PUBLIC` and `ACC_PROTECTED`. + + In this document we use the term `Public API` to refer both to methods and fields defined as `ACC_PUBLIC` and `ACC_PROTECTED`. Changes do binary descriptors of Public API may lead to runtime linkage failures. - + * ##### Binary compatibility Two versions of the same class are called binary compatible if there are no changes to the Public API of this class, @@ -61,22 +64,22 @@ As defined by the JVM spec\[[4]\]: 1. Publishing a library that would work across major Scala versions, such as 2.12 & 2.13 and Dotty. 2. Defining a class which is supposed to be used from other JVM languages such as Java\Kotlin. -`@stableABI` will ensure both binary compatibility and that there are no unexpected methods +`@stableABI` will ensure both binary compatibility and that there are no unexpected methods that would show up in members of a class or an interface. 3. Library authors can take advantage of language features introduced in new major versions of Scala while still serving users on older language versions by defining their Public API as `@stableABI`. - -The important use-case envisioned here by the authors is migration to Dotty. -We envision that there might be code-bases that for some reason don't compile either with Dotty or with Scalac. -This can be either because they rely on union types, only present in Dotty, + +The important use-case envisioned here by the authors is migration to Dotty. +We envision that there might be code-bases that for some reason don't compile either with Dotty or with Scalac. +This can be either because they rely on union types, only present in Dotty, or because they need early initializers, which are only supported by Scalac. -At the same time, by marking either those classes themselves or their parents as `@stableABI`, -the compiled artifacts could be used in both Dotty-compiled and Scalac-compiled projects. - +At the same time, by marking either those classes themselves or their parents as `@stableABI`, +the compiled artifacts could be used in both Dotty-compiled and Scalac-compiled projects. + ## Current Status -In case there's a need to develop an API that will be used by clients compiled using different major versions of Scala, +In case there's a need to develop an API that will be used by clients compiled using different major versions of Scala, the current approach is to either develop them in Java or to use best guess to restrict what Scala features should be used. There's also a different approach which is used by sbt: instead of publishing a binary `compiler-interface`, sources are published instead that would be locally compiled. @@ -84,21 +87,21 @@ There's also a different approach which is used by sbt: instead of publishing a Examples: 1. Zinc\[[8]\] is writing their interfaces in Java because the interface has to be Scala version agnostic, as it is shipped in every sbt release, independently of Scala version that was used to compile zinc or will be used in to compile the project. -sbt additionally compiles on demand the compiler bridge, which implements this Java interface. +sbt additionally compiles on demand the compiler bridge, which implements this Java interface. - 2. Dotty\[[7]\] currently uses java defined interfaces as public API for IntelliJ in order to ensure binary compatibility. + 2. Dotty\[[7]\] currently uses java defined interfaces as public API for IntelliJ in order to ensure binary compatibility. These interfaces can be replaced by `@stableABI` annotated traits to reach the same goal. ## Design Guidelines `@stableABI` is a feature which is supposed to be used by a small subset of the ecosystem to be binary compatible across major versions of Scala. -Thus this is designed as an advanced feature that is used rarely and thus is intentionally verbose. +Thus this is designed as an advanced feature that is used rarely and thus is intentionally verbose. It's designed to provide strong guarantees, in some cases sacrificing ease of use and to be used in combination with MiMa\[[2]\] - -The limitations enforced by `@stableABI` are designed to be an overapproximation: -instead of permitting a list of features known to be compatible, `@stableABI` enforces a stronger -check which is sufficient to promise binary compatibility. -This SIP intentionally follows a very conservative approach. +The limitations enforced by `@stableABI` are designed to be an overapproximation: +instead of permitting a list of features known to be compatible, `@stableABI` enforces a stronger +check which is sufficient to promise binary compatibility. + +This SIP intentionally follows a very conservative approach. This is because we will be able to allow more features later, but we won't have an opportunity to remove them. ## Overview ## @@ -111,7 +114,7 @@ In order for a class, trait or an object to succeed compilation with the `@stabl - changing binary descriptors of existing members, either concrete or abstract; `@stableABI` does not change the compilation scheme of a class: - compiling a class previously annotated with the `@stableABI`, will produce the same bytecode with or without `@stableABI` annotation. + compiling a class previously annotated with the `@stableABI`, will produce the same bytecode with or without `@stableABI` annotation. Below are several examples of classes and traits that succeed compilation with `@stableABI` @@ -158,7 +161,7 @@ class FeaturesInBodies { ## Features that will fail compilation with `@stableABI` The features listed below have complex encodings that may change in future versions. We prefer not to compromise on them. -Most of those features can be simulated in a binary compatible way by writing a verbose re-implemtation +Most of those features can be simulated in a binary compatible way by writing a verbose re-implemtation which won't rely on desugaring performed inside compiler. Note that while those features are prohibited in the public API, they can be safely used inside bodies of the methods. @@ -188,12 +191,12 @@ trait MyOption[T]{ {% endhighlight %} -Consider a situation when we re-compile `MyOption` using a different major compiler version than the one used to compile `Example`. +Consider a situation when we re-compile `MyOption` using a different major compiler version than the one used to compile `Example`. Let's assume the new major version of compile has changing binary descriptor of method `get`. While the code in runtime would still successfully invoke the method `Example.foo`, this method will fail in execution, as it will itself call a `MyOption.get` using an outdated descriptor. - + While in perfect world it would be nice to require all `@stableABI` classes and traits to only take `@stableABI` arguments and only return `@stableABI` values, we believe that all-or-nothing system will be a lot harder to adopt and migrate to. @@ -202,81 +205,81 @@ Because of this we propose to emmit warnings in those cases: - non-`@stableABI` value is returned from a method or field defined inside a `@stableABI` class or trait; - an invocation to a method not-defined inside a `@stableABI` class is used in implementation of a method or a field initializer inside a `@stableABI` class or trait. - + Those warnings can be suppressed using an `@unchecked` annotations or made fatal using `+Xfatal-warnings`. - + ## The case of the standard library ## -The Standard library defines types commonly used as arguments or return types such as `Option` and `List`, +The Standard library defines types commonly used as arguments or return types such as `Option` and `List`, as well as methods and implicit conversions imported from `scala` and `Predef`. As such Standard library is expected to be the biggest source of warnings defined in previous section. -We propose to consider either making some classes in standard library use `@stableABI` or define new `@stableABI` -super-interfaces for them that should be used in `@stableABI` classes. +We propose to consider either making some classes in standard library use `@stableABI` or define new `@stableABI` +super-interfaces for them that should be used in `@stableABI` classes. This would also allow to consume Scala classes from other JVM languages such as Kotlin and Java. ## `@stableABI` and Scala.js -Allowing to write API-defining classes in Scala instead of Java will allow them to compile with Scala.js, +Allowing to write API-defining classes in Scala instead of Java will allow them to compile with Scala.js, which would have benefit of sharing the same source for two ecosystems. Scala.js currently is binary compatible as long as original bytecode compiled by Scala JVM is binary compatible. Providing stronger binary compatibility guarantees for JVM will automatically provide stronger guarantees for Scala.js. - + ## Comparison with MiMa ## The Migration Manager for Scala (MiMa in short) is a tool for diagnosing binary incompatibilities for Scala libraries. MiMa allows to compare binary APIs of two already compiled classfiles and reports errors if APIs do not match perfectly. -MiMa and `@stableABI` complement each other, as `@stableABI` helps to develop APIs that stay compatible +MiMa and `@stableABI` complement each other, as `@stableABI` helps to develop APIs that stay compatible across major versions, while MiMa checks that previously published artifacts indeed have the same API. -`@stableABI` does not compare the currently compiled class or trait against previous version, +`@stableABI` does not compare the currently compiled class or trait against previous version, so introduction of new members won't be prohibited. This is a use-case for MiMa. - + MiMa does not indicate how hard, if possible, would it be to maintain compatibility of a class across future versions of Scala. Multiple features of Scala, most notably lazy vals and traits, have been compiled diffently by different Scala versions -making porting existing compiled bytecode across versions very hard. -MiMa will complain retroactively that the new version is incompatible with the old one. +making porting existing compiled bytecode across versions very hard. +MiMa will complain retroactively that the new version is incompatible with the old one. `@stableABI` will instead indicate at compile time that the old version used features whose encoding is prone to change. This provides early guidance and warning when designing long-living APIs before they are publicly released. ## Compilation scheme ## No modification of typer or any existing phase is planned. The current proposed scheme introduces a late phase that runs before the very bytecode emission that checks that: - - classes, traits and objects annotated as `@stableABI` are on the top level; + - classes, traits and objects annotated as `@stableABI` are on the top level; - compiler did not introduce new Public API methods or fields inside `@stableABI` classes, traits and objects; - compiler did not change descriptors of existing Public API methods or fields inside `@stableABI` classes, traits and objects. - + This phase additionally warns if Public API method or field takes an argument or returns a value that isn't marked as `@stableABI`. This warning can be suppressed by annotating with `@unchecked`. -The current prototype is implemented for Dotty and supports everything described in this SIP, except warnings. -The implementation is simple with less than 50 lines of non-boilerplate code. -The current implementation has a scope for improvement of error messages that will report domain specific details for disallowed features, +The current prototype is implemented for Dotty and supports everything described in this SIP, except warnings. +The implementation is simple with less than 50 lines of non-boilerplate code. +The current implementation has a scope for improvement of error messages that will report domain specific details for disallowed features, but it already prohibits them. ## Addendum: Default methods ## By `default methods` we mean non-abstract methods defined and implemented by a trait. The way how those methods are implemented by compiler has changed substantially over years. -At the same time, `invokeinterface` has always been a reliable way to invoke such a method, +At the same time, `invokeinterface` has always been a reliable way to invoke such a method, independently from how it was implemented under the hood. -One might reason that, as there has been a reliable way to call methods on the binary level, +One might reason that, as there has been a reliable way to call methods on the binary level, it should be allowed to use them in binary compatible APIs. - -At the same time, the mixin composition protocol that is followed when a class inherits those traits has also -changed substantially. + +At the same time, the mixin composition protocol that is followed when a class inherits those traits has also +changed substantially. The classes which have been correctly inheriting those traits compiled by previous versions of Scala -may need recompilation if trait has been recompiled with a new major version of Scala. - -Thus, the authors of this SIP has decided not to allow default methods in the +may need recompilation if trait has been recompiled with a new major version of Scala. + +Thus, the authors of this SIP has decided not to allow default methods in the `@stableABI` traits. ## See Also ## 1. [dotty#1900][1] - 2. [MiMa][2] + 2. [MiMa][2] 3. [releases-compatibility][3] 4. [Descriptor definition in JVM Specification][4] 5. [JVM access flags][5] diff --git a/sips/pending/_posts/2017-02-07-make-types-behave-like-expressions.md b/_sips/sips/2017-02-07-make-types-behave-like-expressions.md similarity index 93% rename from sips/pending/_posts/2017-02-07-make-types-behave-like-expressions.md rename to _sips/sips/2017-02-07-make-types-behave-like-expressions.md index eee8ecd738..0b92d60179 100644 --- a/sips/pending/_posts/2017-02-07-make-types-behave-like-expressions.md +++ b/_sips/sips/2017-02-07-make-types-behave-like-expressions.md @@ -1,7 +1,10 @@ --- layout: sip discourse: true -title: SIP-NN - Match infix & prefix types to meet expression rules +title: SIP-NN - Match infix & prefix types to meet expression rules + +vote-status: pending +permalink: /sips/:title.html --- **By: Oron Port** @@ -22,10 +25,10 @@ Your feedback is welcome! If you're interested in discussing this proposal, head Currently scala allows symbol operators (`-`, `*`, `~~>`, etc.) for both type names and definition names. Unfortunately, there is a 'surprise' element since the two differ in behaviour: -### Infix operator precedence and associativity -Infix types are 'mostly' left-associative, -while the expression operation precedence is determined by the operator's first character (e.g., `/` is precedent to `+`). -Please see [Infix Types](http://scala-lang.org/files/archive/spec/2.12/03-types.html#infix-types) and [Infix Operations](http://scala-lang.org/files/archive/spec/2.12/06-expressions.html#infix-operations) sections of the Scala specifications for more details. +### Infix operator precedence and associativity +Infix types are 'mostly' left-associative, +while the expression operation precedence is determined by the operator's first character (e.g., `/` is precedent to `+`). +Please see [Infix Types](http://scala-lang.org/files/archive/spec/2.12/03-types.html#infix-types) and [Infix Operations](http://scala-lang.org/files/archive/spec/2.12/06-expressions.html#infix-operations) sections of the Scala specifications for more details. **Example**: @@ -59,8 +62,8 @@ Please see [Infix Types](http://scala-lang.org/files/archive/spec/2.12/03-types. {% endhighlight %} ### Prefix operators bracketless unary use -While expressions have prefix unary operators, there are none for types. See the [Prefix Operations](http://scala-lang.org/files/archive/spec/2.12/06-expressions.html#prefix-operations) section of the Scala specification. -This is a lacking feature of the type language Scala offers. See also interactions of this feature with other Scala features, further down this text. +While expressions have prefix unary operators, there are none for types. See the [Prefix Operations](http://scala-lang.org/files/archive/spec/2.12/06-expressions.html#prefix-operations) section of the Scala specification. +This is a lacking feature of the type language Scala offers. See also interactions of this feature with other Scala features, further down this text. **Example**: @@ -101,7 +104,7 @@ The proposal is split into two; type infix precedence, and prefix unary types. N Make infix types conform to the same precedence and associativity traits as expression operations. ### Proposal, Part 2: Prefix unary types -Add prefix types, exactly as specified for prefix expression. +Add prefix types, exactly as specified for prefix expression. --- @@ -112,22 +115,22 @@ The general motivation is developers expect terms and types to behave equally re ### Motivating examples #### Dotty infix type similarity -Dotty infix type associativity and precedence seem to act the same as expressions. +Dotty infix type associativity and precedence seem to act the same as expressions. No documentation available to prove this, but the infix example above works perfectly in dotty. Dotty has no prefix types, same as Scalac. #### Singleton-ops library example -The [singleton-ops library](https://github.com/fthomas/singleton-ops) with [Typelevel Scala](https://github.com/typelevel/scala) (which implemented [SIP-23](http://docs.scala-lang.org/sips/pending/42.type.html)) enables developers to express literal type operations more intuitively. -For example: +The [singleton-ops library](https://github.com/fthomas/singleton-ops) with [Typelevel Scala](https://github.com/typelevel/scala) (which implemented [SIP-23](http://docs.scala-lang.org/sips/pending/42.type.html)) enables developers to express literal type operations more intuitively. +For example: {% highlight scala %} import singleton.ops._ - + val four1 : 4 = implicitly[2 + 2] val four2 : 2 + 2 = 4 val four3 : 1 + 3 = implicitly[2 + 2] - + class MyVec[L] { def doubleSize = new MyVec[2 * L] def nSize[N] = new MyVec[N * L] @@ -160,8 +163,8 @@ E.g. {% highlight scala %} trait Negate[A] trait Positive[A] - type unary_-[A] = Negate[A] - type unary_+[A] = Positive[A] + type unary_-[A] = Negate[A] + type unary_+[A] = Positive[A] trait Contravariant[B, -A <: -B] //contravariant A subtype upper-bounded by Negate[B] trait Covariant[B, +A <: +B] //covariant A subtype upper-bounded by Positive[B] {% endhighlight %} @@ -171,10 +174,10 @@ Negative literal types are annotated using the `-` symbol. This can lead to the {% highlight scala %} trait Negate[A] - type unary_-[A] = Negate[A] + type unary_-[A] = Negate[A] trait MyTrait[B] - - type MinusFortyTwo = MyTrait[-42] + + type MinusFortyTwo = MyTrait[-42] type NegateFortyTwo = MyTrait[Negate[42]] {% endhighlight %} @@ -185,7 +188,7 @@ Note: It is not possible to annotate a positive literal type in Scala (checked b {% highlight scala %} val a : 42 = +42 //works val b : -42 = -42 //works - val c : +42 = 42 //error: ';' expected but integer literal found + val c : +42 = 42 //error: ';' expected but integer literal found {% endhighlight %} This means that if unary prefix types are added, then `+42` will be a type expansion of `unary_+[42]`. diff --git a/sips/pending/_posts/2017-02-22-comonadic-comprehensions.md b/_sips/sips/2017-02-22-comonadic-comprehensions.md similarity index 91% rename from sips/pending/_posts/2017-02-22-comonadic-comprehensions.md rename to _sips/sips/2017-02-22-comonadic-comprehensions.md index d86e89387b..45e3826c67 100644 --- a/sips/pending/_posts/2017-02-22-comonadic-comprehensions.md +++ b/_sips/sips/2017-02-22-comonadic-comprehensions.md @@ -2,6 +2,9 @@ layout: sip discourse: true title: SIP-NN - comonadic-comprehensions + +vote-status: pending +permalink: /sips/:title.html --- **By: Shimi Bandiel** @@ -14,7 +17,7 @@ title: SIP-NN - comonadic-comprehensions ## Motivation -Scala provides a concise syntax for working with Monads(map & flatMap): +Scala provides a concise syntax for working with Monads(map & flatMap): the for comprehension. Following is a proposal for a concise syntax for working with Comonads(map, extract & coflatMap): @@ -31,12 +34,12 @@ Consider the following class: {% highlight scala %} case class StreamZipper[A](left: Stream[A], focus: A, right: Stream[A]) { - def map[B](f: A => B): StreamZipper[B] = + def map[B](f: A => B): StreamZipper[B] = StreamZipper(left.map(f), f(focus), right.map(f)) - def extract: A = - focus - def coflatMap(f: StreamZipper[A] => B): StreamZipper[B] = - ??? + def extract: A = + focus + def coflatMap(f: StreamZipper[A] => B): StreamZipper[B] = + ??? } {% endhighlight %} @@ -44,7 +47,7 @@ case class StreamZipper[A](left: Stream[A], focus: A, right: Stream[A]) { StreamZipper[A] represents a non-empty Stream of As with a cursor (focus).
        -
      • The map method invokes f on every element and produces a StreamZipper of +
      • The map method invokes f on every element and produces a StreamZipper of the results.
      • The extract method returns the value at the cursor
      • The coflatMap method invokes f on every cursor (all possible zippers) providing a contextual global operation. @@ -56,25 +59,25 @@ The above implementation for `coflatMap` was left out for brevity. See [3]. Now, consider the following methods: {% highlight scala %} - + // returns whether the current cursor in a zipper of ints is between the previous // and the next numbers. - def isInTheMiddle(z : StreamZipper[Int]): Boolean = + def isInTheMiddle(z : StreamZipper[Int]): Boolean = z match { case StreamZipper(pi +: _, i, ni +: _) if (pi < i && i < ni) => true case _ => false } // counts how many consecutive values of true starting from the cursor - def numberOfTrues(z: StreamZipper[Boolean]) : Int = - if (z.focus) 1 + z.right.takeWhile(true ==).size else 0 + def numberOfTrues(z: StreamZipper[Boolean]) : Int = + if (z.focus) 1 + z.right.takeWhile(true ==).size else 0 {% endhighlight %} And, let's say we have a StreamZipper[Person]: {% highlight scala %} case class Person(name: String, age: Int) - + // a given stream with cursor at some position val people: StreamZipper[Person] = ??? {% endhighlight %} @@ -111,12 +114,12 @@ the context between the invocations of coflatMap. The proposed syntax allows for the following usage: {% highlight scala %} - val flow : StreamZipper[Person] => (Person, Boolean, Int) = + val flow : StreamZipper[Person] => (Person, Boolean, Int) = cofor (p @ Person(_, age)) { tag <- isInTheMiddle(age) count <- numberOfTrues(tag) } yield (p.extract, tag.extract, count.extract) - + val goal = people.coflatMap(flow) {% endhighlight %} @@ -132,14 +135,14 @@ The syntax for `cofor` is defined as: pattern2 <- generator2 ... } yield body - + patternN = regular case patterns - generatorN = expr + generatorN = expr body = expr {% endhighlight %} -The result type of a `cofor` expression is a function from the comonad type to +The result type of a `cofor` expression is a function from the comonad type to a result (`T[A] => B`). This means that the return type must be available at call-site! Note that unlike `for`, guards and assignments are not supported. @@ -151,12 +154,12 @@ A `cofor` desugaring is much more complex than the respective `for`. Desugaring example: {% highlight scala %} - val flow : StreamZipper[Person] => (Person, Boolean, Int) = + val flow : StreamZipper[Person] => (Person, Boolean, Int) = cofor (p @ Person(_, age)) { tag <- isInTheMiddle(age) count <- numberOfTrues(tag) } yield (p.extract, tag.extract, count.extract) - + val goal = people.coflatMap(flow) {% endhighlight %} @@ -164,7 +167,7 @@ The above `cofor` expression will be desugared into the following function: {% highlight scala %} input => { // desugaring the generators - val enums = + val enums = // assign values to input variables // actual assignment is done through pattern matching input.map(p => ( diff --git a/sips/pending/_posts/2017-07-12-right-associative-by-name-operators.md b/_sips/sips/2017-07-12-right-associative-by-name-operators.md similarity index 98% rename from sips/pending/_posts/2017-07-12-right-associative-by-name-operators.md rename to _sips/sips/2017-07-12-right-associative-by-name-operators.md index a631bf02fb..179ae2f783 100644 --- a/sips/pending/_posts/2017-07-12-right-associative-by-name-operators.md +++ b/_sips/sips/2017-07-12-right-associative-by-name-operators.md @@ -2,6 +2,9 @@ layout: sip discourse: true title: SIP-NN - Right-Associative By-Name Operators + +vote-status: pending +permalink: /sips/:title.html --- **By: Stefan Zeiger** diff --git a/style/control-structures.md b/_style/control-structures.md similarity index 98% rename from style/control-structures.md rename to _style/control-structures.md index 3107418452..df74036aee 100644 --- a/style/control-structures.md +++ b/_style/control-structures.md @@ -1,8 +1,10 @@ --- -layout: overview-large +layout: style-guide title: Control Structures -partof: style-guide +partof: style +overview-name: "Style Guide" + num: 7 previous-page: files diff --git a/style/declarations.md b/_style/declarations.md similarity index 99% rename from style/declarations.md rename to _style/declarations.md index 0960308418..6679fecada 100644 --- a/style/declarations.md +++ b/_style/declarations.md @@ -1,8 +1,10 @@ --- -layout: overview-large +layout: style-guide title: Declarations -partof: style-guide +partof: style +overview-name: "Style Guide" + num: 9 previous-page: method-invocation diff --git a/style/files.md b/_style/files.md similarity index 97% rename from style/files.md rename to _style/files.md index 5a14b53006..668ac3c82b 100644 --- a/style/files.md +++ b/_style/files.md @@ -1,8 +1,10 @@ --- -layout: overview-large +layout: style-guide title: Files -partof: style-guide +partof: style +overview-name: "Style Guide" + num: 6 previous-page: nested-blocks diff --git a/style/indentation.md b/_style/indentation.md similarity index 97% rename from style/indentation.md rename to _style/indentation.md index 701b447d5e..c378957e17 100644 --- a/style/indentation.md +++ b/_style/indentation.md @@ -1,8 +1,10 @@ --- -layout: overview-large +layout: style-guide title: Indentation -partof: style-guide +partof: style +overview-name: "Style Guide" + num: 2 previous-page: overview diff --git a/_style/index.md b/_style/index.md new file mode 100644 index 0000000000..f722b624c3 --- /dev/null +++ b/_style/index.md @@ -0,0 +1,139 @@ +--- +layout: style-guide +title: Scala Style Guide +partof: style +overview-name: " " +--- + +This document is intended to outline some basic Scala stylistic guidelines which should be followed with more or less fervency. Wherever possible, this guide attempts to detail why a particular style is encouraged and how it relates to other alternatives. As with all style guides, treat this document as a list of rules to be broken. There are certainly times when alternative styles should be preferred over the ones given here. + + +
        + +

        Thanks to

        +

        Daniel Spiewak and David Copeland for putting this style guide together, and Simon Ochsenreither for converting it to Markdown.

        + +
        diff --git a/style/method-invocation.md b/_style/method-invocation.md similarity index 98% rename from style/method-invocation.md rename to _style/method-invocation.md index 9558a78241..bb9fdb3fb9 100644 --- a/style/method-invocation.md +++ b/_style/method-invocation.md @@ -1,8 +1,10 @@ --- -layout: overview-large +layout: style-guide title: Method Invocation -partof: style-guide +partof: style +overview-name: "Style Guide" + num: 8 previous-page: control-structures diff --git a/style/naming-conventions.md b/_style/naming-conventions.md similarity index 99% rename from style/naming-conventions.md rename to _style/naming-conventions.md index eccdc40c91..6293ee55b0 100644 --- a/style/naming-conventions.md +++ b/_style/naming-conventions.md @@ -1,8 +1,10 @@ --- -layout: overview-large +layout: style-guide title: Naming Conventions -partof: style-guide +partof: style +overview-name: "Style Guide" + num: 3 previous-page: indentation diff --git a/style/nested-blocks.md b/_style/nested-blocks.md similarity index 94% rename from style/nested-blocks.md rename to _style/nested-blocks.md index 45e38c6ac2..3c489da966 100644 --- a/style/nested-blocks.md +++ b/_style/nested-blocks.md @@ -1,8 +1,10 @@ --- -layout: overview-large +layout: style-guide title: Nested Blocks -partof: style-guide +partof: style +overview-name: "Style Guide" + num: 5 previous-page: types diff --git a/_style/overview.md b/_style/overview.md new file mode 100644 index 0000000000..c86bf6a4ce --- /dev/null +++ b/_style/overview.md @@ -0,0 +1,13 @@ +--- +layout: style-guide +title: Overview + +partof: style +overview-name: "Style Guide" + +num: 1 + +next-page: indentation +--- + +Please see the [table of contents of the style guide]({{ site.baseurl }}/style) for an outline-style overview. diff --git a/style/scaladoc.md b/_style/scaladoc.md similarity index 99% rename from style/scaladoc.md rename to _style/scaladoc.md index 2687e8fb6b..c5558a412c 100644 --- a/style/scaladoc.md +++ b/_style/scaladoc.md @@ -1,8 +1,10 @@ --- -layout: overview-large +layout: style-guide title: Scaladoc -partof: style-guide +partof: style +overview-name: "Style Guide" + num: 10 previous-page: declarations diff --git a/style/types.md b/_style/types.md similarity index 98% rename from style/types.md rename to _style/types.md index 878c75dc2c..87b9a1eb3d 100644 --- a/style/types.md +++ b/_style/types.md @@ -1,8 +1,10 @@ --- -layout: overview-large +layout: style-guide title: Types -partof: style-guide +partof: style +overview-name: "Style Guide" + num: 4 previous-page: naming-conventions diff --git a/_tour/abstract-types.md b/_tour/abstract-types.md new file mode 100644 index 0000000000..8479a60a03 --- /dev/null +++ b/_tour/abstract-types.md @@ -0,0 +1,73 @@ +--- +layout: tour +title: Abstract Types + +discourse: true + +partof: scala-tour + +num: 23 +next-page: compound-types +previous-page: inner-classes +prerequisite-knowledge: variance, upper-type-bound +--- + +Traits and abstract classes can have an abstract type member. This means that the concrete implementations define the actual type. Here's an example: + +```tut +trait Buffer { + type T + val element: T +} +``` +Here we have defined an abstract `type T`. It is used to describe the type of `element`. We can extend this trait in an abstract class, adding an upper-type-bound to `T` to make it more specific. + +```tut +abstract class SeqBuffer extends Buffer { + type U + type T <: Seq[U] + def length = element.length +} +``` +Notice how we can use yet another abstract `type U` as an upper-type-bound. This `class SeqBuffer` allows us to store only sequences in the buffer by stating that type `T` has to be a subtype of `Seq[U]` for a new abstract type `U`. + +Traits or [classes](classes.html) with abstract type members are often used in combination with anonymous class instantiations. To illustrate this, we now look at a program which deals with a sequence buffer that refers to a list of integers: + +```tut +abstract class IntSeqBuffer extends SeqBuffer { + type U = Int +} + + +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) +``` +Here the factory `newIntSeqBuf` uses an anonymous class implementation of `IntSeqBuf` (i.e. `new IntSeqBuffer`), setting `type T` to a `List[Int]`. + +It is also possible to turn abstract type members into type parameters of classes and vice versa. Here is a version of the code above which only uses type parameters: + +```tut +abstract class Buffer[+T] { + val element: T +} +abstract class SeqBuffer[U, +T <: Seq[U]] extends Buffer[T] { + def length = element.length +} + +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) +``` + +Note that we have to use [variance annotations](variances.html) here (`+T <: Seq[U]`) in order to hide the concrete sequence implementation type of the object returned from method `newIntSeqBuf`. Furthermore, there are cases where it is not possible to replace abstract types with type parameters. diff --git a/_tour/annotations.md b/_tour/annotations.md new file mode 100644 index 0000000000..0c00aa2b63 --- /dev/null +++ b/_tour/annotations.md @@ -0,0 +1,126 @@ +--- +layout: tour +title: Annotations + +discourse: true + +partof: scala-tour + +num: 32 +next-page: default-parameter-values +previous-page: by-name-parameters +--- + +Annotations associate meta-information with definitions. For example, the annotation `@deprecated` before a method causes the compiler to print a warning if the method is used. +``` +object DeprecationDemo extends App { + @deprecated + def hello = "hola" + + hello +} +``` +This will compile but the compiler will print a warning: "there was one deprecation warning". + +An annotation clause applies to the first definition or declaration following it. More than one annotation clause may precede a definition and declaration. The order in which these clauses are given does not matter. + + +## Annotations that ensure correctness of encodings +Certain annotations will actually cause compilation to fail if a condition(s) is not met. For example, the annotation `@tailrec` ensures that a method is [tail-recursive](https://en.wikipedia.org/wiki/Tail_call). Tail-recursion can keep memory requirements constant. Here's how it's used in a method which calculates the factorial: +```tut +import scala.annotation.tailrec + +def factorial(x: Int): Int = { + + @tailrec + def factorialHelper(x: Int, accumulator: Int): Int = { + if (x == 1) accumulator else factorialHelper(x - 1, accumulator * x) + } + factorialHelper(x, 1) +} +``` +The `factorialHelper` method has the `@tailrec` which ensures the method is indeed tail-recursive. If we were to change the implementation of `factorialHelper` to the following, it would fail: +``` +import scala.annotation.tailrec + +def factorial(x: Int): Int = { + @tailrec + def factorialHelper(x: Int): Int = { + if (x == 1) 1 else x * factorialHelper(x - 1) + } + factorialHelper(x) +} +``` +We would get the message "Recursive call not in tail position". + + +## Annotations affecting code generation +Some annotations like `@inline` affect the generated code (i.e. your jar file might have different bytes than if you hadn't used the annotation). Inlining means inserting the code in a method's body at the call site. The resulting bytecode is longer, but hopefully runs faster. Using the annotation `@inline` does not ensure that a method will be inlined, but it will cause the compiler to do it if and only if some heuristics about the size of the generated code are met. + +### Java Annotations ### +When writing Scala code which interoperates with Java, there are a few differences in annotation syntax to note. +**Note:** Make sure you use the `-target:jvm-1.8` option with Java annotations. + +Java has user-defined metadata in the form of [annotations](https://docs.oracle.com/javase/tutorial/java/annotations/). A key feature of annotations is that they rely on specifying name-value pairs to initialize their elements. For instance, if we need an annotation to track the source of some class we might define it as + +``` +@interface Source { + public String URL(); + public String mail(); +} +``` + +And then apply it as follows + +``` +@Source(URL = "http://coders.com/", + mail = "support@coders.com") +public class MyClass extends HisClass ... +``` + +An annotation application in Scala looks like a constructor invocation, for instantiating a Java annotation one has to use named arguments: + +``` +@Source(URL = "http://coders.com/", + mail = "support@coders.com") +class MyScalaClass ... +``` + +This syntax is quite tedious if the annotation contains only one element (without default value) so, by convention, if the name is specified as `value` it can be applied in Java using a constructor-like syntax: + +``` +@interface SourceURL { + public String value(); + public String mail() default ""; +} +``` + +And then apply it as follows + +``` +@SourceURL("http://coders.com/") +public class MyClass extends HisClass ... +``` + +In this case, Scala provides the same possibility + +``` +@SourceURL("http://coders.com/") +class MyScalaClass ... +``` + +The `mail` element was specified with a default value so we need not explicitly provide a value for it. However, if we need to do it we can not mix-and-match the two styles in Java: + +``` +@SourceURL(value = "http://coders.com/", + mail = "support@coders.com") +public class MyClass extends HisClass ... +``` + +Scala provides more flexibility in this respect + +``` +@SourceURL("http://coders.com/", + mail = "support@coders.com") + class MyScalaClass ... +``` diff --git a/_tour/basics.md b/_tour/basics.md new file mode 100644 index 0000000000..157b3e2aac --- /dev/null +++ b/_tour/basics.md @@ -0,0 +1,311 @@ +--- +layout: tour +title: Basics + +discourse: true + +partof: scala-tour + +num: 2 +next-page: unified-types +previous-page: tour-of-scala +--- + +In this page, we will cover basics of Scala. + +## Trying Scala in the Browser + +You can run Scala in your browser with ScalaFiddle. + +1. Go to [https://scalafiddle.io](https://scalafiddle.io). +2. Paste `println("Hello, world!")` in the left pane. +3. Hit "Run" button. Output appears in the right pane. + +This is an easy, zero-setup way to experiment with pieces of Scala code. + +## Expressions + +Expressions are computable statements. +``` +1 + 1 +``` +You can output results of expressions using `println`. + +```tut +println(1) // 1 +println(1 + 1) // 2 +println("Hello!") // Hello! +println("Hello," + " world!") // Hello, world! +``` + +### Values + +You can name results of expressions with the `val` keyword. + +```tut +val x = 1 + 1 +println(x) // 2 +``` + +Named results, such as `x` here, are called values. Referencing +a value does not re-compute it. + +Values cannot be re-assigned. + +```tut:nofail +val x = 1 + 1 +x = 3 // This does not compile. +``` + +Types of values can be inferred, but you can also explicitly state the type, like this: + +```tut +val x: Int = 1 + 1 +``` + +Notice how the type declaration `Int` comes after the identifier `x`. You also need a `:`. + +### Variables + +Variables are like values, except you can re-assign them. You can define a variable with the `var` keyword. + +```tut +var x = 1 + 1 +x = 3 // This compiles because "x" is declared with the "var" keyword. +println(x * x) // 9 +``` + +As with values, you can explicitly state the type if you want: + +```tut +var x: Int = 1 + 1 +``` + + +## Blocks + +You can combine expressions by surrounding them with `{}`. We call this a block. + +The result of the last expression in the block is the result of the overall block, too. + +```tut +println({ + val x = 1 + 1 + x + 1 +}) // 3 +``` + +## Functions + +Functions are expressions that take parameters. + +You can define an anonymous function (i.e. no name) that returns a given integer plus one: + +```tut +(x: Int) => x + 1 +``` + +On the left of `=>` is a list of parameters. On the right is an expression involving the parameters. + +You can also name functions. + +```tut +val addOne = (x: Int) => x + 1 +println(addOne(1)) // 2 +``` + +Functions may take multiple parameters. + +```tut +val add = (x: Int, y: Int) => x + y +println(add(1, 2)) // 3 +``` + +Or it can take no parameters. + +```tut +val getTheAnswer = () => 42 +println(getTheAnswer()) // 42 +``` + +We will cover functions in depth [later](anonymous-function-syntax.html). + +## Methods + +Methods look and behave very similar to functions, but there are a few key differences between them. + +Methods are defined with the `def` keyword. `def` is followed by a name, parameter lists, a return type, and a body. + +```tut +def add(x: Int, y: Int): Int = x + y +println(add(1, 2)) // 3 +``` + +Notice how the return type is declared _after_ the parameter list and a colon `: Int`. + +Methods can take multiple parameter lists. + +```tut +def addThenMultiply(x: Int, y: Int)(multiplier: Int): Int = (x + y) * multiplier +println(addThenMultiply(1, 2)(3)) // 9 +``` + +Or no parameter lists at all. + +```tut +def name: String = System.getProperty("name") +println("Hello, " + name + "!") +``` + +There are some other differences, but for now, you can think of them as something similar to functions. + +Methods can have multi-line expressions as well. +```tut +def getSquareString(input: Double): String = { + val square = input * input + square.toString +} +``` +The last expression in the body is the method's return value. (Scala does have a `return` keyword, but it's rarely used.) + +## Classes + +You can define classes with the `class` keyword followed by its name and constructor parameters. + +```tut +class Greeter(prefix: String, suffix: String) { + def greet(name: String): Unit = + println(prefix + name + suffix) +} +``` +The return type of the method `greet` is `Unit`, which says there's nothing meaningful to return. It's used similarly to `void` in Java and C. (A difference is that because every Scala expression must have some value, there is actually a singleton value of type Unit, written (). It carries no information.) + +You can make an instance of a class with the `new` keyword. + +```tut +val greeter = new Greeter("Hello, ", "!") +greeter.greet("Scala developer") // Hello, Scala developer! +``` + +We will cover classes in depth [later](classes.html). + +## Case Classes + +Scala has a special type of class called a "case" class. By default, case classes are immutable and compared by value. You can define case classes with the `case class` keywords. + +```tut +case class Point(x: Int, y: Int) +``` + +You can instantiate case classes without `new` keyword. + +```tut +val point = Point(1, 2) +val anotherPoint = Point(1, 2) +val yetAnotherPoint = Point(2, 2) +``` + +And they are compared by value. + +```tut +if (point == anotherPoint) { + println(point + " and " + anotherPoint + " are the same.") +} else { + println(point + " and " + anotherPoint + " are different.") +} +// Point(1,2) and Point(1,2) are the same. + +if (point == yetAnotherPoint) { + println(point + " and " + yetAnotherPoint + " are the same.") +} else { + println(point + " and " + yetAnotherPoint + " are different.") +} +// Point(1,2) and Point(2,2) are different. +``` + +There is a lot more to case classes that we'd like to introduce, and we are convinced you will fall in love with them! We will cover them in depth [later](case-classes.html). + +## Objects + +Objects are single instances of their own definitions. You can think of them as singletons of their own classes. + +You can define objects with the `object` keyword. + +```tut +object IdFactory { + private var counter = 0 + def create(): Int = { + counter += 1 + counter + } +} +``` + +You can access an object by referring to its name. + +```tut +val newId: Int = IdFactory.create() +println(newId) // 1 +val newerId: Int = IdFactory.create() +println(newerId) // 2 +``` + +We will cover objects in depth [later](singleton-objects.html). + +## Traits + +Traits are types containing certain fields and methods. Multiple traits can be combined. + +You can define traits with `trait` keyword. + +```tut +trait Greeter { + def greet(name: String): Unit +} +``` + +Traits can also have default implementations. + +```tut +trait Greeter { + def greet(name: String): Unit = + println("Hello, " + name + "!") +} +``` + +You can extend traits with the `extends` keyword and override an implementation with the `override` keyword. + +```tut +class DefaultGreeter extends Greeter + +class CustomizableGreeter(prefix: String, postfix: String) extends Greeter { + override def greet(name: String): Unit = { + println(prefix + name + postfix) + } +} + +val greeter = new DefaultGreeter() +greeter.greet("Scala developer") // Hello, Scala developer! + +val customGreeter = new CustomizableGreeter("How are you, ", "?") +customGreeter.greet("Scala developer") // How are you, Scala developer? +``` + +Here, `DefaultGreeter` extends only a single trait, but it could extend multiple traits. + +We will cover traits in depth [later](traits.html). + +## Main Method + +The main method is an entry point of a program. The Java Virtual +Machine requires a main method to be named `main` and take one +argument, an array of strings. + +Using an object, you can define a main method as follows: + +```tut +object Main { + def main(args: Array[String]): Unit = + println("Hello, Scala developer!") +} +``` diff --git a/_tour/by-name-parameters.md b/_tour/by-name-parameters.md new file mode 100644 index 0000000000..18b7d2ec1a --- /dev/null +++ b/_tour/by-name-parameters.md @@ -0,0 +1,40 @@ +--- +layout: tour +title: By-name Parameters + +discourse: true + +partof: scala-tour + +num: 31 +next-page: annotations +previous-page: operators +--- + +_By-name parameters_ are only evaluated when used. They are in contrast to _by-value parameters_. To make a parameter called by-name, simply prepend `=>` to its type. +```tut +def calculate(input: => Int) = input * 37 +``` +By-name parameters have the the advantage that they are not evaluated if they aren't used in the function body. On the other hand, by-value parameters have the advantage that they are evaluated only once. + +Here's an example of how we could implement a while loop: + +```tut +def whileLoop(condition: => Boolean)(body: => Unit): Unit = + if (condition) { + body + whileLoop(condition)(body) + } + +var i = 2 + +whileLoop (i > 0) { + println(i) + i -= 1 +} // prints 2 1 +``` +The method `whileLoop` uses multiple parameter lists to take a condition and a body of the loop. If the `condition` is true, the `body` is executed and then a recursive call to whileLoop is made. If the `condition` is false, the body is never evaluated because we prepended `=>` to the type of `body`. + +Now when we pass `i > 0` as our `condition` and `println(i); i-= 1` as the `body`, it behaves like the standard while loop in many languages. + +This ability to delay evaluation of a parameter until it is used can help performance if the parameter is computationally intensive to evaluate or a longer-running block of code such as fetching a URL. diff --git a/_tour/case-classes.md b/_tour/case-classes.md new file mode 100644 index 0000000000..230f25b019 --- /dev/null +++ b/_tour/case-classes.md @@ -0,0 +1,57 @@ +--- +layout: tour +title: Case Classes + +discourse: true + +partof: scala-tour + +num: 11 +next-page: pattern-matching +previous-page: currying +prerequisite-knowledge: classes, basics, mutability +--- + +Case classes are like regular classes with a few key differences which we will go over. Case classes are good for modeling immutable data. In the next step of the tour, we'll see how they are useful in [pattern matching](pattern-matching.html). + +## Defining a case class +A minimal case class requires the keywords `case class`, an identifier, and a parameter list (which may be empty): +```tut +case class Book(isbn: String) + +val frankenstein = Book("978-0486282114") +``` +Notice how the keyword `new` was not used to instantiate the `Message` case class. This is because case classes have an `apply` method by default which takes care of object construction. + +When you create a case class with parameters, the parameters are public `val`s. +``` +case class Message(sender: String, recipient: String, body: String) +val message1 = Message("guillaume@quebec.ca", "jorge@catalonia.es", "Ça va ?") + +println(message1.sender) // prints guillaume@quebec.ca +message1.sender = "travis@washington.us" // this line does not compile +``` +You can't reassign `message1.sender` because it is a `val` (i.e. immutable). It is possible to use `var`s in case classes but this is discouraged. + +## Comparison +Case classes are compared by structure and not by reference: +``` +case class Message(sender: String, recipient: String, body: String) + +val message2 = Message("jorge@catalonia.es", "guillaume@quebec.ca", "Com va?") +val message3 = Message("jorge@catalonia.es", "guillaume@quebec.ca", "Com va?") +val messagesAreTheSame = message2 == message3 // true +``` +Even though `message2` and `message3` refer to different objects, the value of each object is equal. + +## Copying +You can create a (shallow) copy of an instance of a case class simply by using the `copy` method. You can optionally change the constructor arguments. +``` +case class Message(sender: String, recipient: String, body: String) +val message4 = Message("julien@bretagne.fr", "travis@washington.us", "Me zo o komz gant ma amezeg") +val message5 = message4.copy(sender = message4.recipient, recipient = "claire@bourgogne.fr") +message5.sender // travis@washington.us +message5.recipient // claire@bourgogne.fr +message5.body // "Me zo o komz gant ma amezeg" +``` +The recipient of `message4` is used as the sender of `message5` but the `body` of `message4` was copied directly. diff --git a/_tour/classes.md b/_tour/classes.md new file mode 100644 index 0000000000..6f4a842f85 --- /dev/null +++ b/_tour/classes.md @@ -0,0 +1,111 @@ +--- +layout: tour +title: Classes + +discourse: true + +partof: scala-tour + +num: 4 +next-page: traits +previous-page: unified-types +topics: classes +prerequisite-knowledge: no-return-keyword, type-declaration-syntax, string-interpolation, procedures +--- + +Classes in Scala are blueprints for creating objects. They can contain methods, +values, variables, types, objects, traits, and classes which are collectively called _members_. Types, objects, and traits will be covered later in the tour. + +## Defining a class +A minimal class definition is simply the keyword `class` and +an identifier. Class names should be capitalized. +```tut +class User + +val user1 = new User +``` +The keyword `new` is used to create an instance of the class. `User` has a default constructor which takes no arguments because no constructor was defined. However, you'll often want a constructor and class body. Here is an example class definition for a 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 = + s"($x, $y)" +} + +val point1 = new Point(2, 3) +point1.x // 2 +println(point1) // prints (x, y) +``` + +This `Point` class has four members: the variables `x` and `y` and the methods `move` and +`toString`. Unlike many other languages, the primary constructor is in the class signature `(var x: Int, var y: Int)`. The `move` method takes two integer arguments and returns the Unit value `()`, which carries no information. This corresponds roughly with `void` in Java-like languages. `toString`, on the other hand, does not take any arguments but returns a `String` value. Since `toString` overrides `toString` from [`AnyRef`](unified-types.html), it is tagged with the `override` keyword. + +## Constructors + +Constructors can have optional parameters by providing a default value like so: + +```tut +class Point(var x: Int = 0, var y: Int = 0) + +val origin = new Point // x and y are both set to 0 +val point1 = new Point(1) +println(point1.x) // prints 1 + +``` + +In this version of the `Point` class, `x` and `y` have the default value `0` so no arguments are required. However, because the constructor reads arguments left to right, if you just wanted to pass in a `y` value, you would need to name the parameter. +``` +class Point(var x: Int = 0, var y: Int = 0) +val point2 = new Point(y=2) +println(point2.y) // prints 2 +``` + +This is also a good practice to enhance clarity. + +## Private Members and Getter/Setter Syntax +Members are public by default. Use the `private` access modifier +to hide them from outside of the class. +```tut +class Point { + private var _x = 0 + private var _y = 0 + private val bound = 100 + + def x = _x + def x_= (newValue: Int): Unit = { + if (newValue < bound) _x = newValue else printWarning + } + + def y = _y + def y_= (newValue: Int): Unit = { + if (newValue < bound) _y = newValue else printWarning + } + + private def printWarning = println("WARNING: Out of bounds") +} + +val point1 = new Point +point1.x = 99 +point1.y = 101 // prints the warning +``` +In this version of the `Point` class, the data is stored in private variables `_x` and `_y`. There are methods `def x` and `def y` for accessing the private data. `def x_=` and `def y_=` are for validating and setting the value of `_x` and `_y`. Notice the special syntax for the setters: the method has `_=` appended to the identifier of the getter and the parameters come after. + +Primary constructor parameters with `val` and `var` are public. However, because `val`s are immutable, you can't write the following. +``` +class Point(val x: Int, val y: Int) +val point = new Point(1, 2) +point.x = 3 // <-- does not compile +``` + +Parameters without `val` or `var` are private values, visible only within the class. +``` +class Point(x: Int, y: Int) +val point = new Point(1, 2) +point.x // <-- does not compile +``` diff --git a/_tour/compound-types.md b/_tour/compound-types.md new file mode 100644 index 0000000000..6e76997e4d --- /dev/null +++ b/_tour/compound-types.md @@ -0,0 +1,52 @@ +--- +layout: tour +title: Compound Types + +discourse: true + +partof: scala-tour + +num: 24 +next-page: self-types +previous-page: abstract-types +--- + +Sometimes it is necessary to express that the type of an object is a subtype of several other types. In Scala this can be expressed with the help of *compound types*, which are intersections of object types. + +Suppose we have two traits `Cloneable` and `Resetable`: + +```tut +trait Cloneable extends java.lang.Cloneable { + override def clone(): Cloneable = { + super.clone().asInstanceOf[Cloneable] + } +} +trait Resetable { + def reset: Unit +} +``` + +Now suppose we want to write a function `cloneAndReset` which takes an object, clones it and resets the original object: + +``` +def cloneAndReset(obj: ?): Cloneable = { + val cloned = obj.clone() + obj.reset + cloned +} +``` + +The question arises what the type of the parameter `obj` is. If it's `Cloneable` then the object can be `clone`d, but not `reset`; if it's `Resetable` we can `reset` it, but there is no `clone` operation. To avoid type casts in such a situation, we can specify the type of `obj` to be both `Cloneable` and `Resetable`. This compound type is written like this in Scala: `Cloneable with Resetable`. + +Here's the updated function: + +``` +def cloneAndReset(obj: Cloneable with Resetable): Cloneable = { + //... +} +``` + +Compound types can consist of several object types and they may have a single refinement which can be used to narrow the signature of existing object members. +The general form is: `A with B with C ... { refinement }` + +An example for the use of refinements is given on the page about [abstract types](abstract-types.html). diff --git a/_tour/currying.md b/_tour/currying.md new file mode 100644 index 0000000000..00aeef1c5a --- /dev/null +++ b/_tour/currying.md @@ -0,0 +1,41 @@ +--- +layout: tour +title: Currying + +discourse: true + +partof: scala-tour + +num: 10 +next-page: case-classes +previous-page: nested-functions +--- + +Methods may define multiple parameter lists. When a method is called with a fewer number of parameter lists, then this will yield a function taking the missing parameter lists as its arguments. + +Here is an example: + +```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))) +} +``` + +_Note: method `modN` is partially applied in the two `filter` calls; i.e. only its first argument is actually applied. The term `modN(2)` yields a function of type `Int => Boolean` and is thus a possible candidate for the second argument of function `filter`._ + +Here's the output of the program above: + +``` +List(2,4,6,8) +List(3,6) +``` diff --git a/_tour/default-parameter-values.md b/_tour/default-parameter-values.md new file mode 100644 index 0000000000..4598d8259a --- /dev/null +++ b/_tour/default-parameter-values.md @@ -0,0 +1,47 @@ +--- +layout: tour +title: Default Parameter Values + +discourse: true + +partof: scala-tour + +num: 33 +next-page: named-arguments +previous-page: annotations +prerequisite-knowledge: named-arguments, function syntax +--- + +Scala provides the ability to give parameters default values that can be used to allow a caller to omit those parameters. + +```tut +def log(message: String, level: String = "INFO") = println(s"$level: $message") + +log("System starting") // prints INFO: System starting +log("User not found", "WARNING") // prints WARNING: User not found +``` + +The parameter `level` has a default value so it is optional. On the last line, the argument `"WARNING"` overrides the default argument `"INFO"`. Where you might do overloaded methods in Java, you can use methods with optional parameters to achieve the same effect. However, if the caller omits an argument, any following arguments must be named. + +```tut +class Point(val x: Double = 0, val y: Double = 0) + +val point1 = new Point(y = 1) +``` +Here we have to say `y = 1`. + +Note that default parameters in Scala are not optional when called from Java code: + +```tut +// Point.scala +class Point(val x: Double = 0, val y: Double = 0) +``` + +```java +// Main.java +public class Main { + public static void main(String[] args) { + Point point = new Point(1); // does not compile + } +} +``` diff --git a/tutorials/tour/dot-hot-reload.sh b/_tour/dot-hot-reload.sh similarity index 100% rename from tutorials/tour/dot-hot-reload.sh rename to _tour/dot-hot-reload.sh diff --git a/_tour/extractor-objects.md b/_tour/extractor-objects.md new file mode 100644 index 0000000000..8c3265f5f7 --- /dev/null +++ b/_tour/extractor-objects.md @@ -0,0 +1,58 @@ +--- +layout: tour +title: Extractor Objects + +discourse: true + +partof: scala-tour + +num: 16 +next-page: for-comprehensions +previous-page: regular-expression-patterns +--- + +An extractor object is an object with an `unapply` method. Whereas the `apply` method is like a constructor which takes arguments and creates an object, the `unapply` takes an object and tries to give back the arguments. This is most often used in pattern matching and partial functions. + +```tut +import scala.util.Random + +object CustomerID { + + def apply(name: String) = s"$name--${Random.nextLong}" + + def unapply(customerID: String): Option[String] = { + val name = customerID.split("--").head + if (name.nonEmpty) Some(name) else None + } +} + +val customer1ID = CustomerID("Sukyoung") // Sukyoung--23098234908 +customer1ID match { + case CustomerID(name) => println(name) // prints Sukyoung + case _ => println("Could not extract a CustomerID") +} +``` + +The `apply` method creates a `CustomerID` string from a `name`. The `unapply` does the inverse to get the `name` back. When we call `CustomerID("Sukyoung")`, this is shorthand syntax for calling `CustomerID.apply("Sukyoung")`. When we call `case CustomerID(name) => customer1ID`, we're calling the unapply method. + +The unapply method can also be used to assign a value. + +```tut +val customer2ID = CustomerID("Nico") +val CustomerID(name) = customer2ID +println(name) // prints Nico +``` + +This is equivalent to `val name = CustomerID.unapply(customer2ID).get`. If there is no match, a `scala.MatchError` is thrown: + +```tut:fail +val CustomerID(name2) = "--asdfasdfasdf" +``` + +The return type of an `unapply` should be chosen as follows: + +* If it is just a test, return a `Boolean`. For instance `case even()` +* If it returns a single sub-value of type T, return an `Option[T]` +* If you want to return several sub-values `T1,...,Tn`, group them in an optional tuple `Option[(T1,...,Tn)]`. + +Sometimes, the number of sub-values is fixed and we would like to return a sequence. For this reason, you can also define patterns through `unapplySeq` which returns `Option[Seq[T]]` This mechanism is used for instance in pattern `case List(x1, ..., xn)`. diff --git a/_tour/for-comprehensions.md b/_tour/for-comprehensions.md new file mode 100644 index 0000000000..54fb57203f --- /dev/null +++ b/_tour/for-comprehensions.md @@ -0,0 +1,51 @@ +--- +layout: tour +title: For Comprehensions + +discourse: true + +partof: scala-tour + +num: 17 +next-page: generic-classes +previous-page: extractor-objects +--- + +Scala offers a lightweight notation for expressing *sequence comprehensions*. Comprehensions have the form `for (enumerators) yield e`, where `enumerators` refers to a semicolon-separated list of enumerators. An *enumerator* is either a generator which introduces new variables, or it is a filter. A comprehension evaluates the body `e` for each binding generated by the enumerators and returns a sequence of these values. + +Here's an example: + +```tut +case class User(val name: String, val age: Int) + +val userBase = List(new User("Travis", 28), + new User("Kelly", 33), + new User("Jennifer", 44), + new User("Dennis", 23)) + +val twentySomethings = for (user <- userBase if (user.age >=20 && user.age < 30)) + yield user.name // i.e. add this to a list + +twentySomethings.foreach(name => println(name)) // prints Travis Dennis +``` +The `for` loop used with a `yield` statement actually creates a `List`. Because we said `yield user.name`, it's a `List[String]`. `user <- userBase` is our generator and `if (user.age >=20 && user.age < 30)` is a guard that filters out users who are in their 20s. + +Here is a more complicated example using two generators. It computes all pairs of numbers between `0` and `n-1` whose sum is equal to a given value `v`: + +```tut +def foo(n: Int, v: Int) = + for (i <- 0 until n; + j <- i until n if i + j == v) + yield (i, j) + +foo(10, 10) foreach { + case (i, j) => + print(s"($i, $j) ") // prints (1, 9) (2, 8) (3, 7) (4, 6) (5, 5) +} + +``` +Here `n == 10` and `v == 10`. On the first iteration, `i == 0` and `j == 0` so `i + j != v` and therefore nothing is yielded. `j` gets incremented 9 more times before `i` gets incremented to `1`. Without the `if` guard, this would simply print the following: +``` + +(0, 0) (0, 1) (0, 2) (0, 3) (0, 4) (0, 5) (0, 6) (0, 7) (0, 8) (0, 9) (1, 1) ... +``` diff --git a/_tour/generic-classes.md b/_tour/generic-classes.md new file mode 100644 index 0000000000..3c3120ad0c --- /dev/null +++ b/_tour/generic-classes.md @@ -0,0 +1,57 @@ +--- +layout: tour +title: Generic Classes + +discourse: true + +partof: scala-tour + +num: 18 +next-page: variances +previous-page: for-comprehensions +assumed-knowledge: classes unified-types +--- +Generic classes are classes which take a type as a parameter. They are particularly useful for collection classes. + +## Defining a generic class +Generic classes take a type as a parameter within square brackets `[]`. One convention is to use the letter `A` as type parameter identifier, though any parameter name may be used. +```tut +class Stack[A] { + private var elements: List[A] = Nil + def push(x: A) { elements = x :: elements } + def peek: A = elements.head + def pop(): A = { + val currentTop = peek + elements = elements.tail + currentTop + } +} +``` +This implementation of a `Stack` class takes any type `A` as a parameter. This means the underlying list, `var elements: List[A] = Nil`, can only store elements of type `A`. The procedure `def push` only accepts objects of type `A` (note: `elements = x :: elements` reassigns `elements` to a new list created by prepending `x` to the current `elements`). + +## Usage + +To use a generic class, put the type in the square brackets in place of `A`. +``` +val stack = new Stack[Int] +stack.push(1) +stack.push(2) +println(stack.pop) // prints 2 +println(stack.pop) // prints 1 +``` +The instance `stack` can only take Ints. However, if the type argument had subtypes, those could be passed in: +``` +class Fruit +class Apple extends Fruit +class Banana extends Fruit + +val stack = new Stack[Fruit] +val apple = new Apple +val banana = new Banana + +stack.push(apple) +stack.push(banana) +``` +Class `Apple` and `Banana` both extend `Fruit` so we can push instances `apple` and `banana` onto the stack of `Fruit`. + +_Note: subtyping of generic types is *invariant*. This means that if we have a stack of characters of type `Stack[Char]` then it cannot be used as an integer stack of type `Stack[Int]`. This would be unsound because it would enable us to enter true integers into the character stack. To conclude, `Stack[A]` is only a subtype of `Stack[B]` if and only if `B = A`. Since this can be quite restrictive, Scala offers a [type parameter annotation mechanism](variances.html) to control the subtyping behavior of generic types._ diff --git a/_tour/higher-order-functions.md b/_tour/higher-order-functions.md new file mode 100644 index 0000000000..f021b94b8a --- /dev/null +++ b/_tour/higher-order-functions.md @@ -0,0 +1,42 @@ +--- +layout: tour +title: Higher-order Functions + +discourse: true + +partof: scala-tour + +num: 8 +next-page: nested-functions +previous-page: mixin-class-composition +--- + +Scala allows the definition of higher-order functions. These are functions that _take other functions as parameters_, or whose _result is a function_. Here is a function `apply` which takes another function `f` and a value `v` and applies function `f` to `v`: + +```tut +def apply(f: Int => String, v: Int) = f(v) +``` + +_Note: methods are automatically coerced to functions if the context requires this._ + +Here is another example: + +```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)) +} +``` + +Execution yields the output: + +``` +[7] +``` + +In this example, the method `decorator.layout` is coerced automatically to a value of type `Int => String` as required by method `apply`. Please note that method `decorator.layout` is a _polymorphic method_ (i.e. it abstracts over some of its signature types) and the Scala compiler has to instantiate its method type first appropriately. diff --git a/_tour/implicit-conversions.md b/_tour/implicit-conversions.md new file mode 100644 index 0000000000..bd38bead3c --- /dev/null +++ b/_tour/implicit-conversions.md @@ -0,0 +1,59 @@ +--- +layout: tour +title: Implicit Conversions + +discourse: true + +partof: scala-tour + +num: 27 +next-page: polymorphic-methods +previous-page: implicit-parameters +--- + +An implicit conversion from type `S` to type `T` is defined by an implicit value which has function type `S => T`, or by an implicit method convertible to a value of that type. + +Implicit conversions are applied in two situations: + +* If an expression `e` is of type `S`, and `S` does not conform to the expression's expected type `T`. +* In a selection `e.m` with `e` of type `S`, if the selector `m` does not denote a member of `S`. + +In the first case, a conversion `c` is searched for which is applicable to `e` and whose result type conforms to `T`. +In the second case, a conversion `c` is searched for which is applicable to `e` and whose result contains a member named `m`. + +The following operation on the two lists xs and ys of type `List[Int]` is legal: + +``` +xs <= ys +``` + +assuming the implicit methods `list2ordered` and `int2ordered` defined below are in scope: + +``` +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] { /* .. */ } +``` + +The implicitly imported object `scala.Predef` declares several predefined types (e.g. `Pair`) and methods (e.g. `assert`) but also several implicit conversions. + +For example, when calling a Java method that expects a `java.lang.Integer`, you are free to pass it a `scala.Int` instead. That's because Predef includes the following implicit conversions: + +```tut +import scala.language.implicitConversions + +implicit def int2Integer(x: Int) = + java.lang.Integer.valueOf(x) +``` + +Because implicit conversions can have pitfalls if used indiscriminately the compiler warns when compiling the implicit conversion definition. + +To turn off the warnings take either of these actions: + +* Import `scala.language.implicitConversions` into the scope of the implicit conversion definition +* Invoke the compiler with `-language:implicitConversions` + +No warning is emitted when the conversion is applied by the compiler. diff --git a/_tour/implicit-parameters.md b/_tour/implicit-parameters.md new file mode 100644 index 0000000000..76f7317233 --- /dev/null +++ b/_tour/implicit-parameters.md @@ -0,0 +1,58 @@ +--- +layout: tour +title: Implicit Parameters + +discourse: true + +partof: scala-tour + +num: 26 +next-page: implicit-conversions +previous-page: self-types +--- + +A method with _implicit parameters_ can be applied to arguments just like a normal method. In this case the implicit label has no effect. However, if such a method misses arguments for its implicit parameters, such arguments will be automatically provided. + +The actual arguments that are eligible to be passed to an implicit parameter fall into two categories: + +* First, eligible are all identifiers x that can be accessed at the point of the method call without a prefix and that denote an implicit definition or an implicit parameter. +* Second, eligible are also all members of companion modules of the implicit parameter's type that are labeled implicit. + +In the following example we define a method `sum` which computes the sum of a list of elements using the monoid's `add` and `unit` operations. Please note that implicit values can not be top-level, they have to be members of a template. + +```tut +/** This example uses a structure from abstract algebra to show how implicit parameters work. A semigroup is an algebraic structure on a set A with an (associative) operation, called add here, that combines a pair of A's and returns another A. */ +abstract class SemiGroup[A] { + def add(x: A, y: A): A +} +/** A monoid is a semigroup with a distinguished element of A, called unit, that when combined with any other element of A returns that other element again. */ +abstract class Monoid[A] extends SemiGroup[A] { + def unit: A +} +object ImplicitTest extends App { + /** To show how implicit parameters work, we first define monoids for strings and integers. The implicit keyword indicates that the corresponding object can be used implicitly, within this scope, as a parameter of a function marked implicit. */ + 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 + } + /** This method takes a List[A] returns an A which represent the combined value of applying the monoid operation successively across the whole list. Making the parameter m implicit here means we only have to provide the xs parameter at the call site, since if we have a List[A] we know what type A actually is and therefore what type Monoid[A] is needed. We can then implicitly find whichever val or object in the current scope also has that type and use that without needing to specify it explicitly. */ + def sum[A](xs: List[A])(implicit m: Monoid[A]): A = + if (xs.isEmpty) m.unit + else m.add(xs.head, sum(xs.tail)) + + /** Here we call sum twice, with only one parameter each time. Since the second parameter of sum, m, is implicit its value is looked up in the current scope, based on the type of monoid required in each case, meaning both expressions can be fully evaluated. */ + println(sum(List(1, 2, 3))) // uses IntMonoid implicitly + println(sum(List("a", "b", "c"))) // uses StringMonoid implicitly +} +``` + +Here is the output of the Scala program: + +``` +6 +abc +``` diff --git a/_tour/inner-classes.md b/_tour/inner-classes.md new file mode 100644 index 0000000000..f9d6d73986 --- /dev/null +++ b/_tour/inner-classes.md @@ -0,0 +1,81 @@ +--- +layout: tour +title: Inner Classes + +discourse: true + +partof: scala-tour + +num: 22 +next-page: abstract-types +previous-page: lower-type-bounds +--- + +In Scala it is possible to let classes have other classes as members. As opposed to Java-like languages where such inner classes are members of the enclosing class, in Scala such inner classes are bound to the outer object. Suppose we want the compiler to prevent us, at compile time, from mixing up which nodes belong to what graph. Path-dependent types provide a solution. + +To illustrate the difference, we quickly sketch the implementation of a graph datatype: + +```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 + } +} +``` +This program represents a graph as a list of nodes (`List[Node]`). Each node has a list of other nodes it's connected to (`connectedNodes`). The `class Node` is a _path-dependent type_ because it is nested in the `class Graph`. Therefore, all nodes in the `connectedNodes` must be created using the `newNode` from the same instance of `Graph`. + +```tut +val graph1: Graph = new Graph +val node1: graph1.Node = graph1.newNode +val node2: graph1.Node = graph1.newNode +val node3: graph1.Node = graph1.newNode +node1.connectTo(node2) +node3.connectTo(node1) +``` +We have explicitly declared the type of `node1`, `node2`, and `node3` as `graph1.Node` for clarity but the compiler could have inferred it. This is because when we call `graph1.newNode` which calls `new Node`, the method is using the instance of `Node` specific to the instance `graph1`. + +If we now have two graphs, the type system of Scala does not allow us to mix nodes defined within one graph with the nodes of another graph, since the nodes of the other graph have a different type. +Here is an illegal program: + +``` +val graph1: Graph = new Graph +val node1: graph1.Node = graph1.newNode +val node2: graph1.Node = graph1.newNode +node1.connectTo(node2) // legal +val graph2: Graph = new Graph +val node3: graph2.Node = graph2.newNode +node1.connectTo(node3) // illegal! +``` +The type `graph1.Node` is distinct from the type `graph2.Node`. In Java, the last line in the previous example program would have been correct. For nodes of both graphs, Java would assign the same type `Graph.Node`; i.e. `Node` is prefixed with class `Graph`. In Scala such a type can be expressed as well, it is written `Graph#Node`. If we want to be able to connect nodes of different graphs, we have to change the definition of our initial graph implementation in the following way: + +```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 + } +} +``` + +> Note that this program doesn't allow us to attach a node to two different graphs. If we want to remove this restriction as well, we have to change the type of variable nodes to `Graph#Node`. diff --git a/_tour/local-type-inference.md b/_tour/local-type-inference.md new file mode 100644 index 0000000000..4a452a8eac --- /dev/null +++ b/_tour/local-type-inference.md @@ -0,0 +1,62 @@ +--- +layout: tour +title: Local Type Inference + +discourse: true + +partof: scala-tour + +num: 29 +next-page: operators +previous-page: polymorphic-methods +--- +Scala has a built-in type inference mechanism which allows the programmer to omit certain type annotations. It is, for instance, often not necessary in Scala to specify the type of a variable, since the compiler can deduce the type from the initialization expression of the variable. Also return types of methods can often be omitted since they correspond to the type of the body, which gets inferred by the compiler. + +Here is an example: + +```tut +object InferenceTest1 extends App { + val x = 1 + 2 * 3 // the type of x is Int + val y = x.toString() // the type of y is String + def succ(x: Int) = x + 1 // method succ returns Int values +} +``` + +For recursive methods, the compiler is not able to infer a result type. Here is a program which will fail the compiler for this reason: + +```tut:fail +object InferenceTest2 { + def fac(n: Int) = if (n == 0) 1 else n * fac(n - 1) +} +``` + +It is also not compulsory to specify type parameters when [polymorphic methods](polymorphic-methods.html) are called or [generic classes](generic-classes.html) are instantiated. The Scala compiler will infer such missing type parameters from the context and from the types of the actual method/constructor parameters. + +Here is an example which illustrates this: + +``` +case class MyPair[A, B](x: A, y: B); +object InferenceTest3 extends App { + def id[T](x: T) = x + val p = MyPair(1, "scala") // type: MyPair[Int, String] + val q = id(1) // type: Int +} +``` + +The last two lines of this program are equivalent to the following code where all inferred types are made explicit: + +``` +val x: MyPair[Int, String] = MyPair[Int, String](1, "scala") +val y: Int = id[Int](1) +``` + +In some situations it can be quite dangerous to rely on Scala's type inference mechanism as the following program shows: + +```tut:fail +object InferenceTest4 { + var obj = null + obj = new Object() +} +``` + +This program does not compile because the type inferred for variable `obj` is `Null`. Since the only value of that type is `null`, it is impossible to make this variable refer to another value. diff --git a/_tour/lower-type-bounds.md b/_tour/lower-type-bounds.md new file mode 100644 index 0000000000..60e670b48f --- /dev/null +++ b/_tour/lower-type-bounds.md @@ -0,0 +1,67 @@ +--- +layout: tour +title: Lower Type Bounds + +discourse: true + +partof: scala-tour + +num: 21 +next-page: inner-classes +previous-page: upper-type-bounds +prerequisite-knowledge: upper-type-bounds, generics, variance +--- + +While [upper type bounds](upper-type-bounds.html) limit a type to a subtype of another type, *lower type bounds* declare a type to be a supertype of another type. The term `B >: A` expresses that the type parameter `B` or the abstract type `B` refer to a supertype of type `A`. In most cases, `A` will be the type parameter of the class and `B` will be the type parameter of a method. + +Here is an example where this is useful: + +```tut:fail +trait Node[+B] { + def prepend(elem: B): Unit +} + +case class ListNode[+B](h: B, t: Node[B]) extends Node[B] { + def prepend(elem: B) = ListNode[B](elem, this) + def head: B = h + def tail = t +} + +case class Nil[+B]() extends Node[B] { + def prepend(elem: B) = ListNode[B](elem, this) +} +``` +This program implements a singly-linked list. `Nil` represents an empty element (i.e. an empty list). `class ListNode` is a node which contains an element of type `B` (`head`) and a reference to the rest of the list (`tail`). The `class Node` and its subtypes are covariant because we have `+B`. + +However, this program does _not_ compile because the parameter `elem` in `prepend` is of type `B`, which we declared *co*variant. This doesn't work because functions are *contra*variant in their parameter types and *co*variant in their result types. + +To fix this, we need to flip the variance of the type of the parameter `elem` in `prepend`. We do this by introducing a new type parameter `U` that has `B` as a lower type bound. + +```tut +trait Node[+B] { + def prepend[U >: B](elem: U) +} + +case class ListNode[+B](h: B, t: Node[B]) extends Node[B] { + def prepend[U >: B](elem: U) = ListNode[U](elem, this) + def head: B = h + def tail = t +} + +case class Nil[+B]() extends Node[B] { + def prepend[U >: B](elem: U) = ListNode[U](elem, this) +} +``` + +Now we can do the following: +```tut +trait Mammal +case class AfricanSwallow() extends Mammal +case class EuropeanSwallow() extends Mammal + + +val africanSwallowList= ListNode[AfricanSwallow](AfricanSwallow(), Nil()) +val mammalList: Node[Mammal] = africanSwallowList +mammalList.prepend(new EuropeanSwallow) +``` +The `Node[Mammal]` can be assigned the `africanSwallowList` but then accept `EuropeanSwallow`s. diff --git a/_tour/mixin-class-composition.md b/_tour/mixin-class-composition.md new file mode 100644 index 0000000000..c45f3e802b --- /dev/null +++ b/_tour/mixin-class-composition.md @@ -0,0 +1,81 @@ +--- +layout: tour +title: Class Composition with Mixins + +discourse: true + +partof: scala-tour + +num: 6 +next-page: higher-order-functions +previous-page: traits +prerequisite-knowledge: inheritance, traits, abstract-classes, unified-types +--- +Mixins are traits which are used to compose a class. + +```tut +abstract class A { + val message: String +} +class B extends A { + val message = "I'm an instance of class B" +} +trait C extends A { + def loudMessage = message.toUpperCase() +} +class D extends B with C + +val d = new D +d.message // I'm an instance of class B +d.loudMessage // I'M AN INSTANCE OF CLASS B +``` +Class `D` has a superclass `B` and a mixin `C`. Classes can only have one superclass but many mixins (using the keywords `extends` and `with` respectively). The mixins and the superclass may have the same supertype. + +Now let's look at a more interesting example starting with an abstract class: + +```tut +abstract class AbsIterator { + type T + def hasNext: Boolean + def next: T +} +``` +The class has an abstract type `T` and the standard iterator methods. + +Next, we'll implement a concrete class (all abstract members `T`, `hasNext`, and `next` have implementations): + +```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 + } +} +``` +`StringIterator` takes a `String` and can be used to iterate over the String (e.g. to see if a String contains a certain character). + +Now let's create a trait which also extends `AbsIterator`. + +```tut +trait RichIterator extends AbsIterator { + def foreach(f: T => Unit): Unit = while (hasNext) f(next) +} +``` +Because `RichIterator` is a trait, it doesn't need to implement the abstract members of AbsIterator. + +We would like to combine the functionality of `StringIterator` and `RichIterator` into a single class. + +```tut +object StringIteratorTest extends App { + class Iter extends StringIterator(args(0)) with RichIterator + val iter = new Iter + iter foreach println +} +``` +The new class `RichStringIter` has `StringIterator` as a superclass and `RichIterator` as a mixin. + +With single inheritance we would not be able to achieve this level of flexibility. diff --git a/_tour/named-arguments.md b/_tour/named-arguments.md new file mode 100644 index 0000000000..de549a8b0e --- /dev/null +++ b/_tour/named-arguments.md @@ -0,0 +1,35 @@ +--- +layout: tour +title: Named Arguments + +discourse: true + +partof: scala-tour + +num: 34 +previous-page: default-parameter-values +prerequisite-knowledge: function-syntax +--- + +When calling methods, you can label the arguments with their parameter names like so: + +```tut + def printName(first: String, last: String): Unit = { + println(first + " " + last) + } + + printName("John", "Smith") // Prints "John Smith" + printName(first = "John", last = "Smith") // Prints "John Smith" + printName(last = "Smith", first = "John") // Prints "John Smith" +``` +Notice how the order of named arguments can be rearranged. However, if some arguments are named and others are not, the unnamed arguments must come first and in the order of their parameters in the method signature. + +``` +def printName(first: String, last: String): Unit = { + println(first + " " + last) +} + +printName(last = "Smith", "john") // Does not compile +``` + +Note that named arguments do not work with calls to Java methods. diff --git a/_tour/nested-functions.md b/_tour/nested-functions.md new file mode 100644 index 0000000000..5d93e4eceb --- /dev/null +++ b/_tour/nested-functions.md @@ -0,0 +1,34 @@ +--- +layout: tour +title: Nested Methods + +discourse: true + +partof: scala-tour + +num: 9 +next-page: currying +previous-page: higher-order-functions +--- + +In Scala it is possible to nest function definitions. The following object provides a `factorial` function for computing the factorial of a given number: + +```tut + def factorial(x: Int): Int = { + def fact(x: Int, accumulator: Int): Int = { + if (x <= 1) accumulator + else fact(x - 1, x * accumulator) + } + fact(x, 1) + } + + println("Factorial of 2: " + factorial(2)) + println("Factorial of 3: " + factorial(3)) +``` + +The output of this program is: + +``` +Factorial of 2: 2 +Factorial of 3: 6 +``` diff --git a/_tour/operators.md b/_tour/operators.md new file mode 100644 index 0000000000..58429795b7 --- /dev/null +++ b/_tour/operators.md @@ -0,0 +1,79 @@ +--- +layout: tour +title: Operators + +discourse: true + +partof: scala-tour + +num: 30 +next-page: by-name-parameters +previous-page: local-type-inference +prerequisite-knowledge: case-classes +--- +In Scala, operators are methods. Any method with a single parameter can be used as an _infix operator_. For example, `+` can be called with dot-notation: +``` +10.+(1) +``` + +However, it's easier to read as an infix operator: +``` +10 + 1 +``` + +## Defining and using operators +You can use any legal identifier as an operator. This includes a name like `add` or a symbol(s) like `+`. +```tut +case class Vec(val x: Double, val y: Double) { + def +(that: Vec) = new Vec(this.x + that.x, this.y + that.y) +} + +val vector1 = Vec(1.0, 1.0) +val vector2 = Vec(2.0, 2.0) + +val vector3 = vector1 + vector2 +vector3.x // 3.0 +vector3.y // 3.0 +``` +The class Vec has a method `+` which we used to add `vector1` and `vector2`. Using parentheses, you can build up complex expressions with readable syntax. Here is the definition of class `MyBool` which includes methods `and` and `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) +} +``` + +It is now possible to use `and` and `or` as infix operators: + +```tut +def not(x: MyBool) = x.negate +def xor(x: MyBool, y: MyBool) = (x or y) and not(x and y) +``` + +This helps to make the definition of `xor` more readable. + +## Precedence +When an expression uses multiple operators, the operators are evaluated based on the priority of the first character: +``` +(characters not shown below) +* / % ++ - +: += ! +< > +& +^ +| +(all letters) +``` +This applies to functions you define. For example, the following expression: +``` +a + b ^? c ?^ d less a ==> b | c +``` +Is equivalent to +``` +((a + b) ^? (c ?^ d)) less ((a ==> b) | c) +``` +`?^` has the highest precedence because it starts with the character `?`. `+` has the second highest precedence, followed by `^?`, `==>`, `|`, and `less`. diff --git a/_tour/pattern-matching.md b/_tour/pattern-matching.md new file mode 100644 index 0000000000..23d1a7b4ae --- /dev/null +++ b/_tour/pattern-matching.md @@ -0,0 +1,149 @@ +--- +layout: tour +title: Pattern Matching + +discourse: true + +partof: scala-tour + +num: 12 + +next-page: singleton-objects +previous-page: case-classes +prerequisite-knowledge: case-classes, string-interpolation, subtyping +--- + +Pattern matching is a mechanism for checking a value against a pattern. A successful match can also deconstruct a value into its constituent parts. It is a more powerful version of the `switch` statement in Java and it can likewise be used in place of a series of if/else statements. + +## Syntax +A match expression has a value, the `match` keyword, and at least one `case` clause. +```tut +import scala.util.Random + +val x: Int = Random.nextInt(10) + +x match { + case 0 => "zero" + case 1 => "one" + case 2 => "two" + case _ => "many" +} +``` +The `val x` above is a random integer between 0 and 10. `x` becomes the left operand of the `match` operator and on the right is an expression with four cases. The last case `_` is a "catch all" case for any number greater than 2. Cases are also called _alternatives_. + +Match expressions have a value. +```tut +def matchTest(x: Int): String = x match { + case 1 => "one" + case 2 => "two" + case _ => "many" +} +matchTest(3) // many +matchTest(1) // one +``` +This match expression has a type String because all of the cases return String. Therefore, the function `matchTest` returns a String. + +## Matching on case classes + +Case classes are especially useful for pattern matching. + +```tut +abstract class Notification + +case class Email(sender: String, title: String, body: String) extends Notification + +case class SMS(caller: String, message: String) extends Notification + +case class VoiceRecording(contactName: String, link: String) extends Notification + + +``` +`Notification` is an abstract super class which has three concrete Notification types implemented with case classes `Email`, `SMS`, and `VoiceRecording`. Now we can do pattern matching on these case classes: + +``` +def showNotification(notification: Notification): String = { + notification match { + case Email(email, title, _) => + s"You got an email from $email with title: $title" + case SMS(number, message) => + s"You got an SMS from $number! Message: $message" + case VoiceRecording(name, link) => + s"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)) // prints You got an SMS from 12345! Message: Are you there? + +println(showNotification(someVoiceRecording)) // you received a Voice Recording from Tom! Click the link to hear it: voicerecording.org/id/123 +``` +The function `showNotification` takes as a parameter the abstract type `Notification` and matches on the type of `Notification` (i.e. it figures out whether it's an `Email`, `SMS`, or `VoiceRecording`). In the `case Email(email, title, _)` the fields `email` and `title` are used in the return value but the `body` field is ignored with `_`. + +## Pattern guards +Pattern guards are simply boolean expressions which are used to make cases more specific. Just add `if ` after the pattern. +``` + +def showImportantNotification(notification: Notification, importantPeopleInfo: Seq[String]): String = { + notification match { + case Email(email, _, _) if importantPeopleInfo.contains(email) => + "You got an email from special someone!" + case SMS(number, _) if importantPeopleInfo.contains(number) => + "You got an SMS from special someone!" + case other => + showNotification(other) // nothing special, delegate to our original showNotification function + } +} + +val importantPeopleInfo = Seq("867-5309", "jenny@gmail.com") + +val someSms = SMS("867-5309", "Are you there?") +val someVoiceRecording = VoiceRecording("Tom", "voicerecording.org/id/123") +val importantEmail = Email("jenny@gmail.com", "Drinks tonight?", "I'm free after 5!") +val importantSms = SMS("867-5309", "I'm here! Where are you?") + +println(showImportantNotification(someSms, importantPeopleInfo)) +println(showImportantNotification(someVoiceRecording, importantPeopleInfo)) +println(showImportantNotification(importantEmail, importantPeopleInfo)) +println(showImportantNotification(importantSms, importantPeopleInfo)) +``` + +In the `case Email(email, _, _) if importantPeopleInfo.contains(email)`, the pattern is matched only if the `email` is in the list of important people. + +## Matching on type only +You can match on the type like so: +```tut +abstract class Device +case class Phone(model: String) extends Device{ + def screenOff = "Turning screen off" +} +case class Computer(model: String) extends Device { + def screenSaverOn = "Turning screen saver on..." +} + +def goIdle(device: Device) = device match { + case p: Phone => p.screenOff + case c: Computer => c.screenSaverOn +} +``` +`def goIdle` has a different behavior depending on the type of `Device`. This is useful when the case needs to call a method on the pattern. It is a convention to use the first letter of the type as the case identifier (`p` and `c` in this case). + +## Sealed classes +Traits and classes can be marked `sealed` which means all subtypes must be declared in the same file. The assures that all subtypes are known. + +```tut +sealed abstract class Furniture +case class Couch() extends Furniture +case class Chair() extends Furniture + +def findPlaceToSit(piece: Furniture): String = piece match { + case a: Couch => "Lie on the couch" + case b: Chair => "Sit on the chair" +} +``` +This is useful for pattern matching because we don't need a "catch all" case. + +## Notes + +Scala's pattern matching statement is most useful for matching on algebraic types expressed via [case classes](case-classes.html). +Scala also allows the definition of patterns independently of case classes, using `unapply` methods in [extractor objects](extractor-objects.html). diff --git a/_tour/polymorphic-methods.md b/_tour/polymorphic-methods.md new file mode 100644 index 0000000000..ad2c7533be --- /dev/null +++ b/_tour/polymorphic-methods.md @@ -0,0 +1,33 @@ +--- +layout: tour +title: Polymorphic Methods + +discourse: true + +partof: scala-tour + +num: 28 + +next-page: local-type-inference +previous-page: implicit-conversions +prerequisite-knowledge: unified-types, +--- + +Methods in Scala can be parameterized by type as well as value. The syntax is similar to that of generic classes. Type parameters are declared within a pair of brackets while value parameters are enclosed in a pair of parentheses. + +Here is an example: + +```tut +def listOfDuplicates[A](x: A, length: Int): List[A] = { + if (length < 1) + Nil + else + x :: listOfDuplicates(x, length - 1) +} +println(listOfDuplicates[Int](3, 4)) // List(3, 3, 3, 3) +println(listOfDuplicates("La", 8)) // List(La, La, La, La, La, La, La, La) +``` + +The method `listOfDuplicates` takes a type parameter `A` and values parameters `x` and `n`. In this case, value `x` is of type `A`. If `length < 1` we return an empty list. Otherwise we prepend `x` to the the list of duplicates returned by the recursive call to `listOfDuplicates`. (note: `::` means prepend an element on the left to a sequence on the right). + +When we call `listOfDuplicates` with `[Int]` as the type parameter, the first argument must be an int and the return type will be List[Int]. However, you don't always need to explicitly provide the the type parameter because the compiler can often figure it out based on the type of value argument (`"La"` is a String). In fact, if calling this method from Java it is impossible to provide the type parameter. diff --git a/_tour/regular-expression-patterns.md b/_tour/regular-expression-patterns.md new file mode 100644 index 0000000000..1c3fd8dd12 --- /dev/null +++ b/_tour/regular-expression-patterns.md @@ -0,0 +1,61 @@ +--- +layout: tour +title: Regular Expression Patterns + +discourse: true + +partof: scala-tour + +num: 15 + +next-page: extractor-objects +previous-page: singleton-objects +--- + +Regular expressions are strings which can be used to find patterns (or lack thereof) in data. Any string can be converted to a regular expression using the `.r` method. + +```tut +import scala.util.matching.Regex + +val numberPattern: Regex = "[0-9]".r + +numberPattern.findFirstMatchIn("awesomepassword") match { + case Some(_) => println("Password OK") + case None => println("Password must contain a number") +} +``` + +In the above example, the `numberPattern` is a `Regex` +(regular expression) which we use to make sure a password contains a number. + +You can also search for groups of regular expressions using parentheses. + +```tut +import scala.util.matching.Regex + +val keyValPattern: Regex = "([0-9a-zA-Z-#() ]+): ([0-9a-zA-Z-#() ]+)".r + +val input: String = + """background-color: #A03300; + |background-image: url(img/header100.png); + |background-position: top center; + |background-repeat: repeat-x; + |background-size: 2160px 108px; + |margin: 0; + |height: 108px; + |width: 100%;""".stripMargin + +for (patternMatch <- keyValPattern.findAllMatchIn(input)) + println(s"key: ${patternMatch.group(1)} value: ${patternMatch.group(2)}") +``` +Here we parse out the keys and values of a String. Each match has a group of sub-matches. Here is the output: +``` +key: background-color value: #A03300 +key: background-image value: url(img +key: background-position value: top center +key: background-repeat value: repeat-x +key: background-size value: 2160px 108px +key: margin value: 0 +key: height value: 108px +key: width value: 100 +``` diff --git a/_tour/self-types.md b/_tour/self-types.md new file mode 100644 index 0000000000..ee43cd426b --- /dev/null +++ b/_tour/self-types.md @@ -0,0 +1,37 @@ +--- +layout: tour +title: Self-type + +discourse: true + +partof: scala-tour + +num: 25 +next-page: implicit-parameters +previous-page: compound-types +prerequisite-knowledge: nested-classes, mixin-class-composition +--- +Self-types are a way to declare that a trait must be mixed into another trait, even though it doesn't directly extend it. That makes the members of the dependency available without imports. + +A self-type is a way to narrow the type of `this` or another identifier that aliases `this`. The syntax looks like normal function syntax but means something entirely different. + +To use a self-type in a trait, write an identifier, the type of another trait to mix in, and a `=>` (e.g. `someIdentifier: SomeOtherTrait =>`). +```tut +trait User { + def username: String +} + +trait Tweeter { + this: User => // reassign this + def tweet(tweetText: String) = println(s"$username: $tweetText") +} + +class VerifiedTweeter(val username_ : String) extends Tweeter with User { // We mixin User because Tweeter required it + def username = s"real $username_" +} + +val realBeyoncé = new VerifiedTweeter("Beyoncé") +realBeyoncé.tweet("Just spilled my glass of lemonade") // prints "real Beyoncé: Just spilled my glass of lemonade" +``` + +Because we said `this: User =>` in `trait Tweeter`, now the variable `username` is in scope for the `tweet` method. This also means that since `VerifiedTweeter` extends `Tweeter`, it must also mix-in `User` (using `with User`). diff --git a/_tour/sequence-comprehensions.md b/_tour/sequence-comprehensions.md new file mode 100644 index 0000000000..87160e06bd --- /dev/null +++ b/_tour/sequence-comprehensions.md @@ -0,0 +1,69 @@ +--- +layout: tour +title: Sequence Comprehensions + +disqus: true + +partof: scala-tour + +num: 17 +next-page: generic-classes +previous-page: extractor-objects +--- + +Scala offers a lightweight notation for expressing *sequence comprehensions*. Comprehensions have the form `for (enumerators) yield e`, where `enumerators` refers to a semicolon-separated list of enumerators. An *enumerator* is either a generator which introduces new variables, or it is a filter. A comprehension evaluates the body `e` for each binding generated by the enumerators and returns a sequence of these values. + +Here is an example: + +```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)) +} +``` + +The for-expression in function introduces a new variable `i` of type `Int` which is subsequently bound to all values of the list `List(from, from + 1, ..., to - 1)`. The guard `if i % 2 == 0` filters out all odd numbers so that the body (which only consists of the expression i) is only evaluated for even numbers. Consequently, the whole for-expression returns a list of even numbers. + +The program yields the following output: + +``` +List(0, 2, 4, 6, 8, 10, 12, 14, 16, 18) +``` + +Here is a more complicated example which computes all pairs of numbers between `0` and `n-1` whose sum is equal to a given value `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)") + } +} +``` + +This example shows that comprehensions are not restricted to lists. The previous program uses iterators instead. Every datatype that supports the operations `withFilter`, `map`, and `flatMap` (with the proper types) can be used in sequence comprehensions. + +Here's the output of the program: + +``` +(13, 19) +(14, 18) +(15, 17) +(16, 16) +``` + +There is also a special form of sequence comprehension which returns `Unit`. Here the bindings that are created from the list of generators and filters are used to perform side-effects. The programmer has to omit the keyword `yield` to make use of such a sequence comprehension. +Here's a program which is equivalent to the previous one but uses the special for comprehension returning `Unit`: + +``` +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/_tour/singleton-objects.md b/_tour/singleton-objects.md new file mode 100644 index 0000000000..64b97775eb --- /dev/null +++ b/_tour/singleton-objects.md @@ -0,0 +1,74 @@ +--- +layout: tour +title: Singleton Objects + +discourse: true + +partof: scala-tour + +num: 13 + +next-page: regular-expression-patterns +previous-page: pattern-matching +--- + +Methods and values that aren't associated with individual instances of a [class](classes.html) belong in *singleton objects*, denoted by using the keyword `object` instead of `class`. + +``` +package test + +object Blah { + def sum(l: List[Int]): Int = l.sum +} +``` + +This `sum` method is available globally, and can be referred to, or imported, as `test.Blah.sum`. + +Singleton objects are sort of a shorthand for defining a single-use class, which can't directly be instantiated, and a `val` member at the point of definition of the `object`, with the same name. Indeed, like `val`s, singleton objects can be defined as members of a [trait](traits.html) or class, though this is atypical. + +A singleton object can extend classes and traits. In fact, a [case class](case-classes.html) with no [type parameters](generic-classes.html) will by default create a singleton object of the same name, with a [`Function*`](http://www.scala-lang.org/api/current/scala/Function1.html) trait implemented. + +## Companions ## + +Most singleton objects do not stand alone, but instead are associated with a class of the same name. The “singleton object of the same name” of a case class, mentioned above, is an example of this. When this happens, the singleton object is called the *companion object* of the class, and the class is called the *companion class* of the object. + +[Scaladoc](https://wiki.scala-lang.org/display/SW/Introduction) has special support for jumping between a class and its companion: if the big “C” or “O” circle has its edge folded up at the bottom, you can click the circle to jump to the companion. + +A class and its companion object, if any, must be defined in the same source file. Like this: + +```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)) +} +``` + +It's common to see typeclass instances as [implicit values](implicit-parameters.html), such as `ipord` above, defined in the companion, when following the typeclass pattern. This is because the companion's members are included in the default implicit search for related values. + +## Notes for Java programmers ## + +`static` is not a keyword in Scala. Instead, all members that would be static, including classes, should go in a singleton object. They can be referred to with the same syntax, imported piecemeal or as a group, and so on. + +Frequently, Java programmers define static members, perhaps `private`, as implementation aids for their instance members. These move to the companion, too; a common pattern is to import the companion object's members in the class, like so: + +``` +class X { + import X._ + + def blah = foo +} + +object X { + private def foo = 42 +} +``` + +This illustrates another feature: in the context of `private`, a class and its companion are friends. `object X` can access private members of `class X`, and vice versa. To make a member *really* private to one or the other, use `private[this]`. + +For Java convenience, methods, including `var`s and `val`s, defined directly in a singleton object also have a static method defined in the companion class, called a *static forwarder*. Other members are accessible via the `X$.MODULE$` static field for `object X`. + +If you move everything to a companion object and find that all you have left is a class you don't want to be able to instantiate, simply delete the class. Static forwarders will still be created. diff --git a/_tour/tour-of-scala.md b/_tour/tour-of-scala.md new file mode 100644 index 0000000000..0592bce58b --- /dev/null +++ b/_tour/tour-of-scala.md @@ -0,0 +1,46 @@ +--- +layout: tour +title: Introduction + +discourse: true + +partof: scala-tour + +num: 1 + +next-page: basics +--- + +Scala is a modern multi-paradigm programming language designed to express common programming patterns in a concise, elegant, and type-safe way. It smoothly integrates features of object-oriented and functional languages. + +## Scala is object-oriented ## +Scala is a pure object-oriented language in the sense that [every value is an object](unified-types.html). Types and behavior of objects are described by [classes](classes.html) and [traits](traits.html). Classes are extended by subclassing and a flexible [mixin-based composition](mixin-class-composition.html) mechanism as a clean replacement for multiple inheritance. + +## Scala is functional ## +Scala is also a functional language in the sense that [every function is a value](unified-types.html). Scala provides a [lightweight syntax](anonymous-function-syntax.html) for defining anonymous functions, it supports [higher-order functions](higher-order-functions.html), it allows functions to be [nested](nested-functions.html), and supports [currying](currying.html). Scala's [case classes](case-classes.html) and its built-in support for [pattern matching](pattern-matching.html) model algebraic types used in many functional programming languages. [Singleton objects](singleton-objects.html) provide a convenient way to group functions that aren't members of a class. + +Furthermore, Scala's notion of pattern matching naturally extends to the [processing of XML data](xml-processing.html) with the help of [right-ignoring sequence patterns](regular-expression-patterns.html), by way of general extension via [extractor objects](extractor-objects.html). In this context, [for comprehensions](for-comprehensions.html) are useful for formulating queries. These features make Scala ideal for developing applications like web services. + +## Scala is statically typed ## +Scala is equipped with an expressive type system that enforces statically that abstractions are used in a safe and coherent manner. In particular, the type system supports: + +* [generic classes](generic-classes.html) +* [variance annotations](variances.html) +* [upper](upper-type-bounds.html) and [lower](lower-type-bounds.html) type bounds, +* [inner classes](inner-classes.html) and [abstract types](abstract-types.html) as object members +* [compound types](compound-types.html) +* [explicitly typed self references](explicitly-typed-self-references.html) +* [implicit parameters](implicit-parameters.html) and [conversions](implicit-conversions.html) +* [polymorphic methods](polymorphic-methods.html) + +A [local type inference mechanism](local-type-inference.html) takes care that the user is not required to annotate the program with redundant type information. In combination, these features provide a powerful basis for the safe reuse of programming abstractions and for the type-safe extension of software. + +## Scala is extensible ## + +In practice, the development of domain-specific applications often requires domain-specific language extensions. Scala provides a unique combination of language mechanisms that make it easy to smoothly add new language constructs in the form of libraries. + +A joint use of both features facilitates the definition of new statements without using meta-programming facilities such as macros. + +Scala is designed to interoperate well with the popular Java 2 Runtime Environment (JRE). In particular, the interaction with the mainstream object-oriented Java programming language is as smooth as possible. Newer Java features like [annotations](annotations.html) and Java generics have direct analogues in Scala. Those Scala features without Java analogues, such as [default](default-parameter-values.html) and [named parameters](named-parameters.html), compile as close to Java as they can reasonably come. Scala has the same compilation model (separate compilation, dynamic class loading) like Java and allows access to thousands of existing high-quality libraries. + +Please continue to the next page to read more. diff --git a/_tour/traits.md b/_tour/traits.md new file mode 100644 index 0000000000..142618b5fe --- /dev/null +++ b/_tour/traits.md @@ -0,0 +1,82 @@ +--- +layout: tour +title: Traits + +discourse: true + +partof: scala-tour + +num: 5 +next-page: mixin-class-composition +previous-page: classes +assumed-knowledge: expressions, classes, generics, objects, companion-objects +--- + +Traits are used to share interfaces and fields between classes. They are similar to Java 8's interfaces. Classes and objects can extend traits but traits cannot be instantiated and therefore have no parameters. + +## Defining a trait +A minimal trait is simply the keyword `trait` and an identifier: + +```tut +trait HairColor +``` + +Traits become especially useful as generic types and with abstract methods. +```tut +trait Iterator[A] { + def hasNext: Boolean + def next(): A +} +``` + +Extending the `trait Iterator[A]` requires a type `A` and implementations of the methods `hasNext` and `next`. + +## Using traits +Use the `extends` keyword to extend a trait. Then implement any abstract members of the trait using the `override` keyword: +```tut +trait Iterator[A] { + def hasNext: Boolean + def next(): A +} + + +class IntIterator(to: Int) extends Iterator[Int] { + private var current = 0 + override def hasNext: Boolean = current < to + override def next(): Int = { + if (hasNext) { + val t = current + current += 1 + t + } else 0 + } +} + + +val iterator = new IntIterator(10) +iterator.next() // prints 0 +iterator.next() // prints 1 +``` +This `IntIterator` class takes a parameter `to` as an upper bound. It `extends Iterator[Int]` which means that the `next` method must return an Int. + +## Subtyping +Subtypes of traits can be used where a the trait is required. +```tut +import scala.collection.mutable.ArrayBuffer + +trait Pet { + val name: String +} + +class Cat(val name: String) extends Pet +class Dog(val name: String) extends Pet + +val dog = new Dog("Harry") +val cat = new Cat("Sally") + +val animals = ArrayBuffer.empty[Pet] +animals.append(dog) +animals.append(cat) +animals.foreach(pet => println(pet.name)) // Prints Harry Sally +``` +The `trait Pet` has an abstract field `name` which gets implemented by Cat and Dog in their constructors. On the last line, we call `pet.name` which must be implemented in any subtype of the trait `Pet`. diff --git a/_tour/unified-types.md b/_tour/unified-types.md new file mode 100644 index 0000000000..0d2f958a7d --- /dev/null +++ b/_tour/unified-types.md @@ -0,0 +1,80 @@ +--- +layout: tour +title: Unified Types + +discourse: true + +partof: scala-tour + +num: 3 +next-page: classes +previous-page: basics +prerequisite-knowledge: classes, basics +--- + +In Scala, all values have a type, including numerical values and functions. The diagram below illustrates a subset of the type hierarchy. + +Scala Type Hierarchy + +## Scala Type Hierarchy ## + +[`Any`](http://www.scala-lang.org/api/2.12.1/scala/Any.html) is the supertype of all types, also called the top type. It defines certain universal methods such as `equals`, `hashCode`, and `toString`. `Any` has two direct subclasses: `AnyVal` and `AnyRef`. + +`AnyVal` represents value types. There are nine predefined value types and they are non-nullable: `Double`, `Float`, `Long`, `Int`, `Short`, `Byte`, `Char`, `Unit`, and `Boolean`. `Unit` is a value type which carries no meaningful information. There is exactly one instance of `Unit` which can be declared literally like so: `()`. All functions must return something so sometimes `Unit` is a useful return type. + +`AnyRef` represents reference types. All non-value types are defined as reference types. Anywhere an `AnyRef` Every user-defined type in Scala is a subtype of `AnyRef`. If Scala is used in the context of a Java runtime environment, `AnyRef` corresponds to `java.lang.Object`. + +Here is an example that demonstrates that strings, integers, characters, boolean values, and functions are all objects just like every other object: + +```tut +val list: List[Any] = List( + "a string", + 732, // an integer + 'c', // a character + true, // a boolean value + () => "an anonymous function returning a string" +) + +list.foreach(element => println(element)) +``` + +It defines a variable `list` of type `List[Any]`. The list is initialized with elements of various types, but they all are instance of `scala.Any`, so you can add them to the list. + +Here is the output of the program: + +``` +a string +732 +c +true + +``` + +## Type Casting +Value types can be cast in the following way: +Scala Type Hierarchy + +For example: + +```tut +val x: Long = 987654321 +val y: Float = x // 9.8765434E8 (note that some precision is lost in this case) + +val face: Char = '☺' +val number: Int = face // 9786 +``` + +Casting is unidirectional. This will not compile: + +``` +val x: Long = 987654321 +val y: Float = x // 9.8765434E8 +val z: Long = y // Does not conform +``` + +You can also cast a reference type to a subtype. This will be covered later in the tour. + +## Nothing and Null +`Nothing` is a subtype of all types, also called the bottom type. There is no value that has type `Nothing`. A common use is to signal non-termination such as a thrown exception, program exit, or an infinite loop (i.e., it is the type of an expression which does not evaluate to a value, or a method that does not return normally). + +`Null` is a subtype of all reference types (i.e. any subtype of AnyRef). It has a single value identified by the keyword literal `null`. `Null` is provided mostly for interoperability with other JVM languages and should almost never be used in Scala code. We'll cover alternatives to `null` later in the tour. diff --git a/_tour/upper-type-bounds.md b/_tour/upper-type-bounds.md new file mode 100644 index 0000000000..b434279cd2 --- /dev/null +++ b/_tour/upper-type-bounds.md @@ -0,0 +1,49 @@ +--- +layout: tour +title: Upper Type Bounds + +discourse: true + +partof: scala-tour +categories: tour +num: 20 +next-page: lower-type-bounds +previous-page: variances +--- + +In Scala, [type parameters](generic-classes.html) and [abstract types](abstract-types.html) may be constrained by a type bound. Such type bounds limit the concrete values of the type variables and possibly reveal more information about the members of such types. An _upper type bound_ `T <: A` declares that type variable `T` refers to a subtype of type `A`. +Here is an example that demonstrates upper type bound for a type parameter of class `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 PetContainer[P <: Pet](p: P) { + def pet: P = p +} + +val dogContainer = new PetContainer[Dog](new Dog) +val catContainer = new PetContainer[Cat](new Cat) +// val lionContainer = new PetContainer[Lion](new Lion) +// ^this would not compile +``` +The `class PetContainer` take a type parameter `P` which must be a subtype of `Pet`. `Dog` and `Cat` are subtypes of `Pet` so we can create a new `PetContainer[Dog]` and `PetContainer[Cat]`. However, if we tried to create a `PetContainer[Lion]`, we would get the following Error: + +`type arguments [Lion] do not conform to class PetContainer's type parameter bounds [P <: Pet]` + +This is because `Lion` is not a subtype of `Pet`. diff --git a/_tour/variances.md b/_tour/variances.md new file mode 100644 index 0000000000..4e54f242e2 --- /dev/null +++ b/_tour/variances.md @@ -0,0 +1,152 @@ +--- +layout: tour +title: Variances + +discourse: true + +partof: scala-tour + +num: 19 +next-page: upper-type-bounds +previous-page: generic-classes +--- + +Variance is the correlation of subtyping relationships of complex types and the subtyping relationships of their component types. Scala supports variance annotations of type parameters of [generic classes](generic-classes.html), to allow them to be covariant, contravariant, or invariant if no annotations are used. The use of variance in the type system allows us to make intuitive connections between complex types, whereas the lack of variance can restrict the reuse of a class abstraction. + +```tut +class Foo[+A] // A covariant class +class Bar[-A] // A contravariant class +class Baz[A] // An invariant class +``` + +### Covariance + +A type parameter `A` of a generic class can be made covariant by using the annotation `+A`. For some `class List[+A]`, making `A` covariant implies that for two types `A` and `B` where `A` is a subtype of `B`, then `List[A]` is a subtype of `List[B]`. This allows us to make very useful and intuitive subtyping relationships using generics. + +Consider this simple class structure: + +```tut +abstract class Animal { + def name: String +} +case class Cat(name: String) extends Animal +case class Dog(name: String) extends Animal +``` + +Both `Cat` and `Dog` are subtypes of `Animal`. The Scala standard library has a generic immutable `sealed abstract class List[+A]` class, where the type parameter `A` is covariant. This means that a `List[Cat]` is a `List[Animal]` and a `List[Dog]` is also a `List[Animal]`. Intuitively, it makes sense that a list of cats and a list of dogs are each lists of animals, and you should be able to substitute either of them for a `List[Animal]`. + +In the following example, the method `printAnimalNames` will accept a list of animals as an argument and print their names each on a new line. If `List[A]` were not covariant, the last two method calls would not compile, which would severely limit the usefulness of the `printAnimalNames` method. + +```tut +object CovarianceTest extends App { + def printAnimalNames(animals: List[Animal]): Unit = { + animals.foreach { animal => + println(animal.name) + } + } + + val cats: List[Cat] = List(Cat("Whiskers"), Cat("Tom")) + val dogs: List[Dog] = List(Dog("Fido"), Dog("Rex")) + + printAnimalNames(cats) + // Whiskers + // Tom + + printAnimalNames(dogs) + // Fido + // Rex +} +``` + +### Contravariance + +A type parameter `A` of a generic class can be made contravariant by using the annotation `-A`. This creates a subtyping relationship between the class and its type parameter that is similar, but opposite to what we get with covariance. That is, for some `class Writer[-A]`, making `A` contravariant implies that for two types `A` and `B` where `A` is a subtype of `B`, `Writer[B]` is a subtype of `Writer[A]`. + +Consider the `Cat`, `Dog`, and `Animal` classes defined above for the following example: + +```tut +abstract class Printer[-A] { + def print(value: A): Unit +} +``` + +A `Printer[A]` is a simple class that knows how to print out some type `A`. Let's define some subclasses for specific types: + +```tut +class AnimalPrinter extends Printer[Animal] { + def print(animal: Animal): Unit = + println("The animal's name is: " + animal.name) +} + +class CatPrinter extends Printer[Cat] { + def print(cat: Cat): Unit = + println("The cat's name is: " + cat.name) +} +``` + +If a `Printer[Cat]` knows how to print any `Cat` to the console, and a `Printer[Animal]` knows how to print any `Animal` to the console, it makes sense that a `Printer[Animal]` would also know how to print any `Cat`. The inverse relationship does not apply, because a `Printer[Cat]` does not know how to print any `Animal` to the console. Therefore, we should be able to substitute a `Printer[Animal]` for a `Printer[Cat]`, if we wish, and making `Printer[A]` contravariant allows us to do exactly that. + +```tut +object ContravarianceTest extends App { + val myCat: Cat = Cat("Boots") + + def printMyCat(printer: Printer[Cat]): Unit = { + printer.print(myCat) + } + + val catPrinter: Printer[Cat] = new CatPrinter + val animalPrinter: Printer[Animal] = new AnimalPrinter + + printMyCat(catPrinter) + printMyCat(animalPrinter) +} +``` + +The output of this program will be: + +``` +The cat's name is: Boots +The animal's name is: Boots +``` + +### Invariance + +Generic classes in Scala are invariant by default. This means that they are neither covariant nor contravariant. In the context of the following example, `Container` class is invariant. A `Container[Cat]` is _not_ a `Container[Animal]`, nor is the reverse true. + +```tut +class Container[A](value: A) { + private var _value: A = value + def getValue: A = _value + def setValue(value: A): Unit = { + _value = value + } +} +``` + +It may seem like a `Container[Cat]` should naturally also be a `Container[Animal]`, but allowing a mutable generic class to be covariant would not be safe. In this example, it is very important that `Container` is invariant. Supposing `Container` was actually covariant, something like this could happen: + +``` +val catContainer: Container[Cat] = new Container(Cat("Felix")) +val animalContainer: Container[Animal] = catContainer +animalContainer.setValue(Dog("Spot")) +val cat: Cat = catContainer.getValue // Oops, we'd end up with a Dog assigned to a Cat +``` + +Fortunately, the compiler stops us long before we could get this far. + +### Other Examples + +Another example that can help one understand variance is `trait Function1[-T, R]` from the Scala standard library. `Function1` represents a function with one argument, where the first type parameter `T` represents the argument type, and the second type parameter `R` represents the return type. A `Function1` is contravariant over its argument type, and covariant over its return type. For this example we'll use the literal notation `A => B` to represent a `Function1[A, B]`. + +Assume the similar `Cat`, `Dog`, `Animal` inheritance tree used earlier, plus the following: + +```tut +class SmallAnimal +class Mouse extends SmallAnimal +``` + +Suppose we're working with functions that accept types of animals, and return the types of food they eat. If we would like a `Cat => SmallAnimal` (because cats eat small animals), but are given a `Animal => Mouse` instead, our program will still work. Intuitively an `Animal => Mouse` will still accept a `Cat` as an argument, because a `Cat` is an `Animal`, and it returns a `Mouse`, which is also an `SmallAnimal`. Since we can safely and invisibly substitute the former for the latter, we can say `Animal => Mouse` is a subtype of `Cat => SmallAnimal`. + +### Comparison With Other Languages + +Variance is supported in different ways by some languages that are similar to Scala. For example, variance annotations in Scala closely resemble those in C#, where the annotations are added when a class abstraction is defined (declaration-site variance). In Java, however, variance annotations are given by clients when a class abstraction is used (use-site variance). diff --git a/_zh-cn/cheatsheets/index.md b/_zh-cn/cheatsheets/index.md new file mode 100644 index 0000000000..0559d37acd --- /dev/null +++ b/_zh-cn/cheatsheets/index.md @@ -0,0 +1,89 @@ +--- +layout: cheatsheet +title: Scalacheat + +partof: cheatsheet + +by: Brendan O'Connor +about: 感谢 Brendan O'Connor, 本速查表可以用于快速地查找Scala语法结构。Licensed by Brendan O'Connor under a CC-BY-SA 3.0 license. + +language: "zh-cn" +--- + +###### Contributed by {{ page.by }} +{{ page.about }} + + +| 变量 | | +|  `var x = 5`                                                                                             | 可变量       | +| Good `val x = 5`
        Bad `x=6` | 常量 | +|  `var x: Double = 5`                                                                                     | 显式类型 | +| 函数 | | +|  Good `def f(x: Int) = { x*x }`
        Bad `def f(x: Int)   { x*x }` | 定义函数
        隐藏出错: 不加“=”号将会是一段返回Unit类型的过程,这将会导致意想不到的错误。 | +| Good `def f(x: Any) = println(x)`
        Bad `def f(x) = println(x)` | 定义函数
        语法错误: 每个参数都需要指定类型。 | +| `type R = Double` | 类型别名 | +|  `def f(x: R)` vs.
        `def f(x: => R)`                                                                 | 传值调用
        传名调用 (惰性参数) | +| `(x:R) => x*x` | 匿名函数 | +| `(1 to 5).map(_*2)` vs.
        `(1 to 5).reduceLeft( _+_ )` | 匿名函数: 下划线是arg参数的占位符。 | +| `(1 to 5).map( x => x*x )` | 匿名函数: 必须命名以后才可以使用一个arg参数两次。 | +|  Good `(1 to 5).map(2*)`
        Bad `(1 to 5).map(*2)` | 匿名函数: 绑定前缀方法,理智的方法是`2*_`。 | +|  `(1 to 5).map { x => val y=x*2; println(y); y }`                                                             | 匿名函数: 程序块风格,最后一个表达式作为返回值。 | +|  `(1 to 5) filter {_%2 == 0} map {_*2}`                                                                 | 匿名函数: 管道风格(或者用圆括号)。 | +|  `def compose(g:R=>R, h:R=>R) = (x:R) => g(h(x))`
        `val f = compose({_*2}, {_-1})`                   | 匿名函数: 要传入多个程序块的话,需要外围括号。 | +| `val zscore = (mean:R, sd:R) => (x:R) => (x-mean)/sd` | 柯里化, 显然的语法。 | +| `def zscore(mean:R, sd:R) = (x:R) => (x-mean)/sd` | 柯里化, 显然的语法。 | +|  `def zscore(mean:R, sd:R)(x:R) = (x-mean)/sd`                                                           | 柯里化,语法糖。然后:) | +|  `val normer = zscore(7, 0.4) _`                                                                         | 需要在尾部加下划线来变成偏函数(只对语法糖版本适用)。 | +| `def mapmake[T](g:T=>T)(seq: List[T]) = seq.map(g)` | 泛型 | +|  `5.+(3); 5 + 3`
        `(1 to 5) map (_*2)`                                                               | 中缀语法糖 | +| `def sum(args: Int*) = args.reduceLeft(_+_)` | 可变参数 | +| | | +| `import scala.collection._` | 通配符导入 | +| `import scala.collection.Vector`
        `import scala.collection.{Vector, Sequence}` | 选择性导入 | +| `import scala.collection.{Vector => Vec28}` | 重命名导入 | +| `import java.util.{Date => _, _}` | 导入java.util包里除Date之外的所有文件. | +|  `package pkg` _at start of file_
        `package pkg { ... }`                                             | 声明这是一个包 | +| 数据结构 | | +|  `(1,2,3)`                                                                                               | 元组字面量 (`Tuple3`) | +| `var (x,y,z) = (1,2,3)` | 解构绑定:通过模式匹配来解构元组。 | +|  Bad`var x,y,z = (1,2,3)`                                           | 隐藏出错:每一个变量都被赋值了整个元组。 | +| `var xs = List(1,2,3)` | 列表 (不可变). | +| `xs(2)` | 用括号索引 ([slides](http://www.slideshare.net/Odersky/fosdem-2009-1013261/27)) | +|  `1 :: List(2,3)`                                                                                       | Cons(构成) | +|  `1 to 5` _等价于_ `1 until 6`
        `1 to 10 by 2`                                                     | Range类型(语法糖) | +|  `()` _(空括号)_                                                                                   | Unit类型的唯一成员 (相当于 C/Java 里的void). | +| 控制结构 | | +| `if (check) happy else sad` | 条件 | +| `if (check) happy` _same as_
        `if (check) happy else ()` | 条件(语法糖) | +| `while (x < 5) { println(x); x += 1}` | while循环 | +| `do { println(x); x += 1} while (x < 5)` | do while循环 | +| `import scala.util.control.Breaks._`
        `breakable {`
        ` for (x <- xs) {`
        ` if (Math.random < 0.1) break`
        ` }`
        `}`| break. ([slides](http://www.slideshare.net/Odersky/fosdem-2009-1013261/21)) | +|  `for (x <- xs if x%2 == 0) yield x*10` _等价于_
        `xs.filter(_%2 == 0).map(_*10)`                   | for comprehension (for 导出): filter/map | +|  `for ((x,y) <- xs zip ys) yield x*y` _等价于_
        `(xs zip ys) map { case (x,y) => x*y }`             | for comprehension (for 导出): 解构绑定 | +| `for (x <- xs; y <- ys) yield x*y` _等价于_
        `xs flatMap {x => ys map {y => x*y}}` | for comprehension (for 导出): 叉乘 | +|  `for (x <- xs; y <- ys) {`
           `println("%d/%d = %.1f".format(x, y, x/y.toFloat))`
        `}`                     | for comprehension (for 导出): 不可避免的格式
        [sprintf-style](http://java.sun.com/javase/6/docs/api/java/util/Formatter.html#syntax) | +| `for (i <- 1 to 5) {`
        `println(i)`
        `}` | for comprehension (for 导出): 包括上边界的遍历 | +| `for (i <- 1 until 5) {`
        `println(i)`
        `}` | for comprehension (for 导出): 忽略上边界的遍历 | +| 模式匹配 | | +|  Good `(xs zip ys) map { case (x,y) => x*y }`
        Bad `(xs zip ys) map( (x,y) => x*y )` | 在函数的参数中使用模式匹配的例子。 | +| Bad
        `val v42 = 42`
        `Some(3) match {`
        ` case Some(v42) => println("42")`
        ` case _ => println("Not 42")`
        `}` | "v42" 被解释为可以匹配任何Int类型值的名称,打印输出"42"。 | +|  Good
        `val v42 = 42`
        `Some(3) match {`
        ``   case Some(`v42`) => println("42")``
        `case _ => println("Not 42")`
        `}` | 有反引号的 "\`v42\`" 被解释为已经存在的val `v42`,所以输出的是 "Not 42". | +|  Good
        `val UppercaseVal = 42`
        `Some(3) match {`
        ` case Some(UppercaseVal) => println("42")`
        `   case _ => println("Not 42")`
        `}` |  `UppercaseVal` 被视作已经存在的 val,而不是一个新的模式变量,因为它是以大写字母开头的,所以`UppercaseVal` 所包含的值(42)和检查的值(3)不匹配,输出"Not 42"。| +| 面向对象 | | +| `class C(x: R)` _same as_
        `class C(private val x: R)`
        `var c = new C(4)` | 构造器参数 - 私有 | +| `class C(val x: R)`
        `var c = new C(4)`
        `c.x` | 构造器参数 - 公有 | +|  `class C(var x: R) {`
        `assert(x > 0, "positive please")`
        `var y = x`
        `val readonly = 5`
        `private var secret = 1`
        `def this = this(42)`
        `}`|
        构造函数就是类的主体
        声明一个公有成员变量
        声明一个可get但不可set的成员变量
        声明一个私有变量
        可选构造器| +| `new{ ... }` | 匿名类 | +| `abstract class D { ... }` | 定义一个抽象类。(不可创建) | +| `class C extends D { ... }` | 定义一个继承子类。 | +| `class D(var x: R)`
        `class C(x: R) extends D(x)` | 继承与构造器参数(愿望清单: 默认自动传参) +|  `object O extends D { ... }`                                                                           | 定义一个单例(和模块一样) | +|  `trait T { ... }`
        `class C extends T { ... }`
        `class C extends D with T { ... }`                 | 特质
        带有实现的接口,没有构造参数。 [mixin-able]({{ site.baseurl }}/tutorials/tour/mixin-class-composition.html). +|  `trait T1; trait T2`
        `class C extends T1 with T2`
        `class C extends D with T1 with T2`             | (混入)多个特质 | +|  `class C extends D { override def f = ...}`                                                           | 必须声明覆盖该方法。 | +|  `new java.io.File("f")`                                                                             | 创建对象。 | +|  Bad `new List[Int]`
        Good `List(1,2,3)` | 类型错误: 抽象类型
        相反,习惯上:调用工厂(方法)会自动推测类型 | +| `classOf[String]` | 类字面量 | +| `x.isInstanceOf[String]` | 类型检查 (运行时) | +| `x.asInstanceOf[String]` | 类型强制转换 (运行时) | +| `x: String` | 归属 (编译时) | diff --git a/_zh-cn/overviews/collections/arrays.md b/_zh-cn/overviews/collections/arrays.md new file mode 100644 index 0000000000..348f8d14c8 --- /dev/null +++ b/_zh-cn/overviews/collections/arrays.md @@ -0,0 +1,120 @@ +--- +layout: multipage-overview +title: 数组 + +discourse: false + +partof: collections +overview-name: Collections + +num: 10 +language: zh-cn +--- + +在Scala中,[数组](http://www.scala-lang.org/api/2.10.0/scala/Array.html)是一种特殊的collection。一方面,Scala数组与Java数组是一一对应的。即Scala数组Array[Int]可看作Java的Int[],Array[Double]可看作Java的double[],以及Array[String]可看作Java的String[]。但Scala数组比Java数组提供了更多内容。首先,Scala数组是一种泛型。即可以定义一个Array[T],T可以是一种类型参数或抽象类型。其次,Scala数组与Scala序列是兼容的 - 在需要Seq[T]的地方可由Array[T]代替。最后,Scala数组支持所有的序列操作。这里有个实际的例子: + + scala> val a1 = Array(1, 2, 3) + a1: Array[Int] = Array(1, 2, 3) + scala> val a2 = a1 map (_ * 3) + a2: Array[Int] = Array(3, 6, 9) + scala> val a3 = a2 filter (_ % 2 != 0) + a3: Array[Int] = Array(3, 9) + scala> a3.reverse + res0: Array[Int] = Array(9, 3) + +既然Scala数组表现的如同Java的数组,那么Scala数组这些额外的特性是如何运作的呢?实际上,Scala 2.8与早期版本在这个问题的处理上有所不同。早期版本中执行打包/解包过程时,Scala编译器做了一些“神奇”的包装/解包的操作,进行数组与序列对象之间互转。其中涉及到的细节相当复杂,尤其是创建一个新的泛型类型数组Array[T]时。一些让人迷惑的罕见实例以及数组操作的性能都是不可预测的。 + +Scala 2.8设计要简单得多,其数组实现系统地使用隐式转换,从而基本去除了编译器的特殊处理。Scala 2.8中数组不再看作序列,因为本地数组的类型不是Seq的子类型。而是在数组和 `scala.collection.mutable.WrappedArray`这个类的实例之间隐式转换,后者则是Seq的子类。这里有个例子: + + scala> val seq: Seq[Int] = a1 + seq: Seq[Int] = WrappedArray(1, 2, 3) + scala> val a4: Array[Int] = s.toArray + a4: Array[Int] = Array(1, 2, 3) + scala> a1 eq a4 + res1: Boolean = true + +上面的例子说明数组与序列是兼容的,因为数组可以隐式转换为WrappedArray。反之可以使用Traversable提供的toArray方法将WrappedArray转换为数组。REPL最后一行表明,隐式转换与toArray方法作用相互抵消。 + +数组还有另外一种隐式转换,不需要将数组转换成序列,而是简单地把所有序列的方法“添加”给数组。“添加”其实是将数组封装到一个ArrayOps类型的对象中,后者支持所有序列的方法。ArrayOps对象的生命周期通常很短暂,不调用序列方法的时候基本不会用到,其内存也可以回收。现代虚拟机一般不会创建这个对象。 + +在接下来REPL中展示数组的这两种隐式转换的区别: + + scala> val seq: Seq[Int] = a1 + seq: Seq[Int] = WrappedArray(1, 2, 3) + scala> seq.reverse + res2: Seq[Int] = WrappedArray(3, 2, 1) + scala> val ops: collection.mutable.ArrayOps[Int] = a1 + ops: scala.collection.mutable.ArrayOps[Int] = [I(1, 2, 3) + scala> ops.reverse + res3: Array[Int] = Array(3, 2, 1) + +注意seq是一个WrappedArray,seq调用reverse方法也会得到一个WrappedArray。这是没问题的,因为封装的数组就是Seq,在任意Seq上调用reverse方法都会得到Seq。反之,变量ops属于ArrayOps这个类,对其调用reverse方法得到一个数组,而不是Seq。 + +上例直接使用ArrayOps仅为了展示其与WrappedArray的区别,这种用法非常不自然。一般情况下永远不要实例化一个ArrayOps,而是在数组上调用Seq的方法: + + scala> a1.reverse + res4: Array[Int] = Array(3, 2, 1) + +ArrayOps的对象会通过隐式转换自动的插入,因此上述的代码等价于 + + scala> intArrayOps(a1).reverse + res5: Array[Int] = Array(3, 2, 1) + +这里的intArrayOps就是之前例子中插入的隐式转换。这里引出一个疑问,上面代码中,编译器为何选择了intArrayOps而不是WrappedArray做隐式转换?毕竟,两种转换都是将数组映射到支持reverse方法的类型,并且指定输入。答案是两种转换是有优先级次序的,ArrayOps转换比WrappedArray有更高的优先级。前者定义在Predef对象中,而后者定义在继承自Predef的`scala.LowPriorityImplicits`类中。子类、子对象中隐式转换的优先级低于基类。所以如果两种转换都可用,Predef中的会优先选取。字符串的情况也是如此。 + +数组与序列兼容,并支持所有序列操作的方法,你现在应该已经了然于胸。那泛型呢?在Java中你不可以定义一个以T为类型参数的`T[]`。那么Scala的`Array[T]`是如何做的呢?事实上一个像`Array[T] `的泛型数组在运行时态可任意为Java的八个原始数组类型像`byte[]`, `short[]`, `char[]`, `int[]`, `long[]`, `float[]`, `double[]`, `boolean[]`,甚至它可以是一个对象数组。最常见的运行时态类型是AnyRef ,它包括了所有的这些类型(相当于java.lang.Object),因此这样的类型可以通过Scala编译器映射到`Array[T]`.在运行时,当`Array[T]`类型的数组元素被访问或更新时,就会有一个序列的类型测试用于确定真正的数组类型,随后就是java中的正确的数组操作。这些类型测试会影响数组操作的效率。这意味着如果你需要更大的性能,你应该更喜欢具体而明确的泛型数组。代表通用的泛型数组是不够的,因此,也必然有一种方式去创造泛型数组。这是一个更难的问题,需要一点点的帮助你。为了说明这个问题,考虑下面用一个通用的方法去创造数组的尝试。 + + //这是错的! + def evenElems[T](xs: Vector[T]): Array[T] = { + val arr = new Array[T]((xs.length + 1) / 2) + for (i <- 0 until xs.length by 2) + arr(i / 2) = xs(i) + arr + } + +evenElems方法返回一个新数组,该数组包含了参数向量xs的所有元素甚至在向量中的位置。evenElems 主体的第一行构建了结果数组,将相同元素类型作为参数。所以根据T的实际类型参数,这可能是一个`Array[Int]`,或者是一个`Array[Boolean]`,或者是一个在java中有一些其他基本类型的数组,或者是一个有引用类型的数组。但是这些类型有不同的运行时表达,那么Scala如何在运行时选择正确的呢?事实上,它不是基于信息传递做的,因为与类型参数T相对应的实际类型在运行时已被抹去。这就是为什么你在编译上面的代码时会出现如下的错误信息: + + error: cannot find class manifest for element type T + val arr = new Array[T]((arr.length + 1) / 2) + ^ + +这里需要你做的就是通过提供一些运行时的实际元素类型参数的线索来帮助编译器处理。这个运行时的提示采取的形式是一个`scala.reflect.ClassManifest`类型的类声明。一个类声明就是一个类型描述对象,给对象描述了一个类型的顶层类。另外,类声明也有`scala.reflect.Manifest`类型的所有声明,它描述了类型的各个方面。但对于数组创建而言,只需要提供类声明。 + +如果你指示编译器那么做它就会自动的构建类声明。“指示”意味着你决定一个类声明作为隐式参数,像这样: + + def evenElems[T](xs: Vector[T])(implicit m: ClassManifest[T]): Array[T] = ... + +使用一个替换和较短的语法。通过用一个上下文绑定你也可以要求类型与一个类声明一起。这种方式是跟在一个冒号类型和类名为ClassManifest的后面,想这样: + + // this works + def evenElems[T: ClassManifest](xs: Vector[T]): Array[T] = { + val arr = new Array[T]((xs.length + 1) / 2) + for (i <- 0 until xs.length by 2) + arr(i / 2) = xs(i) + arr + } + +这两个evenElems的修订版本意思是完全相同的。当Array[T] 构造时,在任何情况下会发生的是,编译器会寻找类型参数T的一个类声明,这就是说,它会寻找ClassManifest[T]一个隐式类型的值。如果如此的一个值被发现,声明会用来构造正确的数组类型。否则,你就会看到一个错误信息像上面一样。 + +下面是一些使用evenElems 方法的REPL 交互。 + + scala> evenElems(Vector(1, 2, 3, 4, 5)) + res6: Array[Int] = Array(1, 3, 5) + scala> evenElems(Vector("this", "is", "a", "test", "run")) + res7: Array[java.lang.String] = Array(this, a, run) + +在这两种情况下,Scala编译器自动的为元素类型构建一个类声明(首先,Int,然后String)并且通过它传递evenElems 方法的隐式参数。编译器可以对所有的具体类型构造,但如果论点本身是另一个没有类声明的类型参数就不可以。例如,下面的错误: + + scala> def wrap[U](xs: Vector[U]) = evenElems(xs) + :6: error: No ClassManifest available for U. + def wrap[U](xs: Vector[U]) = evenElems(xs) + ^ + +这里所发生的是,evenElems 需要一个类型参数U的类声明,但是没有发现。这种情况下的解决方案是,当然,是为了U的另一个隐式类声明。所以下面起作用了: + + scala> def wrap[U: ClassManifest](xs: Vector[U]) = evenElems(xs) + wrap: [U](xs: Vector[U])(implicit evidence$1: ClassManifest[U])Array[U] + +这个实例还显示在定义U的上下文绑定里这仅是一个简短的隐式参数命名为`ClassManifest[U]`类型的`evidence$1`。 + +总结,泛型数组创建需要类声明。所以每当创建一个类型参数T的数组,你还需要提供一个T的隐式类声明。最简单的方法是声明类型参数与ClassManifest的上下文绑定,如 `[T: ClassManifest]`。 diff --git a/_zh-cn/overviews/collections/concrete-immutable-collection-classes.md b/_zh-cn/overviews/collections/concrete-immutable-collection-classes.md new file mode 100644 index 0000000000..f7ae54da96 --- /dev/null +++ b/_zh-cn/overviews/collections/concrete-immutable-collection-classes.md @@ -0,0 +1,190 @@ +--- +layout: multipage-overview +title: 具体的不可变集实体类 + +discourse: false + +partof: collections +overview-name: Collections + +num: 8 +language: zh-cn +--- + + +Scala中提供了多种具体的不可变集类供你选择,这些类(maps, sets, sequences)实现的接口(traits)不同,比如是否能够是无限(infinite)的,各种操作的速度也不一样。下面的篇幅介绍几种Scala中最常用的不可变集类型。 + +## List(列表) + +列表[List](http://www.scala-lang.org/api/2.10.0/scala/collection/immutable/List.html)是一种有限的不可变序列式。提供了常数时间的访问列表头元素和列表尾的操作,并且提供了常数时间的构造新链表的操作,该操作将一个新的元素插入到列表的头部。其他许多操作则和列表的长度成线性关系。 + +List通常被认为是Scala中最重要的数据结构,所以我们在此不必过于赘述。版本2.8中主要的变化是,List类和其子类::以及其子对象Nil都被定义在了其逻辑上所属的scala.collection.immutable包里。scala包中仍然保留了List,Nil和::的别名,所以对于用户来说可以像原来一样访问List。 + +另一个主要的变化是,List现在更加紧密的融入了Collections Framework中,而不是像过去那样更像一个特例。比如说,大量原本存在于与List相关的对象的方法基本上全部都过时(deprecated)了,取而代之的是被每种Collection所继承的统一的构造方法。 + +## Stream(流) + +流[Stream](http://www.scala-lang.org/api/2.10.0/scala/collection/immutable/Stream.html)与List很相似,只不过其中的每一个元素都经过了一些简单的计算处理。也正是因为如此,stream结构可以无限长。只有那些被要求的元素才会经过计算处理,除此以外stream结构的性能特性与List基本相同。 + +鉴于List通常使用 `:: `运算符来进行构造,stream使用外观上很相像的`#::`。这里用一个包含整数1,2和3的stream来做一个简单的例子: + + scala> val str = 1 #:: 2 #:: 3 #:: Stream.empty + str: scala.collection.immutable.Stream[Int] = Stream(1, ?) + +该stream的头结点是1,尾是2和3.尾部并没有被打印出来,因为还没有被计算。stream被特别定义为懒惰计算,并且stream的toString方法很谨慎的设计为不去做任何额外的计算。 + +下面给出一个稍复杂些的例子。这里讲一个以两个给定的数字为起始的斐波那契数列转换成stream。斐波那契数列的定义是,序列中的每个元素等于序列中在它之前的两个元素之和。 + + scala> def fibFrom(a: Int, b: Int): Stream[Int] = a #:: fibFrom(b, a + b) + fibFrom: (a: Int,b: Int)Stream[Int] + +这个函数看起来比较简单。序列中的第一个元素显然是a,其余部分是以b和位于其后的a+b为开始斐波那契数列。这段程序最大的亮点是在对序列进行计算的时候避免了无限递归。如果函数中使用`::`来替换`#::`,那么之后的每次调用都会产生另一次新的调用,从而导致无限递归。在此例中,由于使用了`#::`,等式右值中的调用在需要求值之前都不会被展开。这里尝试着打印出以1,1开头的斐波那契数列的前几个元素: + + scala> val fibs = fibFrom(1, 1).take(7) + fibs: scala.collection.immutable.Stream[Int] = Stream(1, ?) + scala> fibs.toList + res9: List[Int] = List(1, 1, 2, 3, 5, 8, 13) + +## Vector(向量) + +对于只需要处理数据结构头结点的算法来说,List非常高效。可是相对于访问、添加和删除List头结点只需要固定时间,访问和修改头结点之后元素所需要的时间则是与List深度线性相关的。 + +向量[Vector](http://www.scala-lang.org/api/2.10.0/scala/collection/immutable/Vector.html)是用来解决列表(list)不能高效的随机访问的一种结构。Vector结构能够在“更高效”的固定时间内访问到列表中的任意元素。虽然这个时间会比访问头结点或者访问某数组元素所需的时间长一些,但至少这个时间也是个常量。因此,使用Vector的算法不必仅是小心的处理数据结构的头结点。由于可以快速修改和访问任意位置的元素,所以对Vector结构做写操作很方便。 + +Vector类型的构建和修改与其他的序列结构基本一样。 + + scala> val vec = scala.collection.immutable.Vector.empty + vec: scala.collection.immutable.Vector[Nothing] = Vector() + scala> val vec2 = vec :+ 1 :+ 2 + vec2: scala.collection.immutable.Vector[Int] = Vector(1, 2) + scala> val vec3 = 100 +: vec2 + vec3: scala.collection.immutable.Vector[Int] = Vector(100, 1, 2) + scala> vec3(0) + res1: Int = 100 + +Vector结构通常被表示成具有高分支因子的树(树或者图的分支因子是指数据结构中每个节点的子节点数目)。每一个树节点包含最多32个vector元素或者至多32个子树节点。包含最多32个元素的vector可以表示为一个单一节点,而一个间接引用则可以用来表示一个包含至多`32*32=1024`个元素的vector。从树的根节点经过两跳到达叶节点足够存下有2的15次方个元素的vector结构,经过3跳可以存2的20次方个,4跳2的25次方个,5跳2的30次方个。所以对于一般大小的vector数据结构,一般经过至多5次数组访问就可以访问到指定的元素。这也就是我们之前所提及的随机数据访问时“运行时间的相对高效”。 + +由于Vectors结构是不可变的,所以您不能通过修改vector中元素的方法来返回一个新的vector。尽管如此,您仍可以通过update方法从一个单独的元素中创建出区别于给定数据结构的新vector结构: + + scala> val vec = Vector(1, 2, 3) + vec: scala.collection.immutable.Vector[Int] = Vector(1, 2, 3) + scala> vec updated (2, 4) + res0: scala.collection.immutable.Vector[Int] = Vector(1, 2, 4) + scala> vec + res1: scala.collection.immutable.Vector[Int] = Vector(1, 2, 3) + +从上面例子的最后一行我们可以看出,update方法的调用并不会改变vec的原始值。与元素访问类似,vector的update方法的运行时间也是“相对高效的固定时间”。对vector中的某一元素进行update操作可以通过从树的根节点开始拷贝该节点以及每一个指向该节点的节点中的元素来实现。这就意味着一次update操作能够创建1到5个包含至多32个元素或者子树的树节点。当然,这样做会比就地更新一个可变数组败家很多,但比起拷贝整个vector结构还是绿色环保了不少。 + +由于vector在快速随机选择和快速随机更新的性能方面做到很好的平衡,所以它目前正被用作不可变索引序列的默认实现方式。 + + scala> collection.immutable.IndexedSeq(1, 2, 3) + res2: scala.collection.immutable.IndexedSeq[Int] = Vector(1, 2, 3) + +## Immutable stacks(不可变栈) + +如果您想要实现一个后入先出的序列,那您可以使用[Stack](http://www.scala-lang.org/api/2.10.0/scala/collection/immutable/Stack.html)。您可以使用push向栈中压入一个元素,用pop从栈中弹出一个元素,用top查看栈顶元素而不用删除它。所有的这些操作都仅仅耗费固定的运行时间。 + +这里提供几个简单的stack操作的例子: + + scala> val stack = scala.collection.immutable.Stack.empty + stack: scala.collection.immutable.Stack[Nothing] = Stack() + scala> val hasOne = stack.push(1) + hasOne: scala.collection.immutable.Stack[Int] = Stack(1) + scala> stack + stack: scala.collection.immutable.Stack[Nothing] = Stack() + scala> hasOne.top + res20: Int = 1 + scala> hasOne.pop + res19: scala.collection.immutable.Stack[Int] = Stack() + +不可变stack一般很少用在Scala编程中,因为List结构已经能够覆盖到它的功能:push操作同List中的::基本相同,pop则对应着tail。 + +## Immutable Queues(不可变队列) + +[Queue](http://www.scala-lang.org/api/2.10.0/scala/collection/immutable/Queue.html)是一种与stack很相似的数据结构,除了与stack的后入先出不同,Queue结构的是先入先出的。 + +这里给出一个创建空不可变queue的例子: + + scala> val empty = scala.collection.immutable.Queue[Int]() + empty: scala.collection.immutable.Queue[Int] = Queue() + +您可以使用enqueue方法在不可变Queue中加入一个元素: + + scala> val has1 = empty.enqueue(1) + has1: scala.collection.immutable.Queue[Int] = Queue(1) + +如果想要在queue中添加多个元素需要在调用enqueue方法时用一个collection对象作为参数: + + scala> val has123 = has1.enqueue(List(2, 3)) + has123: scala.collection.immutable.Queue[Int] + = Queue(1, 2, 3) + +如果想要从queue的头部删除一个元素,您可以使用dequeue方法: + + scala> val (element, has23) = has123.dequeue + element: Int = 1 + has23: scala.collection.immutable.Queue[Int] = Queue(2, 3) + +请注意,dequeue方法将会返回两个值,包括被删除掉的元素和queue中剩下的部分。 + +## Ranges (等差数列) + +[Range]表示的是一个有序的等差整数数列。比如说,“1,2,3,”就是一个Range,“5,8,11,14,”也是。在Scala中创建一个Range类,需要用到两个预定义的方法to和by。 + + scala> 1 to 3 + res2: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3) + scala> 5 to 14 by 3 + res3: scala.collection.immutable.Range = Range(5, 8, 11, 14) + +如果您想创建一个不包含范围上限的Range类,那么用until方法代替to更为方便: + + scala> 1 until 3 + res2: scala.collection.immutable.Range = Range(1, 2) + +Range类的空间复杂度是恒定的,因为只需要三个数字就可以定义一个Range类:起始、结束和步长值。也正是因为有这样的特性,对Range类多数操作都非常非常的快。 + +## Hash Tries + +Hash try是高效实现不可变集合和关联数组(maps)的标准方法,[immutable.HashMap](http://www.scala-lang.org/api/2.10.0/scala/collection/immutable/HashMap.html)类提供了对Hash Try的支持。从表现形式上看,Hash Try和Vector比较相似,都是树结构,且每个节点包含32个元素或32个子树,差别只是用不同的hash code替换了指向各个节点的向量值。举个例子吧:当您要在一个映射表里找一个关键字,首先需要用hash code值替换掉之前的向量值;然后用hash code的最后5个bit找到第一层子树,然后每5个bit找到下一层子树。当存储在一个节点中所有元素的代表他们当前所在层的hash code位都不相同时,查找结束。 + +Hash Try对于快速查找和函数式的高效添加和删除操作上取得了很好的平衡,这也是Scala中不可变映射和集合采用Hash Try作为默认实现方式的原因。事实上,Scala对于大小小于5的不可变集合和映射做了更进一步的优化。只有1到4个元素的集合和映射被在现场会被存储在一个单独仅仅包含这些元素(对于映射则只是包含键值对)的对象中。空集合和空映射则视情况不同作为一个单独的对象,空的一般情况下就会一直空下去,所以也没有必要为他们复制一份拷贝。 + +## Red-Black Trees(红黑树) + +红黑树是一种平衡二叉树,树中一些节点被设计成红节点,其余的作为黑节点。同任何平衡二叉树一样,对红黑树的最长运行时间随树的节点数成对数(logarithmic)增长。 + +Scala隐含的提供了不可变集合和映射的红黑树实现,您可以在[TreeSet](http://www.scala-lang.org/api/2.10.0/scala/collection/immutable/TreeSet.html)和[TreeMap](http://www.scala-lang.org/api/2.10.0/scala/collection/immutable/TreeMap.html)下使用这些方法。 + + ## scala> scala.collection.immutable.TreeSet.empty[Int] + res11: scala.collection.immutable.TreeSet[Int] = TreeSet() + scala> res11 + 1 + 3 + 3 + res12: scala.collection.immutable.TreeSet[Int] = TreeSet(1, 3) + +红黑树在Scala中被作为SortedSet的标准实现,因为它提供了一个高效的迭代器,可以用来按照拍好的序列返回所有的元素。 + +## Immutable BitSets(不可变位集合) + +[BitSet](http://www.scala-lang.org/api/2.10.0/scala/collection/immutable/BitSet.html)代表一个由小整数构成的容器,这些小整数的值表示了一个大整数被置1的各个位。比如说,一个包含3、2和0的bit集合可以用来表示二进制数1101和十进制数13. + +BitSet内部的使用了一个64位long型的数组。数组中的第一个long表示整数0到63,第二个表示64到27,以此类推。所以只要集合中最大的整数在千以内BitSet的压缩率都是相当高的。 + +BitSet操作的运行时间是非常快的。查找测试仅仅需要固定时间。向集合内增加一个项所需时间同BitSet数组中long型的个数成正比,但这也通常是个非常小的值。这里有几个关于BitSet用法的例子: + + scala> val bits = scala.collection.immutable.BitSet.empty + bits: scala.collection.immutable.BitSet = BitSet() + scala> val moreBits = bits + 3 + 4 + 4 + moreBits: scala.collection.immutable.BitSet = BitSet(3, 4) + scala> moreBits(3) + res26: Boolean = true + scala> moreBits(0) + res27: Boolean = false + +## List Maps + +[ListMap](http://www.scala-lang.org/api/2.10.0/scala/collection/immutable/ListMap.html)被用来表示一个保存键-值映射的链表。一般情况下,ListMap操作都需要遍历整个列表,所以操作的运行时间也同列表长度成线性关系。实际上ListMap在Scala中很少使用,因为标准的不可变映射通常速度会更快。唯一的例外是,在构造映射时由于某种原因,链表中靠前的元素被访问的频率大大高于其他的元素。 + + scala> val map = scala.collection.immutable.ListMap(1->"one", 2->"two") + map: scala.collection.immutable.ListMap[Int,java.lang.String] = + Map(1 -> one, 2 -> two) + scala> map(2) + res30: String = "two" diff --git a/_zh-cn/overviews/collections/concrete-mutable-collection-classes.md b/_zh-cn/overviews/collections/concrete-mutable-collection-classes.md new file mode 100644 index 0000000000..5e0b578dcf --- /dev/null +++ b/_zh-cn/overviews/collections/concrete-mutable-collection-classes.md @@ -0,0 +1,171 @@ +--- +layout: multipage-overview +title: 具体的可变容器类 + +discourse: false + +partof: collections +overview-name: Collections + +num: 9 +language: zh-cn +--- + + +目前你已经看过了Scala的不可变容器类,这些是标准库中最常用的。现在来看一下可变容器类。 + +## Array Buffers + +一个[ArrayBuffer](http://www.scala-lang.org/api/2.10.0/scala/collection/mutable/ArrayBuffer.html)缓冲包含数组和数组的大小。对数组缓冲的大多数操作,其速度与数组本身无异。因为这些操作直接访问、修改底层数组。另外,数组缓冲可以进行高效的尾插数据。追加操作均摊下来只需常量时间。因此,数组缓冲可以高效的建立一个有大量数据的容器,无论是否总有数据追加到尾部。 + + scala> val buf = scala.collection.mutable.ArrayBuffer.empty[Int] + buf: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer() + scala> buf += 1 + res32: buf.type = ArrayBuffer(1) + scala> buf += 10 + res33: buf.type = ArrayBuffer(1, 10) + scala> buf.toArray + res34: Array[Int] = Array(1, 10) + +## List Buffers + +[ListBuffer](http://www.scala-lang.org/api/2.10.0/scala/collection/mutable/ListBuffer.html) 类似于数组缓冲。区别在于前者内部实现是链表, 而非数组。如果你想把构造完的缓冲转换为列表,那就用列表缓冲,别用数组缓冲。 + + scala> val buf = scala.collection.mutable.ListBuffer.empty[Int] + buf: scala.collection.mutable.ListBuffer[Int] = ListBuffer() + scala> buf += 1 + res35: buf.type = ListBuffer(1) + scala> buf += 10 + res36: buf.type = ListBuffer(1, 10) + scala> buf.toList + res37: List[Int] = List(1, 10) + +## StringBuilders + +数组缓冲用来构建数组,列表缓冲用来创建列表。类似地,[StringBuilder](http://www.scala-lang.org/api/2.10.0/scala/collection/mutable/StringBuilder.html) 用来构造字符串。作为常用的类,字符串构造器已导入到默认的命名空间。直接用 new StringBuilder就可创建字符串构造器 ,像这样: + + scala> val buf = new StringBuilder + buf: StringBuilder = + scala> buf += 'a' + res38: buf.type = a + scala> buf ++= "bcdef" + res39: buf.type = abcdef + scala> buf.toString + res41: String = abcdef + +## 链表 + +链表是可变序列,它由一个个使用next指针进行链接的节点构成。它们的支持类是[LinkedList](http://www.scala-lang.org/api/2.10.0/scala/collection/mutable/LinkedList.html)。在大多数的编程语言中,null可以表示一个空链表,但是在Scalable集合中不是这样。因为就算是空的序列,也必须支持所有的序列方法。尤其是 `LinkedList.empty.isEmpty` 必须返回`true`,而不是抛出一个 `NullPointerException` 。空链表用一种特殊的方式编译: + +它们的 next 字段指向它自身。链表像他们的不可变对象一样,是最佳的顺序遍历序列。此外,链表可以很容易去插入一个元素或链接到另一个链表。 + +## 双向链表 + +双向链表和单向链表相似,只不过它们除了具有 next字段外,还有一个可变字段 prev用来指向当前节点的上一个元素 。这个多出的链接的好处主要在于可以快速的移除元素。双向链表的支持类是[DoubleLinkedList](http://www.scala-lang.org/api/2.10.0/scala/collection/mutable/DoubleLinkedList.html). + +## 可变列表 + +[MutableList](http://www.scala-lang.org/api/2.10.0/scala/collection/mutable/MutableList.html) 由一个单向链表和一个指向该链表终端空节点的指针构成。因为避免了贯穿整个列表去遍历搜索它的终端节点,这就使得列表压缩了操作所用的时间。MutableList 目前是Scala中[mutable.LinearSeq](http://www.scala-lang.org/api/2.10.0/scala/collection/LinearSeq.html) 的标准实现。 + +## 队列 + +Scala除了提供了不可变队列之外,还提供了可变队列。你可以像使用一个不可变队列一样地使用一个可变队列,但你需要使用+= 和++=操作符进行添加的方式来替代排队方法。 +当然,在一个可变队列中,出队方法将只移除头元素并返回该队列。这里是一个例子: + + scala> val queue = new scala.collection.mutable.Queue[String] + queue: scala.collection.mutable.Queue[String] = Queue() + scala> queue += "a" + res10: queue.type = Queue(a) + scala> queue ++= List("b", "c") + res11: queue.type = Queue(a, b, c) + scala> queue + res12: scala.collection.mutable.Queue[String] = Queue(a, b, c) + scala> queue.dequeue + res13: String = a + scala> queue + res14: scala.collection.mutable.Queue[String] = Queue(b, c) + +## 数组序列 + +Array Sequences 是具有固定大小的可变序列。在它的内部,用一个 `Array[Object]`来存储元素。在Scala 中,[ArraySeq](http://www.scala-lang.org/api/2.10.0/scala/collection/mutable/ArraySeq.html) 是它的实现类。 + +如果你想拥有 Array 的性能特点,又想建立一个泛型序列实例,但是你又不知道其元素的类型,在运行阶段也无法提供一个`ClassManifest` ,那么你通常可以使用 `ArraySeq` 。这些问题在[arrays](http://docs.scala-lang.org/overviews/collections/arrays.html)一节中有详细的说明。 + +## 堆栈 + +你已经在前面看过了不可变栈。还有一个可变栈,支持类是[mutable.Stack](http://www.scala-lang.org/api/2.10.0/scala/collection/mutable/Stack.html)。它的工作方式与不可变栈相同,只是适当的做了修改。 + + scala> val stack = new scala.collection.mutable.Stack[Int] + stack: scala.collection.mutable.Stack[Int] = Stack() + scala> stack.push(1) + res0: stack.type = Stack(1) + scala> stack + res1: scala.collection.mutable.Stack[Int] = Stack(1) + scala> stack.push(2) + res0: stack.type = Stack(1, 2) + scala> stack + res3: scala.collection.mutable.Stack[Int] = Stack(1, 2) + scala> stack.top + res8: Int = 2 + scala> stack + res9: scala.collection.mutable.Stack[Int] = Stack(1, 2) + scala> stack.pop + res10: Int = 2 + scala> stack + res11: scala.collection.mutable.Stack[Int] = Stack(1) + +## 数组堆栈 + +[ArrayStack](http://www.scala-lang.org/api/2.10.0/scala/collection/mutable/ArrayStack.html) 是另一种可变栈的实现,用一个可根据需要改变大小的数组做为支持。它提供了快速索引,使其通常在大多数的操作中会比普通的可变堆栈更高效一点。 + +## 哈希表 + +Hash Table 用一个底层数组来存储元素,每个数据项在数组中的存储位置由这个数据项的Hash Code 来决定。添加一个元素到Hash Table不用花费多少时间,只要数组中不存在与其含有相同Hash Code的另一个元素。因此,只要Hash Table能够根据一种良好的hash codes分配机制来存放对象,Hash Table的速度会非常快。所以在Scala中默认的可变map和set都是基于Hash Table的。你也可以直接用[mutable.HashSet](http://www.scala-lang.org/api/2.10.0/scala/collection/mutable/HashSet.html) 和 [mutable.HashMap](http://www.scala-lang.org/api/2.10.0/scala/collection/mutable/HashMap.html) 来访问它们。 + +Hash Set 和 Map 的使用和其他的Set和Map是一样的。这里有一些简单的例子: + + scala> val map = scala.collection.mutable.HashMap.empty[Int,String] + map: scala.collection.mutable.HashMap[Int,String] = Map() + scala> map += (1 -> "make a web site") + res42: map.type = Map(1 -> make a web site) + scala> map += (3 -> "profit!") + res43: map.type = Map(1 -> make a web site, 3 -> profit!) + scala> map(1) + res44: String = make a web site + scala> map contains 2 + res46: Boolean = false + +Hash Table的迭代并不是按特定的顺序进行的。它是按任何可能的顺序,依次处理底层数组的数据。为了保证迭代的次序,可以使用一个Linked Hash Map 或 Set 来做为替代。Linked Hash Map 或 Set 像标准的Hash Map 或 Set一样,只不过它包含了一个Linked List,其中的元素按添加的顺序排列。在这种容器中的迭代都是具有相同的顺序,就是按照元素最初被添加的顺序进行迭代。 + +## Weak Hash Maps + +Weak Hash Map 是一种特殊的Hash Map,垃圾回收器会忽略从Map到存储在其内部的Key值的链接。这也就是说,当一个key不再被引用的时候,这个键和对应的值会从map中消失。Weak Hash Map 可以用来处理缓存,比如当一个方法被同一个键值重新调用时,你想重用这个大开销的方法返回值。如果Key值和方法返回值存储在一个常规的Hash Map里,Map会无限制的扩展,Key值也永远不会被垃圾回收器回收。用Weak Hash Map会避免这个问题。一旦有一个Key对象不再被引用,那它的实体会从Weak Hash Map中删除。在Scala中,[WeakHashMap](http://www.scala-lang.org/api/2.10.0/scala/collection/mutable/WeakHashMap.html)类是Weak Hash Map的实现类,封装了底层的Java实现类`java.util.WeakHashMap`。 + +## Concurrent Maps + +Concurrent Map可以同时被多个线程访问。除了[Map](http://www.scala-lang.org/api/2.10.0/scala/collection/Map.html)的通用方法,它提供了下列的原子方法: + +### Concurrent Map类中的方法: + +|WHAT IT IS | WHAT IT DOES | +|-----------------------|----------------------| +|m putIfAbsent(k, v) | 添加 键/值 绑定 k -> m ,如果k在m中没有被定义过 | +|m remove (k, v) | 如果当前 k 映射于 v,删除k对应的实体。 | +|m replace (k, old, new) | 如果k先前绑定的是old,则把键k 关联的值替换为new。 | +|m replace (k, v) | 如果k先前绑定的是其他值,则把键k对应的值替换为v | + + +`ConcurrentMap`体现了Scala容器库的特性。目前,它的实现类只有Java的`java.util.concurrent.ConcurrentMap`, 它可以用[standard Java/Scala collection conversions](http://docs.scala-lang.org/overviews/collections/conversions-between-java-and-scala-collections.html)(标准的java/Scala容器转换器)来自动转换成一个Scala map。 + +## Mutable Bitsets + +一个类型为[mutable.BitSet](http://www.scala-lang.org/api/2.10.0/scala/collection/mutable/BitSet.html)的可变bit集合和不可变的bit集合很相似,它只是做了适当的修改。Mutable bit sets在更新的操作上比不可变bit set 效率稍高,因为它不必复制没有发生变化的 Long值。 + + scala> val bits = scala.collection.mutable.BitSet.empty + bits: scala.collection.mutable.BitSet = BitSet() + scala> bits += 1 + res49: bits.type = BitSet(1) + scala> bits += 3 + res50: bits.type = BitSet(1, 3) + scala> bits + res51: scala.collection.mutable.BitSet = BitSet(1, 3) diff --git a/_zh-cn/overviews/collections/conversions-between-java-and-scala-collections.md b/_zh-cn/overviews/collections/conversions-between-java-and-scala-collections.md new file mode 100644 index 0000000000..af302dd49f --- /dev/null +++ b/_zh-cn/overviews/collections/conversions-between-java-and-scala-collections.md @@ -0,0 +1,59 @@ +--- +layout: multipage-overview +title: Java和Scala容器的转换 + +discourse: false + +partof: collections +overview-name: Collections + +num: 17 +language: zh-cn +--- + + +和Scala一样,Java同样提供了丰富的容器库,Scala和Java容器库有很多相似点,例如,他们都包含迭代器、可迭代结构、集合、 映射和序列。但是他们有一个重要的区别。Scala的容器库特别强调不可变性,因此提供了大量的新方法将一个容器变换成一个新的容器。 + +某些时候,你需要将一种容器类型转换成另外一种类型。例如,你可能想要像访问Scala容器一样访问某个Java容器,或者你可能想将一个Scala容器像Java容器一样传递给某个Java方法。在Scala中,这是很容易的,因为Scala提供了大量的方法来隐式转换所有主要的Java和Scala容器类型。其中提供了如下的双向类型转换: + + Iterator <=> java.util.Iterator + Iterator <=> java.util.Enumeration + Iterable <=> java.lang.Iterable + Iterable <=> java.util.Collection + mutable.Buffer <=> java.util.List + mutable.Set <=> java.util.Set + mutable.Map <=> java.util.Map + mutable.ConcurrentMap <=> java.util.concurrent.ConcurrentMap + +使用这些转换很简单,只需从JavaConversions对象中import它们即可。 + + scala> import collection.JavaConversions._ + import collection.Java.Conversions._ + +import之后,就可以在Scala容器和与之对应的Java容器之间进行隐式转换了 + + scala> import collection.mutable._ + import collection.mutable._ + scala> val jul: java.util.List[Int] = ArrayBuffer(1, 2, 3) + jul: java.util.List[Int] = [1, 2, 3] + scala> val buf: Seq[Int] = jul + buf: scala.collection.mutable.Seq[Int] = ArrayBuffer(1, 2, 3) + scala> val m: java.util.Map[String, Int] = HashMap("abc" -> 1, "hello" -> 2) + m: java.util.Map[String, Int] = {hello=2, abc=1} + +在Scala内部,这些转换是通过一系列“包装”对象完成的,这些对象会将相应的方法调用转发至底层的容器对象。所以容器不会在Java和Scala之间拷贝来拷贝去。一个值得注意的特性是,如果你将一个Java容器转换成其对应的Scala容器,然后再将其转换回同样的Java容器,最终得到的是一个和一开始完全相同的容器对象(译注:这里的相同意味着这两个对象实际上是指向同一片内存区域的引用,容器转换过程中没有任何的拷贝发生)。 + +还有一些Scala容器类型可以转换成对应的Java类型,但是并没有将相应的Java类型转换成Scala类型的能力,它们是: + + Seq => java.util.List + mutable.Seq => java.util.List + Set => java.util.Set + Map => java.util.Map + +因为Java并未区分可变容器不可变容器类型,所以,虽然能将`scala.immutable.List`转换成`java.util.List`,但所有的修改操作都会抛出“UnsupportedOperationException”。参见下例: + + scala> jul = List(1, 2, 3) + jul: java.util.List[Int] = [1, 2, 3] + scala> jul.add(7) + java.lang.UnsupportedOperationException + at java.util.AbstractList.add(AbstractList.java:131) diff --git a/_zh-cn/overviews/collections/creating-collections-from-scratch.md b/_zh-cn/overviews/collections/creating-collections-from-scratch.md new file mode 100644 index 0000000000..3d9df97e09 --- /dev/null +++ b/_zh-cn/overviews/collections/creating-collections-from-scratch.md @@ -0,0 +1,60 @@ +--- +layout: multipage-overview +title: 从头定义新容器 + +discourse: false + +partof: collections +overview-name: Collections + +num: 16 +language: zh-cn +--- + + +我们已经知道`List(1, 2, 3)`可以创建出含有三个元素的列表,用`Map('A' -> 1, 'C' -> 2)`可以创建含有两对绑定的映射。实际上各种Scala容器都支持这一功能。任意容器的名字后面都可以加上一对带参数列表的括号,进而生成一个以这些参数为元素的新容器。不妨再看一些例子: + + Traversable() // 一个空的Traversable对象 + List() // 空列表 + List(1.0, 2.0) // 一个以1.0、2.0为元素的列表 + Vector(1.0, 2.0) // 一个以1.0、2.0为元素的Vector + Iterator(1, 2, 3) // 一个迭代器,可返回三个整数 + Set(dog, cat, bird) // 一个包含三个动物的集合 + HashSet(dog, cat, bird) // 一个包含三个同样动物的HashSet + Map('a' -> 7, 'b' -> 0) // 一个将字符映射到整数的Map + +实际上,上述每个例子都被“暗地里”转换成了对某个对象的apply方法的调用。例如,上述第三行会展开成如下形式: + + List.apply(1.0, 2.0) + +可见,这里调用的是List类的伴生对象的apply方法。该方法可以接受任意多个参数,并将这些参数作为元素,生成一个新的列表。在Scala标准库中,无论是List、Stream、Vector等具体的实现类还是Seq、Set、Traversable等抽象基类,每个容器类都伴一个带apply方法的伴生对象。针对后者,调用apply方法将得到对应抽象基类的某个默认实现,例如: + + scala > List(1,2,3) + res17: List[Int] = List(1, 2, 3) + scala> Traversable(1, 2, 3) + res18: Traversable[Int] = List(1, 2, 3) + scala> mutable.Traversable(1, 2, 3) + res19: scala.collection.mutable.Traversable[Int] = ArrayBuffer(1, 2, 3) + +除了apply方法,每个容器类的伴生对象还定义了一个名为empty的成员方法,该方法返回一个空容器。也就是说,`List.empty`可以代替`List()`,`Map.empty`可以代替`Map()`,等等。 + +Seq的子类同样在它们伴生对象中提供了工厂方法,总结如下表。简而言之,有这么一些: + +concat,将任意多个Traversable容器串联起来 +fill 和 tabulate,用于生成一维或者多维序列,并用给定的初值或打表函数来初始化。 +range,用于生成步长为step的整型序列,并且iterate,将某个函数反复应用于某个初始元素,从而产生一个序列。 + +## 序列的工厂方法 + +| WHAT IT IS | WHAT IT DOES | +|-------------------|---------------------| +| S.empty | 空序列 | +| S(x, y, z) | 一个包含x、y、z的序列 | +| S.concat(xs, ys, zs) | 将xs、ys、zs串街起来形成一个新序列。 | +| S.fill(n) {e} | 以表达式e的结果为初值生成一个长度为n的序列。 | +| S.fill(m, n){e} | 以表达式e的结果为初值生成一个维度为m x n的序列(还有更高维度的版本) | +| S.tabulate(n) {f} | 生成一个厂素为n、第i个元素为f(i)的序列。 | +| S.tabulate(m, n){f} | 生成一个维度为m x n,第(i, j)个元素为f(i, j)的序列(还有更高维度的版本)。 | +| S.range(start, end) | start, start + 1, ... end-1的序列。(译注:注意始左闭右开区间) | +| S.range(start, end, step) | 生成以start为起始元素、step为步长、最大值不超过end的递增序列(左闭右开)。 | +| S.iterate(x, n)(f) | 生成一个长度为n的序列,其元素值分别为x、f(x)、f(f(x))、…… | diff --git a/_zh-cn/overviews/collections/equality.md b/_zh-cn/overviews/collections/equality.md new file mode 100644 index 0000000000..39eedc64dd --- /dev/null +++ b/_zh-cn/overviews/collections/equality.md @@ -0,0 +1,34 @@ +--- +layout: multipage-overview +title: 等价性 + +discourse: false + +partof: collections +overview-name: Collections + +num: 13 +language: zh-cn +--- + + +容器库有标准的等价性和散列法。这个想法的第一步是将容器划分为集合,映射和序列。不同范畴的容器总是不相等的。例如,即使包含相同的元素,`Set(1, 2, 3)` 与 `List(1, 2, 3)` 不等价。另一方面,在同一范畴下的容器是相等的,当且仅当它们具有相同的元素(对于序列:元素要相同,顺序要相同)。例如`List(1, 2, 3) == Vector(1, 2, 3)`, `HashSet(1, 2) == TreeSet(2, 1)`。 + +一个容器可变与否对等价性校验没有任何影响。对于一个可变容器,在执行等价性测试的同时,你可以简单地思考下它的当前元素。意思是,一个可变容器可能在不同时间等价于不同容器,这是由增加或移除了哪些元素所决定的。当你使用可变容器作为一个hashmap的键时,这将是一个潜在的陷阱。例如: + + scala> import collection.mutable.{HashMap, ArrayBuffer} + import collection.mutable.{HashMap, ArrayBuffer} + scala> val buf = ArrayBuffer(1, 2, 3) + buf: scala.collection.mutable.ArrayBuffer[Int] = + ArrayBuffer(1, 2, 3) + scala> val map = HashMap(buf -> 3) + map: scala.collection.mutable.HashMap[scala.collection。 + mutable.ArrayBuffer[Int],Int] = Map((ArrayBuffer(1, 2, 3),3)) + scala> map(buf) + res13: Int = 3 + scala> buf(0) += 1 + scala> map(buf) + java.util.NoSuchElementException: key not found: + ArrayBuffer(2, 2, 3) + +在这个例子中,由于数组xs的散列码已经在倒数第二行发生了改变,最后一行的选择操作将很有可能失败。因此,基于散列码的查找函数将会查找另一个位置,而不是xs所存储的位置。 diff --git a/_zh-cn/overviews/collections/introduction.md b/_zh-cn/overviews/collections/introduction.md new file mode 100644 index 0000000000..1cad8b7988 --- /dev/null +++ b/_zh-cn/overviews/collections/introduction.md @@ -0,0 +1,38 @@ +--- +layout: multipage-overview +title: 简介 + +discourse: false + +partof: collections +overview-name: Collections + +num: 1 +language: zh-cn +--- + +**Martin Odersky 和 Lex Spoon** + +在许多人看来,新的集合框架是Scala 2.8中最显著的改进。此前Scala也有集合(实际上新框架大部分地兼容了旧框架),但2.8中的集合类在通用性、一致性和功能的丰富性上更胜一筹。 + +即使粗看上去集合新增的内容比较微妙,但这些改动却足以对开发者的编程风格造成深远的影响。实际上,就好像你从事一个高层次的程序,而此程序的基本的构建块的元素被整个集合代替。适应这种新的编程风格需要一个过程。幸运的是,新的Scala集合得益于几个新的几个漂亮的属性,从而它们易于使用、简洁、安全、快速、通用。 + +- **易用性**:由20-50个方法的小词汇量,足以在几个操作内解决大部分的集合问题。没有必要被复杂的循环或递归所困扰。持久化的集合类和无副作用的操作意味着你不必担心新数据会意外的破坏已经存在的集合。迭代器和集合更新之间的干扰会被消除! + +- **简洁**:你可以通过单独的一个词来执行一个或多个循环。你也可以用轻量级的语法和组合轻松地快速表达功能性的操作,以致结果看起来像一个自定义的代数。 + +- **安全**:这一问题必须被熟练的理解,Scala集合的静态类型和函数性质意味着你在编译的时候就可以捕获绝大多数错误。原因是(1)、集合操作本身被大量的使用,是测试良好的。(2)、集合的用法要求输入和输出要显式作为函数参数和结果。(3)这些显式的输入输出会受到静态类型检查。最终,绝大部分的误用将会显示为类型错误。这是很少见的的有数百行的程序的首次运行。 + +- **快速**:集合操作已经在类库里是调整和优化过。因此,使用集合通常是比较高效的。你也许能够通过手动调整数据结构和操作来做的好一点,但是你也可能会由于一些次优的实现而做的更糟。不仅如此,集合类最近已经能支持在多核处理器上并行运算。并行集合类支持有序集合的相同操作,因此没有新的操作需要学习也没有代码需要重写。你可以简单地通过调用标准方法来把有序集合优化为一个并行集合。 + +- **通用**:集合类提供了相同的操作在一些类型上,确实如此。所以,你可以用相当少的词汇完成大量的工作。例如,一个字符串从概念上讲就是一个字符序列。因此,在Scala集合类中,字符串支持所有的序列操作。同样的,数组也是支持的。 + +例子:这有一行代码演示了Scala集合类的先进性。 + + val (minors, adults) = people partition (_.age < 18) + +这个操作是清晰的:通过他们的age(年龄)把这个集合people拆分到到miors(未成年人)和adults(成年人)中。由于这个拆分方法是被定义在根集合类型TraversableLike类中,这部分代码服务于任何类型的集合,包括数组。例子运行的结果就是miors和adults集合与people集合的类型相同。 + +这个代码比使用传统的类运行一到三个循环更加简明(三个循环处理一个数组,是由于中间结果需要有其它地方做缓存)。一旦你已经学习了基本的集合词汇,你将也发现写这种代码显式的循环更简单和更安全。而且,这个拆分操作是非常快速,并且在多核处理器上采用并行集合类达到更快的速度(并行集合类已经Scala 2.9的一部分发布)。 + +本文档从一个用户的角度出发,提供了一个关于Scala集合类的 API的深入讨论。它将带你体验它定义的所有的基础类和方法。 diff --git a/_zh-cn/overviews/collections/iterators.md b/_zh-cn/overviews/collections/iterators.md new file mode 100644 index 0000000000..4325c15710 --- /dev/null +++ b/_zh-cn/overviews/collections/iterators.md @@ -0,0 +1,177 @@ +--- +layout: multipage-overview +title: Iterators + +discourse: false + +partof: collections +overview-name: Collections + +num: 15 +language: zh-cn +--- + +迭代器不是一个容器,更确切的说是逐一访问容器内元素的方法。迭代器it的两个基本操作是next和hasNext。调用it.next()会返回迭代器的下一个元素,并且更新迭代器的状态。在同一个迭代器上再次调用next,会产生一个新元素来覆盖之前返回的元素。如果没有元素可返回,调用next方法会抛出一个NoSuchElementException异常。你可以调用[迭代器]的hasNext方法来查询容器中是否有下一个元素可供返回。 + +让迭代器it逐个返回所有元素最简单的方法是使用while循环: + + while (it.hasNext) + println(it.next()) + +Scala为Traversable, Iterable和Seq类中的迭代器提供了许多类似的方法。比如:这些类提供了foreach方法以便在迭代器返回的每个元素上执行指定的程序。使用foreach方法可以将上面的循环缩写为: + + it foreach println + +与往常一样,for表达式可以作为foreach、map、withFilter和flatMap表达式的替代语法,所以另一种打印出迭代器返回的所有元素的方式会是这样: + + for (elem <- it) println(elem) + +在迭代器或traversable容器中调用foreach方法的最大区别是:当在迭代器中完成调用foreach方法后会将迭代器保留在最后一个元素的位置。所以在这个迭代器上再次调用next方法时会抛出NoSuchElementException异常。与此不同的是,当在容器中调用foreach方法后,容器中的元素数量不会变化(除非被传递进来的函数删除了元素,但不赞成这样做,因为这会导致意想不到的结果)。 + +迭代器的其他操作跟Traversable一样具有相同的特性。例如:迭代器提供了map方法,该方法会返回一个新的迭代器: + + scala> val it = Iterator("a", "number", "of", "words") + it: Iterator[java.lang.String] = non-empty iterator + scala> it.map(_.length) + res1: Iterator[Int] = non-empty iterator + scala> res1 foreach println + 1 + 6 + 2 + 5 + scala> it.next() + java.util.NoSuchElementException: next on empty iterator + +如你所见,在调用了it.map方法后,迭代器it移动到了最后一个元素的位置。 + +另一个例子是关于dropWhile方法,它用来在迭代器中找到第一个具有某些属性的元素。比如:在上文所说的迭代器中找到第一个具有两个以上字符的单词,你可以这样写: + + scala> val it = Iterator("a", "number", "of", "words") + it: Iterator[java.lang.String] = non-empty iterator + scala> it dropWhile (_.length < 2) + res4: Iterator[java.lang.String] = non-empty iterator + scala> it.next() + res5: java.lang.String = number + +再次注意it在调用dropWhile方法后发生的变化:现在it指向了list中的第二个单词"number"。实际上,it和dropWhile返回的结果res4将会返回相同的元素序列。 + +只有一个标准操作允许重用同一个迭代器: + + val (it1, it2) = it.duplicate + +这个操作返回两个迭代器,每个都相当于迭代器it的完全拷贝。这两个iterator相互独立;一个发生变化不会影响到另外一个。相比之下,原来的迭代器it则被指定到元素的末端而无法再次使用。 + +总的来说,如果调用完迭代器的方法后就不再访问它,那么迭代器的行为方式与容器是比较相像的。Scala容器库中的抽象类TraversableOnce使这一特质更加明显,它是 Traversable 和 Iterator 的公共父类。顾名思义,TraversableOnce 对象可以用foreach来遍历,但是没有指定该对象遍历之后的状态。如果TraversableOnce对象是一个迭代器,它遍历之后会位于最后一个元素,但如果是Traversable则不会发生变化。TraversableOnce的一个通常用法是作为一个方法的参数类型,传递的参数既可以是迭代器,也可以是traversable。Traversable类中的追加方法++就是一个例子。它有一个TraversableOnce 类型的参数,所以你要追加的元素既可以来自于迭代器也可以来自于traversable容器。 + +下面汇总了迭代器的所有操作。 + +## Iterator类的操作 + +| WHAT IT IS | WHAT IT DOES | +|--------------|---------------| +| 抽象方法: | | +| it.next() | 返回迭代器中的下一个元素,并将位置移动至该元素之后。 | +| it.hasNext | 如果还有可返回的元素,返回true。 | +| 变量: | | +| it.buffered | 被缓存的迭代器返回it的所有元素。 | +| it grouped size | 迭代器会生成由it返回元素组成的定长序列块。 | +| xs sliding size | 迭代器会生成由it返回元素组成的定长滑动窗口序列。 | +| 复制: | | +| it.duplicate | 会生成两个能分别返回it所有元素的迭代器。 | +| 加法: | | +| it ++ jt | 迭代器会返回迭代器it的所有元素,并且后面会附加迭代器jt的所有元素。 | +| it padTo (len, x) | 首先返回it的所有元素,追加拷贝x直到长度达到len。 | +| Maps: | | +| it map f | 将it中的每个元素传入函数f后的结果生成新的迭代器。 | +| it flatMap f | 针对it指向的序列中的每个元素应用函数f,并返回指向结果序列的迭代器。 | +| it collect f | 针对it所指向的序列中的每一个在偏函数f上有定义的元素应用f,并返回指向结果序列的迭代器。 | +| 转换(Conversions): | | +| it.toArray | 将it指向的所有元素归入数组并返回。 | +| it.toList | 把it指向的所有元素归入列表并返回 | +| it.toIterable | 把it指向的所有元素归入一个Iterable容器并返回。 | +| it.toSeq | 将it指向的所有元素归入一个Seq容器并返回。 | +| it.toIndexedSeq | 将it指向的所有元素归入一个IndexedSeq容器并返回。 | +| it.toStream | 将it指向的所有元素归入一个Stream容器并返回。 | +| it.toSet | 将it指向的所有元素归入一个Set并返回。 | +| it.toMap | 将it指向的所有键值对归入一个Map并返回。 | +| 拷贝: | | +| it copyToBuffer buf | 将it指向的所有元素拷贝至缓冲区buf。| +| it copyToArray(arr, s, n) | 将it指向的从第s个元素开始的n个元素拷贝到数组arr,其中后两个参数是可选的。 | +| 尺寸信息: | | +| it.isEmpty | 检查it是否为空(与hasNext相反)。 | +| it.nonEmpty | 检查容器中是否包含元素(相当于 hasNext)。 | +| it.size | it可返回的元素数量。注意:这个操作会将it置于终点! | +| it.length | 与it.size相同。 | +| it.hasDefiniteSize | 如果it指向的元素个数有限则返回true(缺省等同于isEmpty) | +| 按下标检索元素: | | +| it find p | 返回第一个满足p的元素或None。注意:如果找到满足条件的元素,迭代器会被置于该元素之后;如果没有找到,会被置于终点。 | +| it indexOf x | 返回it指向的元素中index等于x的第一个元素。注意:迭代器会越过这个元素。 | +| it indexWhere p | 返回it指向的元素中下标满足条件p的元素。注意:迭代器会越过这个元素。 | +| 子迭代器: | | +| it take n | 返回一个包含it指向的前n个元素的新迭代器。注意:it的位置会步进至第n个元素之后,如果it指向的元素数不足n个,迭代器将指向终点。 | +| it drop n | 返回一个指向it所指位置之后第n+1个元素的新迭代器。注意:it将步进至相同位置。 | +| it slice (m,n) | 返回一个新的迭代器,指向it所指向的序列中从开始于第m个元素、结束于第n个元素的片段。 | +| it takeWhile p | 返回一个迭代器,指代从it开始到第一个不满足条件p的元素为止的片段。 | +| it dropWhile p | 返回一个新的迭代器,指向it所指元素中第一个不满足条件p的元素开始直至终点的所有元素。 | +| it filter p | 返回一个新迭代器 ,指向it所指元素中所有满足条件p的元素。 | +| it withFilter p | 同it filter p 一样,用于for表达式。 | +| it filterNot p | 返回一个迭代器,指向it所指元素中不满足条件p的元素。 | +| 拆分(Subdivision): | | +| it partition p | 将it分为两个迭代器;一个指向it所指元素中满足条件谓词p的元素,另一个指向不满足条件谓词p的元素。 | +| 条件元素(Element Conditions): | | +| it forall p | 返回一个布尔值,指明it所指元素是否都满足p。 | +| it exists p | 返回一个布尔值,指明it所指元素中是否存在满足p的元素。 | +| it count p | 返回it所指元素中满足条件谓词p的元素总数。 | +| 折叠(Fold): | | +| (z /: it)(op) | 自左向右在it所指元素的相邻元素间应用二元操作op,初始值为z。| +| (it :\ z)(op) | 自右向左在it所指元素的相邻元素间应用二元操作op,初始值为z。 | +| it.foldLeft(z)(op) | 与(z /: it)(op)相同。 | +| it.foldRight(z)(op) | 与(it :\ z)(op)相同。 | +| it reduceLeft op | 自左向右对非空迭代器it所指元素的相邻元素间应用二元操作op。 | +| it reduceRight op | 自右向左对非空迭代器it所指元素的相邻元素间应用二元操作op。 | +| 特殊折叠(Specific Fold): | | +| it.sum | 返回迭代器it所指数值型元素的和。 | +| it.product | 返回迭代器it所指数值型元素的积。 | +| it.min | 返回迭代器it所指元素中最小的元素。 | +| it.max | 返回迭代器it所指元素中最大的元素。 | +| 拉链方法(Zippers): | | +| it zip jt | 返回一个新迭代器,指向分别由it和jt所指元素一一对应而成的二元组序列。 | +| it zipAll (jt, x, y) | 返回一个新迭代器,指向分别由it和jt所指元素一一对应而成的二元组序列,长度较短的迭代器会被追加元素x或y,以匹配较长的迭代器。 | +| it.zipWithIndex | 返回一个迭代器,指向由it中的元素及其下标共同构成的二元组序列。 | +| 更新: | | +| it patch (i, jt, r) | 由it返回一个新迭代器,其中自第i个元素开始的r个元素被迭代器jt所指元素替换。 | +| 比对: | | +| it sameElements jt | 判断迭代器it和jt是否依次返回相同元素注意:it和jt中至少有一个会步进到终点。 | +|字符串(String): | | +| it addString (b, start, sep, end) | 添加一个字符串到StringBuilder b,该字符串以start为前缀、以end为后缀,中间是以sep分隔的it所指向的所有元素。start、end和sep都是可选项。 | +| it mkString (start, sep, end) | 将it所指所有元素转换成以start为前缀、end为后缀、按sep分隔的字符串。start、sep、end都是可选项。 | + +## 带缓冲的迭代器 + +有时候你可能需要一个支持“预览”功能的迭代器,这样我们既可以看到下一个待返回的元素,又不会令迭代器跨过这个元素。比如有这样一个任务,把迭代器所指元素中的非空元素转化成字符串。你可能会这样写: + + def skipEmptyWordsNOT(it: Iterator[String]) = + while (it.next().isEmpty) {} + +但仔细看看这段代码,就会发现明显的错误:代码确实会跳过空字符串,但同时它也跳过了第一个非空字符串! + +要解决这个问题,可以使用带缓冲能力的迭代器。[BufferedIterator]类是[Iterator]的子类,提供了一个附加的方法,head。在BufferedIterator中调用head 会返回它指向的第一个元素,但是不会令迭代器步进。使用BufferedIterator,跳过空字符串的方法可以写成下面这样: + + def skipEmptyWords(it: BufferedIterator[String]) = + while (it.head.isEmpty) { it.next() } + +通过调用buffered方法,所有迭代器都可以转换成BufferedIterator。参见下例: + + scala> val it = Iterator(1, 2, 3, 4) + it: Iterator[Int] = non-empty iterator + scala> val bit = it.buffered + bit: java.lang.Object with scala.collection. + BufferedIterator[Int] = non-empty iterator + scala> bit.head + res10: Int = 1 + scala> bit.next() + res11: Int = 1 + scala> bit.next() + res11: Int = 2 + +注意,调用`BufferedIterator bit`的head方法不会令它步进。因此接下来的`bit.next()`返回的元素跟`bit.head`相同。 diff --git a/_zh-cn/overviews/collections/maps.md b/_zh-cn/overviews/collections/maps.md new file mode 100644 index 0000000000..76e07b2152 --- /dev/null +++ b/_zh-cn/overviews/collections/maps.md @@ -0,0 +1,169 @@ +--- +layout: multipage-overview +title: 映射 + +discourse: false + +partof: collections +overview-name: Collections + +num: 7 +language: zh-cn +--- + + +映射(Map)是一种可迭代的键值对结构(也称映射或关联)。Scala的Predef类提供了隐式转换,允许使用另一种语法:`key -> value`,来代替`(key, value)`。如:`Map("x" -> 24, "y" -> 25, "z" -> 26)`等同于`Map(("x", 24), ("y", 25), ("z", 26))`,却更易于阅读。 + +映射(Map)的基本操作与集合(Set)类似。下面的表格分类总结了这些操作: + +- **查询类操作:**apply、get、getOrElse、contains和DefinedAt。它们都是根据主键获取对应的值映射操作。例如:def get(key): Option[Value]。“m get key” 返回m中是否用包含了key值。如果包含了,则返回对应value的Some类型值。否则,返回None。这些映射中也包括了apply方法,该方法直接返回主键对应的值。apply方法不会对值进行Option封装。如果该主键不存在,则会抛出异常。 +- **添加及更新类操作:**+、++、updated,这些映射操作允许你添加一个新的绑定或更改现有的绑定。 +- **删除类操作:**-、--,从一个映射(Map)中移除一个绑定。 +- **子集类操作:**keys、keySet、keysIterator、values、valuesIterator,可以以不同形式返回映射的键和值。 +- **filterKeys、mapValues等**变换用于对现有映射中的绑定进行过滤和变换,进而生成新的映射。 + +## Map类的操作 + +| WHAT IT IS | WHAT IT DOES | +|---------------|---------------------| +| **查询:** | | +| ms get k | 返回一个Option,其中包含和键k关联的值。若k不存在,则返回None。 | +| ms(k) | (完整写法是ms apply k)返回和键k关联的值。若k不存在,则抛出异常。 | +| ms getOrElse (k, d) | 返回和键k关联的值。若k不存在,则返回默认值d。 | +| ms contains k | 检查ms是否包含与键k相关联的映射。 | +| ms isDefinedAt k | 同contains。 | +| **添加及更新:** | | +| ms + (k -> v) | 返回一个同时包含ms中所有键值对及从k到v的键值对k -> v的新映射。 | +| ms + (k -> v, l -> w) | 返回一个同时包含ms中所有键值对及所有给定的键值对的新映射。 | +| ms ++ kvs | 返回一个同时包含ms中所有键值对及kvs中的所有键值对的新映射。 | +| ms updated (k, v) | 同ms + (k -> v)。 | +| **移除:** | | +| ms - k | 返回一个包含ms中除键k以外的所有映射关系的映射。 | +| ms - (k, 1, m) | 返回一个滤除了ms中与所有给定的键相关联的映射关系的新映射。 | +| ms -- ks | 返回一个滤除了ms中与ks中给出的键相关联的映射关系的新映射。 | +| **子容器(Subcollection):** | | +| ms.keys | 返回一个用于包含ms中所有键的iterable对象(译注:请注意iterable对象与iterator的区别) | +| ms.keySet | 返回一个包含ms中所有的键的集合。 | +| ms.keyIterator | 返回一个用于遍历ms中所有键的迭代器。 | +| ms.values | 返回一个包含ms中所有值的iterable对象。 | +| ms.valuesIterator | 返回一个用于遍历ms中所有值的迭代器。 | +| **变换:** | | +| ms filterKeys p | 一个映射视图(Map View),其包含一些ms中的映射,且这些映射的键满足条件p。用条件谓词p过滤ms中所有的键,返回一个仅包含与过滤出的键值对的映射视图(view)。| +|ms mapValues f | 用f将ms中每一个键值对的值转换成一个新的值,进而返回一个包含所有新键值对的映射视图(view)。| + + +可变映射(Map)还支持下表中列出的操作。 + +## mutable.Map类中的操作 + +| WHAT IT IS | WHAT IT DOES | +|-------------------------|-------------------------| +| **添加及更新** | | +| ms(k) = v | (完整形式为ms.update(x, v))。向映射ms中新增一个以k为键、以v为值的映射关系,ms先前包含的以k为值的映射关系将被覆盖。 | +| ms += (k -> v) | 向映射ms增加一个以k为键、以v为值的映射关系,并返回ms自身。 | +| ms += (k -> v, l -> w) | 向映射ms中增加给定的多个映射关系,并返回ms自身。 | +| ms ++= kvs | 向映射ms增加kvs中的所有映射关系,并返回ms自身。 | +| ms put (k, v) | 向映射ms增加一个以k为键、以v为值的映射,并返回一个Option,其中可能包含此前与k相关联的值。 | +| ms getOrElseUpdate (k, d) | 如果ms中存在键k,则返回键k的值。否则向ms中新增映射关系k -> v并返回d。 | +| **移除:** | | +| ms -= k | 从映射ms中删除以k为键的映射关系,并返回ms自身。 | +| ms -= (k, l, m) | 从映射ms中删除与给定的各个键相关联的映射关系,并返回ms自身。 | +| ms --= ks | 从映射ms中删除与ks给定的各个键相关联的映射关系,并返回ms自身。 | +| ms remove k | 从ms中移除以k为键的映射关系,并返回一个Option,其可能包含之前与k相关联的值。 | +| ms retain p | 仅保留ms中键满足条件谓词p的映射关系。 | +| ms.clear() | 删除ms中的所有映射关系 | +| **变换:** | | +| ms transform f | 以函数f转换ms中所有键值对(译注:原文比较含糊,transform中参数f的类型是(A, B) => B,即对ms中的所有键值对调用f,得到一个新的值,并用该值替换原键值对中的值)。 | +| **克隆:** | | +| ms.clone | 返回一个新的可变映射(Map),其中包含与ms相同的映射关系。 | + +映射(Map)的添加和删除操作与集合(Set)的相关操作相同。同集合(Set)操作一样,可变映射(mutable maps)也支持非破坏性(non-destructive)修改操作+、-、和updated。但是这些操作涉及到可变映射的复制,因此较少被使用。而利用两种变形`m(key) = value和m += (key -> value)`, 我们可以“原地”修改可变映射m。此外,存还有一种变形`m put (key, value)`,该调用返回一个Option值,其中包含此前与键相关联的值,如果不存在这样的值,则返回None。 + +getOrElseUpdate特别适合用于访问用作缓存的映射(Map)。假设调用函数f开销巨大: + + scala> def f(x: String) = { + println("taking my time."); sleep(100) + x.reverse } + f: (x: String)String + +此外,再假设f没有副作用,即反复以相同参数调用f,得到的结果都相同。那么,我们就可以将之前的调用参数和计算结果保存在一个映射(Map)内,今后仅在映射中查不到对应参数的情况下实际调用f,这样就可以节约时间。这个映射便可以认为是函数f的缓存。 + + val cache = collection.mutable.Map[String, String]() + cache: scala.collection.mutable.Map[String,String] = Map() + +现在,我们可以写出一个更高效的带缓存的函数f: + + scala> def cachedF(s: String) = cache.getOrElseUpdate(s, f(s)) + cachedF: (s: String)String + scala> cachedF("abc") + +稍等片刻。 + + res3: String = cba + scala> cachedF("abc") + res4: String = cba + +注意,getOrElseUpdate的第2个参数是“按名称(by-name)"传递的,所以,仅当在缓存映射中找不到第1个参数,而getOrElseUpdate需要其第2个参数的值时,上述的f("abc")才会被执行。当然我们也可以利用Map的基本操作直接实现cachedF,但那样写就要冗长很多了。 + + def cachedF(arg: String) = cache get arg match { + case Some(result) => result + case None => + val result = f(x) + cache(arg) = result + result + } + +## 同步集合(Set)和映射(Map) + +无论什么样的Map实现,只需混入`SychronizedMap trait`,就可以得到对应的线程安全版的Map。例如,我们可以像下述代码那样在HashMap中混入SynchronizedMap。这个示例一上来先从`scala.colletion.mutable`包中import了两个trait:Map、SynchronizedMap,和一个类:HashMap。接下来,示例中定义了一个单例对象MapMaker,其中定义了一个方法makeMap。该方法的返回值类型是一个同时以String为键值类型的可变映射。 + + import scala.collection.mutable.{Map, + SynchronizedMap, HashMap} + object MapMaker { + def makeMap: Map[String, String] = { + new HashMap[String, String] with + SynchronizedMap[String, String] { + override def default(key: String) = + "Why do you want to know?" + } + } + } + +混入SynchronizedMap trait + +makeMap方法中的第1个语句构造了一个新的混入了SynchronizedMap trait的可变映射: + + new HashMap[String, String] with + SynchronizedMap[String, String] + +针对这段代码,Scala编译器会合成HashMap的一个混入了SynchronizedMap trait的子类,同时生成(并返回)该合成子类的一个实例。处于下面这段代码的缘故,这个合成类还覆写了default方法: + + override def default(key: String) = + "Why do you want to know?" + +当向某个Map查询给定的键所对应的值,而Map中不存在与该键相关联的值时,默认情况下会触发一个NoSuchElementException异常。不过,如果自定义一个Map类并覆写default方法,便可以针对不存在的键返回一个default方法返回的值。所以,编译器根据上述代码合成的HashMap子类在碰到不存在的键时将会反过来质问你“Why do you want to know?” + +makeMap方法返回的可变映射混入了 SynchronizedMap trait,因此可以用在多线程环境下。对该映射的每次访问都是同步的。以下示例展示的是从解释器内以单个线程访问该映射: + + scala> val capital = MapMaker.makeMap + capital: scala.collection.mutable.Map[String,String] = Map() + scala> capital ++ List("US" -> "Washington", + "France" -> "Paris", "Japan" -> "Tokyo") + res0: scala.collection.mutable.Map[String,String] = + Map(France -> Paris, US -> Washington, Japan -> Tokyo) + scala> capital("Japan") + res1: String = Tokyo + scala> capital("New Zealand") + res2: String = Why do you want to know? + scala> capital += ("New Zealand" -> "Wellington") + scala> capital("New Zealand") + res3: String = Wellington + +同步集合(synchronized set)的创建方法与同步映射(synchronized map)类似。例如,我们可以通过混入SynchronizedSet trait来创建同步哈希集: + + import scala.collection.mutable //导入包scala.collection.mutable + val synchroSet = + new mutable.HashSet[Int] with + mutable.SynchronizedSet[Int] + +最后,如有使用同步容器(synchronized collection)的需求,还可以考虑使用`java.util.concurrent`中提供的并发容器(concurrent collections)。 diff --git a/_zh-cn/overviews/collections/migrating-from-scala-27.md b/_zh-cn/overviews/collections/migrating-from-scala-27.md new file mode 100644 index 0000000000..016fc7ce69 --- /dev/null +++ b/_zh-cn/overviews/collections/migrating-from-scala-27.md @@ -0,0 +1,46 @@ +--- +layout: multipage-overview +title: Scala 2.7迁移指南 + +discourse: false + +partof: collections +overview-name: Collections + +num: 18 +language: zh-cn +--- + + +现有应用中新旧Scala容器类型的移植基本上是自动的。只有几种情况需要特别注意。 + +Scala 2.7中容器的旧有功能基本上全部予以保留。某些功能被标记为deprecated,这意味着今后版本可能会删除它们。如果在Scala 2.8中使用这些方法,将会得到一个deprecation警告。在2.8下编译时,这些情况被视作迁移警告(migration warnings)。要得到完整的deprecation和迁移警告以及代码修改建议,请在编译时给Scala编译器scalac加上-deprecation和-Xmigration参数(注意,-Xmigration是扩展参数,因此以X开头)。你也可以将参数传给Scala REPL,从而在交互式环境中得到警告,例如: + + >scala -deprecation -Xmigration + Welcome to Scala version 2.8.0.final + 键入表达式来运行 + 键入 :help来看更多信息 + scala> val xs = List((1, 2), (3, 4)) + xs: List[(Int, Int)] = List((1, 2), (3, 4)) + scala> List.unzip(xs) + :7: warning: method unzip in object List is deprecated: use xs.unzip instead of List.unzip(xs) + List.unzip(xs) + ^ + res0: (List[Int], List[Int]) = (List(1, 3), List(2, 4)) + scala> xs.unzip + res1: (List[Int], List[Int]) = (List(1, 3), List(2, 4)) + scala> val m = xs.toMap + m: scala.collection.immutable.Map[Int, Int] = Map((1, 2), (3, 4)) + scala> m.keys + :8: warning: method keys in trait MapLike has changed semantics: + As of 2.8 keys returns Iterable[A] rather than Iterator[A]. + m.keys + ^ + res2: Iterable[Int] = Set(1, 3) + +老版本的库中有两个部分被整个移除,所以在deprecation警告中看不到它们。 + +scala.collection.jcl包被移除了。这个包试图在Scala中模拟某些Java的容器,但是该包破坏了Scala的一些对称性。绝大多数人,当他们需要Java容器的时候,他们会直接选用java.util。 +Scala 2.8通过JavaConversions对象提供了自动的在Java和Scala容器类型间转换的机制,这一机制替代了老的jcl包。 +各种投影操作被泛化整理成了视图。从实际情况来看,投影的用处并不大,因此受影响的代码应该不多。 +所以,如果你的代码用了jcl包或者投影(projections),你将不得不进行一些小的修改。 diff --git a/_zh-cn/overviews/collections/overview.md b/_zh-cn/overviews/collections/overview.md new file mode 100644 index 0000000000..526d6c658f --- /dev/null +++ b/_zh-cn/overviews/collections/overview.md @@ -0,0 +1,91 @@ +--- +layout: multipage-overview +title: Mutable和Immutable集合 + +discourse: false + +partof: collections +overview-name: Collections + +num: 2 +language: zh-cn +--- + + +Scala 集合类系统地区分了可变的和不可变的集合。可变集合可以在适当的地方被更新或扩展。这意味着你可以修改,添加,移除一个集合的元素。而不可变集合类,相比之下,永远不会改变。不过,你仍然可以模拟添加,移除或更新操作。但是这些操作将在每一种情况下都返回一个新的集合,同时使原来的集合不发生改变。 + +所有的集合类都可以在包`scala.collection` 或`scala.collection.mutable`,`scala.collection.immutable`,`scala.collection.generic`中找到。客户端代码需要的大部分集合类都独立地存在于3种变体中,它们位于`scala.collection`, `scala.collection.immutable`, `scala.collection.mutable`包。每一种变体在可变性方面都有不同的特征。 + +`scala.collection.immutable`包是的集合类确保不被任何对象改变。例如一个集合创建之后将不会改变。因此,你可以相信一个事实,在不同的点访问同一个集合的值,你将总是得到相同的元素。。 + +`scala.collection.mutable`包的集合类则有一些操作可以修改集合。所以处理可变集合意味着你需要去理解哪些代码的修改会导致集合同时改变。 + +`scala.collection`包中的集合,既可以是可变的,也可以是不可变的。例如:[collection.IndexedSeq[T]](http://www.scala-lang.org/api/current/scala/collection/IndexedSeq.html)] 就是 [collection.immutable.IndexedSeq[T]](http://www.scala-lang.org/api/current/scala/collection/immutable/IndexedSeq.html) 和[collection.mutable.IndexedSeq[T]](http://www.scala-lang.org/api/current/scala/collection/mutable/IndexedSeq.html)这两类的超类。`scala.collection`包中的根集合类中定义了相同的接口作为不可变集合类,同时,`scala.collection.mutable`包中的可变集合类代表性的添加了一些有辅助作用的修改操作到这个immutable 接口。 + +根集合类与不可变集合类之间的区别是不可变集合类的客户端可以确保没有人可以修改集合。然而,根集合类的客户端仅保证不修改集合本身。即使这个集合类没有提供修改集合的静态操作,它仍然可能在运行时作为可变集合被其它客户端所修改。 + +默认情况下,Scala 一直采用不可变集合类。例如,如果你仅写了`Set` 而没有任何加前缀也没有从其它地方导入`Set`,你会得到一个不可变的`set`,另外如果你写迭代,你也会得到一个不可变的迭代集合类,这是由于这些类在从scala中导入的时候都是默认绑定的。为了得到可变的默认版本,你需要显式的声明`collection.mutable.Set`或`collection.mutable.Iterable`. + +一个有用的约定,如果你想要同时使用可变和不可变集合类,只导入collection.mutable包即可。 + + import scala.collection.mutable //导入包scala.collection.mutable + +然而,像没有前缀的Set这样的关键字, 仍然指的是一个不可变集合,然而`mutable.Set`指的是可变的副本(可变集合)。 + +集合树的最后一个包是`collection.generic`。这个包包含了集合的构建块。集合类延迟了`collection.generic`类中的部分操作实现,另一方面集合框架的用户需要引用`collection.generic`中类在异常情况中。 + +为了方便和向后兼容性,一些导入类型在包scala中有别名,所以你能通过简单的名字使用它们而不需要import。这有一个例子是List 类型,它可以用以下两种方法使用,如下: + + scala.collection.immutable.List // 这是它的定义位置 + scala.List //通过scala 包中的别名 + List // 因为scala._ + // 总是是被自动导入。 + +其它类型的别名有: [Traversable](http://www.scala-lang.org/api/current/scala/collection/Traversable.html), [Iterable](http://www.scala-lang.org/api/current/scala/collection/Iterable.html), [Seq](http://www.scala-lang.org/api/current/scala/collection/Seq.html), [IndexedSeq](http://www.scala-lang.org/api/current/scala/collection/IndexedSeq.html), [Iterator](http://www.scala-lang.org/api/current/scala/collection/Iterator.html), [Stream](http://www.scala-lang.org/api/current/scala/collection/immutable/Stream.html), [Vector](http://www.scala-lang.org/api/current/scala/collection/immutable/Vector.html), [StringBuilder](http://www.scala-lang.org/api/current/scala/collection/mutable/StringBuilder.html), [Range](http://www.scala-lang.org/api/current/scala/collection/immutable/Range.html)。 + +下面的图表显示了`scala.collection`包中所有的集合类。这些都是高级抽象类或特性,它们通常具备和不可变实现一样的可变实现。 + +[]({{ site.baseurl }}/resources/images/collections.png) + +下面的图表显示scala.collection.immutable中的所有集合类。 + +[]({{ site.baseurl }}/resources/images/collections.immutable.png) + +下面的图表显示scala.collection.mutable中的所有集合类。 + +[]({{ site.baseurl }}/resources/images/collections.mutable.png) + +(以上三个图表由Matthias生成, 来自decodified.com)。 + +## 集合API概述 + +大多数重要的集合类都被展示在了上表。而且这些类有很多的共性。例如,每一种集合都能用相同的语法创建,写法是集合类名紧跟着元素。 + + Traversable(1, 2, 3) + Iterable("x", "y", "z") + Map("x" -> 24, "y" -> 25, "z" -> 26) + Set(Color.red, Color.green, Color.blue) + SortedSet("hello", "world") + Buffer(x, y, z) + IndexedSeq(1.0, 2.0) + LinearSeq(a, b, c) + +相同的原则也应用于特殊的集合实现,例如: + + List(1, 2, 3) + HashMap("x" -> 24, "y" -> 25, "z" -> 26) + +所有这些集合类都通过相同的途径,用toString方法展示出来。 + +Traversable类提供了所有集合支持的API,同时,对于特殊类型也是有意义的。例如,Traversable类 的map方法会返回另一个Traversable对象作为结果,但是这个结果类型在子类中被重写了。例如,在一个List上调用map会又生成一个List,在Set上调用会再生成一个Set,以此类推。 + + scala> List(1, 2, 3) map (_ + 1) + res0: List[Int] = List(2, 3, 4) + scala> Set(1, 2, 3) map (_ * 2) + res0: Set[Int] = Set(2, 4, 6) + +在集合类库中,这种在任何地方都实现了的行为,被称之为返回类型一致原则。 + +大多数类在集合树中存在这于三种变体:root, mutable 和immutable。唯一的例外是缓冲区特征,它仅在于mutable集合。 + +下面我们将一个个的回顾这些类。 diff --git a/_zh-cn/overviews/collections/performance-characteristics.md b/_zh-cn/overviews/collections/performance-characteristics.md new file mode 100644 index 0000000000..b9e5719a68 --- /dev/null +++ b/_zh-cn/overviews/collections/performance-characteristics.md @@ -0,0 +1,87 @@ +--- +layout: multipage-overview +title: 性能特点 + +discourse: false + +partof: collections +overview-name: Collections + +num: 12 +language: zh-cn +--- + + +前面的解释明确说明了不同的容器类型具有不同的性能特点。这通常是选择容器类型的首要依据。以下的两张表格,总结了一些关于容器类型常用操作的性能特点。 + +## 序列类型的性能特点 + +| | head | tail | apply | update | prepend | append | insert | +|------|------|------|-------|--------|---------|--------|--------| +|**不可变序列**| | | | | | | +| List | C | C | L | L | C | L | - | +|Stream | C | C | L | L | C | L | - | +|Vector | eC | eC | eC | eC | eC | eC | - | +|Stack | C | C | L | L | C | L | L | +|Queue | aC | aC | L | L | C | C | - | +|Range | C | C | C | - | - | - | - | +|String | C | L | C | L | L | L | - | +|**可变序列**| | | | | | | +|ArrayBuffer | C | L | C | C | L | aC | L | +|ListBuffer | C | L | L | L | C | C | L | +|StringBuilder | C | L | C | C | L | aC | L | +|MutableList | C | L | L | L | C | C | L | +|Queue | C | L | L | L | C | C | L | +|ArraySeq | C | L | C | C | - | - | - | +|Stack | C | L | L | L | C | L | L | +|ArrayStack | C | L | C | C | aC | L | L | +|Array | C | L | C | C | - | - | - | + +## 集合和映射类型的性能特点 + +|lookup | add | remove | min | +|-------|-----|--------|-----| +|**不可变序列**| | | | +|HashSet/HashMap | eC | eC | eC | L | +|TreeSet/TreeMap | Log | Log | Log | Log | +|BitSet | C | L | L | eC1 | +|ListMap | L | L | L | L | +|可变序列| | | | +|HashSet/HashMap | eC | eC | eC | L | +|WeakHashMap | eC | eC | eC | L | +|BitSet | C | aC | C | eC1 | +|TreeSet | Log | Log | Log | Log | + +标注:1 假设位是密集分布的 + +这两个表中的条目: + +|解释如下| | +|--------|-----------------| +|C | 指操作的时间复杂度为常数 | +|eC | 指操作的时间复杂度实际上为常数,但可能依赖于诸如一个向量最大长度或是哈希键的分布情况等一些假设。 | +|aC | 该操作的均摊运行时间为常数。某些调用的可能耗时较长,但多次调用之下,每次调用的平均耗时是常数。 | +|Log | 操作的耗时与容器大小的对数成正比。 | +|L | 操作是线性的,耗时与容器的大小成正比。 | +|- | 操作不被支持。 | + +第一张表处理序列类型——无论可变还是不可变——: + +| 使用以下操作 | | +|--------|-----------------| +|head | 选择序列的第一个元素。 | +|tail | 生成一个包含除第一个元素以外所有其他元素的新的列表。 | +|apply | 索引。 | +|update | 功能性更新不可变序列,同步更新可变序列。 | +|prepend | 添加一个元素到序列头。对于不可变序列,操作会生成个新的序列。对于可变序列,操作会修改原有的序列。 | +|append | 在序列尾部插入一个元素。对于不可变序列,这将产生一个新的序列。对于可变序列,这将修改原有的序列。 | +|insert | 在序列的任意位置上插入一个元素。只有可变序列支持该操作。 | + +第二个表处理可变和不可变集与映射 + +| 使用以下操作:| | +|--------|-----------------| +|lookup | 测试一个元素是否被包含在集合中,或者找出一个键对应的值 | +|add | 添加一个新的元素到一个集合中或者添加一个键值对到一个映射中。 | +|remove | 移除一个集合中的一个元素或者移除一个映射中一个键。 | +|min | 集合中的最小元素,或者映射中的最小键。 | diff --git a/_zh-cn/overviews/collections/seqs.md b/_zh-cn/overviews/collections/seqs.md new file mode 100644 index 0000000000..c9640efd99 --- /dev/null +++ b/_zh-cn/overviews/collections/seqs.md @@ -0,0 +1,108 @@ +--- +layout: multipage-overview +title: 序列trait:Seq、IndexedSeq及LinearSeq + +discourse: false + +partof: collections +overview-name: Collections + +num: 5 +language: zh-cn +--- + + +[Seq](http://www.scala-lang.org/api/current/scala/collection/Seq.html) trait用于表示序列。所谓序列,指的是一类具有一定长度的可迭代访问的对象,其中每个元素均带有一个从0开始计数的固定索引位置。 + +序列的操作有以下几种,如下表所示: + +- **索引和长度的操作** apply、isDefinedAt、length、indices,及lengthCompare。序列的apply操作用于索引访问;因此,Seq[T]类型的序列也是一个以单个Int(索引下标)为参数、返回值类型为T的偏函数。换言之,Seq[T]继承自Partial Function[Int, T]。序列各元素的索引下标从0开始计数,最大索引下标为序列长度减一。序列的length方法是collection的size方法的别名。lengthCompare方法可以比较两个序列的长度,即便其中一个序列长度无限也可以处理。 +- **索引检索操作**(indexOf、lastIndexOf、indexofSlice、lastIndexOfSlice、indexWhere、lastIndexWhere、segmentLength、prefixLength)用于返回等于给定值或满足某个谓词的元素的索引。 +- **加法运算**(+:,:+,padTo)用于在序列的前面或者后面添加一个元素并作为新序列返回。 +- **更新操作**(updated,patch)用于替换原序列的某些元素并作为一个新序列返回。 +- **排序操作**(sorted, sortWith, sortBy)根据不同的条件对序列元素进行排序。 +- **反转操作**(reverse, reverseIterator, reverseMap)用于将序列中的元素以相反的顺序排列。 +- **比较**(startsWith, endsWith, contains, containsSlice, corresponds)用于对两个序列进行比较,或者在序列中查找某个元素。 +- **多集操作**(intersect, diff, union, distinct)用于对两个序列中的元素进行类似集合的操作,或者删除重复元素。 + +如果一个序列是可变的,它提供了另一种更新序列中的元素的,但有副作用的update方法,Scala中常有这样的语法,如seq(idx) = elem。它只是seq.update(idx, elem)的简写,所以update 提供了方便的赋值语法。应注意update 和updated之间的差异。update 再原来基础上更改序列中的元素,并且仅适用于可变序列。而updated 适用于所有的序列,它总是返回一个新序列,而不会修改原序列。 + +## Seq类的操作 + +| WHAT IT IS | WHAT IT DOES | +|------------------ | -------------------| +| **索引和长度** | | +| xs(i) | (或者写作xs apply i)。xs的第i个元素 | +| xs isDefinedAt i | 测试xs.indices中是否包含i。 | +| xs.length | 序列的长度(同size)。 | +| xs.lengthCompare ys | 如果xs的长度小于ys的长度,则返回-1。如果xs的长度大于ys的长度,则返回+1,如果它们长度相等,则返回0。即使其中一个序列是无限的,也可以使用此方法。 | +| xs.indices | xs的索引范围,从0到xs.length - 1。 | +| **索引搜索** | | +| xs indexOf x | 返回序列xs中等于x的第一个元素的索引(存在多种变体)。 | +| xs lastIndexOf x | 返回序列xs中等于x的最后一个元素的索引(存在多种变体)。 | +| xs indexOfSlice ys | 查找子序列ys,返回xs中匹配的第一个索引。 | +| xs indexOfSlice ys | 查找子序列ys,返回xs中匹配的倒数一个索引。 | +| xs indexWhere p | xs序列中满足p的第一个元素。(有多种形式) | +| xs segmentLength (p, i) | xs中,从xs(i)开始并满足条件p的元素的最长连续片段的长度。 | +| xs prefixLength p | xs序列中满足p条件的先头元素的最大个数。 | +| **加法:** | | +| x +: xs | 由序列xs的前方添加x所得的新序列。 | +| xs :+ x | 由序列xs的后方追加x所得的新序列。 | +| xs padTo (len, x) | 在xs后方追加x,直到长度达到len后得到的序列。 | +| **更新** | | +| xs patch (i, ys, r) | 将xs中第i个元素开始的r个元素,替换为ys所得的序列。 | +| xs updated (i, x) | 将xs中第i个元素替换为x后所得的xs的副本。 | +| xs(i) = x | (或写作 xs.update(i, x),仅适用于可变序列)将xs序列中第i个元素修改为x。 | +| **排序** | | +| xs.sorted | 通过使用xs中元素类型的标准顺序,将xs元素进行排序后得到的新序列。 | +| xs sortWith lt | 将lt作为比较操作,并以此将xs中的元素进行排序后得到的新序列。 | +| xs sortBy f | 将序列xs的元素进行排序后得到的新序列。参与比较的两个元素各自经f函数映射后得到一个结果,通过比较它们的结果来进行排序。 | +| **反转** | | +| xs.reverse | 与xs序列元素顺序相反的一个新序列。 | +| xs.reverseIterator | 产生序列xs中元素的反序迭代器。 | +| xs reverseMap f | 以xs的相反顺序,通过f映射xs序列中的元素得到的新序列。 | +| **比较** | | +| xs startsWith ys | 测试序列xs是否以序列ys开头(存在多种形式)。 | +| xs endsWith ys | 测试序列xs是否以序列ys结束(存在多种形式)。 | +| xs contains x | 测试xs序列中是否存在一个与x相等的元素。 | +| xs containsSlice ys | 测试xs序列中是否存在一个与ys相同的连续子序列。 | +| (xs corresponds ys)(p) | 测试序列xs与序列ys中对应的元素是否满足二元的判断式p。 | +| **多集操作** | | +| xs intersect ys | 序列xs和ys的交集,并保留序列xs中的顺序。 | +| xs diff ys | 序列xs和ys的差集,并保留序列xs中的顺序。 | +| xs union ys | 并集;同xs ++ ys。 | +| xs.distinct | 不含重复元素的xs的子序列。 | +| | | + + +特性(trait) [Seq](http://www.scala-lang.org/api/current/scala/collection/Seq.html) 具有两个子特征(subtrait) [LinearSeq](http://www.scala-lang.org/api/current/scala/collection/IndexedSeq.html)和[IndexedSeq](http://www.scala-lang.org/api/current/scala/collection/IndexedSeq.html)。它们不添加任何新的操作,但都提供不同的性能特点:线性序列具有高效的 head 和 tail 操作,而索引序列具有高效的apply, length, 和 (如果可变) update操作。 + +常用线性序列有 `scala.collection.immutable.List`和`scala.collection.immutable.Stream`。常用索引序列有 `scala.Array scala.collection.mutable.ArrayBuffer`。Vector 类提供一个在索引访问和线性访问之间有趣的折中。它同时具有高效的恒定时间的索引开销,和恒定时间的线性访问开销。正因为如此,对于混合访问模式,vector是一个很好的基础。后面将详细介绍vector。 + +## 缓冲器 + +Buffers是可变序列一个重要的种类。它们不仅允许更新现有的元素,而且允许元素的插入、移除和在buffer尾部高效地添加新元素。buffer 支持的主要新方法有:用于在尾部添加元素的 `+=` 和 `++=`;用于在前方添加元素的`+=: `和` ++=:` ;用于插入元素的 `insert`和`insertAll`;以及用于删除元素的` remove` 和 `-=`。如下表所示。 + +ListBuffer和ArrayBuffer是常用的buffer实现 。顾名思义,ListBuffer依赖列表(List),支持高效地将它的元素转换成列表。而ArrayBuffer依赖数组(Array),能快速地转换成数组。 + +## Buffer类的操作 + +| WHAT IT IS | WHAT IT DOES | +|--------------------- | -----------------------| +| **加法:** | | +| buf += x | 将元素x追加到buffer,并将buf自身作为结果返回。 | +| buf += (x, y, z) | 将给定的元素追加到buffer。 | +| buf ++= xs | 将xs中的所有元素追加到buffer。 | +| x +=: buf | 将元素x添加到buffer的前方。 | +| xs ++=: buf | 将xs中的所有元素都添加到buffer的前方。 | +| buf insert (i, x) | 将元素x插入到buffer中索引为i的位置。 | +| buf insertAll (i, xs) | 将xs的所有元素都插入到buffer中索引为i的位置。 | +| **移除:** | | +| buf -= x | 将元素x从buffer中移除。 | +| buf remove i | 将buffer中索引为i的元素移除。 | +| buf remove (i, n) | 将buffer中从索引i开始的n个元素移除。 | +| buf trimStart n | 移除buffer中的前n个元素。 | +| buf trimEnd n | 移除buffer中的后n个元素。 | +| buf.clear() | 移除buffer中的所有元素。 | +| **克隆:** | | +| buf.clone | 与buf具有相同元素的新buffer。 | diff --git a/_zh-cn/overviews/collections/sets.md b/_zh-cn/overviews/collections/sets.md new file mode 100644 index 0000000000..6694dfb2cc --- /dev/null +++ b/_zh-cn/overviews/collections/sets.md @@ -0,0 +1,150 @@ +--- +layout: multipage-overview +title: 集合 + +discourse: false + +partof: collections +overview-name: Collections + +num: 6 +language: zh-cn +--- + + +集合是不包含重复元素的可迭代对象。下面的通用集合表和可变集合表中概括了集合类型适用的运算。分为几类: + +* **测试型的方法:**`contains`,`apply`,`subsetOf`。`contains` 方法用于判断集合是否包含某元素。集合的 `apply` 方法和 `contains` 方法的作用相同,因此 `set(elem)` 等同于 `set contains elem`。这意味着集合对象的名字能作为其自身是否包含某元素的测试函数。 + +例如 + + val fruit = Set("apple", "orange", "peach", "banana") + fruit: scala.collection.immutable.Set[java.lang.String] = + Set(apple, orange, peach, banana) + scala> fruit("peach") + res0: Boolean = true + scala> fruit("potato") + res1: Boolean = false + +* **加法类型的方法:** `+` 和 `++`。添加一个或多个元素到集合中,产生一个新的集合。 +* **减法类型的方法:** `-` 、`--`。它们实现从一个集合中移除一个或多个元素,产生一个新的集合。 +* **Set运算包括并集、交集和差集**。每一种运算都存在两种书写形式:字母和符号形式。字母形式:`intersect`、`union` 和 `diff`,符号形式:`&`、`|` 和 `&~`。事实上,`Set` 中继承自 `Traversable` 的 `++` 也能被看做 `union` 或|的另一个别名。区别是,`++` 的参数为 `Traversable` 对象,而 `union` 和 `|` 的参数是集合。 + +## Set 类的操作 + +| WHAT IT IS | WHAT IT DOES | +|------------------------|--------------------------| +| **实验代码:** | | +| `xs contains x` | 测试 `x` 是否是 `xs` 的元素。 | +| `xs(x)` | 与 `xs contains x` 相同。 | +| `xs subsetOf ys` | 测试 `xs` 是否是 `ys` 的子集。 | +| **加法:** | | +| `xs + x` | 包含 `xs` 中所有元素以及 `x` 的集合。 | +| `xs + (x, y, z)` | 包含 `xs` 中所有元素及附加元素的集合 | +| `xs ++ ys` | 包含 `xs` 中所有元素及 `ys` 中所有元素的集合 | +| **移除:** | | +| `xs - x` | 包含 `xs` 中除x以外的所有元素的集合。 | +| `xs - x` | 包含 `xs` 中除去给定元素以外的所有元素的集合。 | +| `xs -- ys` | 集合内容为:`xs` 中所有元素,去掉 `ys` 中所有元素后剩下的部分。 | +| `xs.empty` | 与 `xs` 同类的空集合。 | +| **二值操作:** | | +| `xs & ys` | 集合 `xs` 和 `ys` 的交集。 | +| `xs intersect ys` | 等同于 `xs & ys`。 | +| xs | ys | 集合 `xs` 和 `ys` 的并集。 | +| `xs union ys` | 等同于 xs | ys。 | +| `xs &~ ys` | 集合 `xs` 和 `ys` 的差集。 | +| `xs diff ys` | 等同于 `xs &~ ys`。 | + + +可变集合提供加法类方法,可以用来添加、删除或更新元素。下面对这些方法做下总结。 + +## mutable.Set 类的操作 + +| WHAT IT IS | WHAT IT DOES | +|------------------|------------------------| +| **加法:** | | +| `xs += x` | 把元素 `x` 添加到集合 `xs` 中。该操作有副作用,它会返回左操作符,这里是 `xs` 自身。 | +| `xs += (x, y, z)` | 添加指定的元素到集合 `xs` 中,并返回 `xs` 本身。(同样有副作用) | +| `xs ++= ys` | 添加集合 `ys` 中的所有元素到集合 `xs` 中,并返回 `xs` 本身。(表达式有副作用) | +| `xs add x` | 把元素 `x` 添加到集合 `xs` 中,如集合 `xs` 之前没有包含 `x`,该操作返回 `true`,否则返回 `false`。 | +| **移除:** | | +| `xs -= x` | 从集合 `xs` 中删除元素 `x`,并返回 `xs` 本身。(表达式有副作用) | +| `xs -= (x, y, z)` | 从集合 `xs` 中删除指定的元素,并返回 `xs` 本身。(表达式有副作用) | +| `xs --= ys` | 从集合 `xs` 中删除所有属于集合 `ys` 的元素,并返回 `xs` 本身。(表达式有副作用) | +| `xs remove x` | 从集合 `xs` 中删除元素 `x` 。如之前 `xs` 中包含了 `x` 元素,返回 `true`,否则返回 `false`。 | +| `xs retain p` | 只保留集合 `xs` 中满足条件 `p` 的元素。 | +| `xs.clear()` | 删除集合 `xs` 中的所有元素。 | +| **更新:** | | +| `xs(x) = b` | ( 同 `xs.update(x, b)` )参数 `b` 为布尔类型,如果值为 `true` 就把元素x加入集合 `xs`,否则从集合 `xs` 中删除 `x`。 | +| **克隆:** | | +| `xs.clone` | 产生一个与 `xs` 具有相同元素的可变集合。 | + + +与不变集合一样,可变集合也提供了`+`和`++`操作符来添加元素,`-`和`--`用来删除元素。但是这些操作在可变集合中通常很少使用,因为这些操作都要通过集合的拷贝来实现。可变集合提供了更有效率的更新方法,`+=`和`-=`。 `s += elem`,添加元素elem到集合s中,并返回产生变化后的集合作为运算结果。同样的,`s -= elem `执行从集合s中删除元素elem的操作,并返回产生变化后的集合作为运算结果。除了`+=`和`-=`之外还有从可遍历对象集合或迭代器集合中添加和删除所有元素的批量操作符`++=`和`--=`。 + +选用`+=`和`-=`这样的方法名使得我们得以用非常近似的代码来处理可变集合和不可变集合。先看一下以下处理不可变集合 `s` 的REPL会话: + + scala> var s = Set(1, 2, 3) + s: scala.collection.immutable.Set[Int] = Set(1, 2, 3) + scala> s += 4 + scala> s -= 2 + scala> s + res2: scala.collection.immutable.Set[Int] = Set(1, 3, 4) + +我们在`immutable.Set`类型的变量中使用`+=`和`-= `。诸如 `s += 4` 的表达式是 `s = s + 4 `的缩写,它的作用是,在集合 `s` 上运用方法`+`,并把结果赋回给变量 `s`。下面我们来分析可变集合上的类似操作。 + + scala> val s = collection.mutable.Set(1, 2, 3) + s: scala.collection.mutable.Set[Int] = Set(1, 2, 3) + scala> s += 4 + res3: s.type = Set(1, 4, 2, 3) + scala> s -= 2 + res4: s.type = Set(1, 4, 3) + +最后结果看起来和之前的在非可变集合上的操作非常相似;从`Set(1, 2, 3)`开始,最后得到`Set(1, 3, 4)`。然而,尽管相似,但它们在实现上其实是不同的。 这里`s += 4 `是在可变集合值s上调用`+=`方法,它会改变 `s` 的内容。同样的,`s -= 2` 也是在s上调用 `-= `方法,也会修改 `s` 集合的内容。 + +通过比较这两种方式得出一个重要的原则。我们通常能用一个非可变集合的变量来替换可变集合的常量,反之亦然。这一原则至少在没有别名的引用添加到Collection时起作用。别名引用主要是用来观察操作在Collection上直接做的修改还是生成了一个新的Collection。 + +可变集合同样提供作为 `+=` 和 `-=` 的变型方法,`add` 和 `remove`,它们的不同之处在于 `add` 和 `remove` 会返回一个表明运算是否对集合有作用的Boolean值 + +目前可变集合默认使用哈希表来存储集合元素,非可变集合则根据元素个数的不同,使用不同的方式来实现。空集用单例对象来表示。元素个数小于等于4的集合可以使用单例对象来表达,元素作为单例对象的字段来存储。 元素超过4个,非可变集合就用哈希前缀树(hash trie)来实现。 + +采用这种表示方法,较小的不可变集合(元素数不超过4)往往会比可变集合更加紧凑和高效。所以,在处理小尺寸的集合时,不妨试试不可变集合。 + +集合的两个特质是 `SortedSet` 和 `BitSet`。 + +## 有序集(SortedSet) + + [SortedSet](http://www.scala-lang.org/api/current/scala/collection/SortedSet.html) 是指以特定的顺序(这一顺序可以在创建集合之初自由的选定)排列其元素(使用iterator或foreach)的集合。 [SortedSet](http://www.scala-lang.org/api/current/scala/collection/SortedSet.html) 的默认表示是有序二叉树,即左子树上的元素小于所有右子树上的元素。这样,一次简单的顺序遍历能按增序返回集合中的所有元素。Scala的类 `immutable.TreeSet` 使用红黑树实现,它在维护元素顺序的同时,也会保证二叉树的平衡,即叶节点的深度差最多为1。 + +创建一个空的 [TreeSet](http://www.scala-lang.org/api/current/scala/collection/immutable/TreeSet.html) ,可以先定义排序规则: + + scala> val myOrdering = Ordering.fromLessThan[String](_ > _) + myOrdering: scala.math.Ordering[String] = ... + +然后,用这一排序规则创建一个空的树集: + + scala> TreeSet.empty(myOrdering) + res1: scala.collection.immutable.TreeSet[String] = TreeSet() + +或者,你也可以不指定排序规则参数,只需要给定一个元素类型或空集合。在这种情况下,将使用此元素类型默认的排序规则。 + + scala> TreeSet.empty[String] + res2: scala.collection.immutable.TreeSet[String] = TreeSet() + +如果通过已有的TreeSet来创建新的集合(例如,通过串联或过滤操作),这些集合将和原集合保持相同的排序规则。例如, + + scala> res2 + ("one", "two", "three", "four") + res3: scala.collection.immutable.TreeSet[String] = TreeSet(four, one, three, two) + +有序集合同样支持元素的范围操作。例如,range方法返回从指定起始位置到结束位置(不含结束元素)的所有元素,from方法返回大于等于某个元素的所有元素。调用这两种方法的返回值依然是有序集合。例如: + + scala> res3 range ("one", "two") + res4: scala.collection.immutable.TreeSet[String] = TreeSet(one, three) + scala> res3 from "three" + res5: scala.collection.immutable.TreeSet[String] = TreeSet(three, two) + +## 位集合(Bitset) + +位集合是由单字或多字的紧凑位实现的非负整数的集合。其内部使用 `Long` 型数组来表示。第一个 `Long` 元素表示的范围为0到63,第二个范围为64到127,以此类推(值为0到127的非可变位集合通过直接将值存储到第一个或第两个 `Long` 字段的方式,优化掉了数组处理的消耗)。对于每个 `Long`,如果有相应的值包含于集合中则它对应的位设置为1,否则该位为0。这里遵循的规律是,位集合的大小取决于存储在该集合的最大整数的值的大小。假如N是为集合所要表示的最大整数,则集合的大小就是 `N/64` 个长整形字,或者 `N/8` 个字节,再加上少量额外的状态信息字节。 + +因此当位集合包含的元素值都比较小时,它比其他的集合类型更紧凑。位集合的另一个优点是它的 `contains` 方法(成员测试)、`+=` 运算(添加元素)、`-=` 运算(删除元素)都非常的高效。 diff --git a/_zh-cn/overviews/collections/strings.md b/_zh-cn/overviews/collections/strings.md new file mode 100644 index 0000000000..2743dc3481 --- /dev/null +++ b/_zh-cn/overviews/collections/strings.md @@ -0,0 +1,29 @@ +--- +layout: multipage-overview +title: 字符串 + +discourse: false + +partof: collections +overview-name: Collections + +num: 11 +language: zh-cn +--- + +像数组,字符串不是直接的序列,但是他们可以转换为序列,并且他们也支持所有的在字符串上的序列操作这里有些例子让你可以理解在字符串上操作。 + + scala> val str = "hello" + str: java.lang.String = hello + scala> str.reverse + res6: String = olleh + scala> str.map(_.toUpper) + res7: String = HELLO + scala> str drop 3 + res8: String = lo + scala> str slice (1, 4) + res9: String = ell + scala> val s: Seq[Char] = str + s: Seq[Char] = WrappedString(h, e, l, l, o) + +这些操作依赖于两种隐式转换。第一种,低优先级转换映射一个String到WrappedString,它是`immutable.IndexedSeq`的子类。在上述代码中这种转换应用在一个string转换为一个Seq。另一种,高优先级转换映射一个string到StringOps 对象,从而在immutable 序列到strings上增加了所有的方法。在上面的例子里,这种隐式转换插入在reverse,map,drop和slice的方法调用中。 diff --git a/_zh-cn/overviews/collections/trait-iterable.md b/_zh-cn/overviews/collections/trait-iterable.md new file mode 100644 index 0000000000..066639c768 --- /dev/null +++ b/_zh-cn/overviews/collections/trait-iterable.md @@ -0,0 +1,68 @@ +--- +layout: multipage-overview +title: Trait Iterable + +discourse: false + +partof: collections +overview-name: Collections + +num: 4 +language: zh-cn +--- + +自下而上的容器(collection)层次结构具有可迭代的Trait。Trait的所有方法可定义为一个抽象方法,逐个生成容器(collection)元素迭代器。Traversable Trait的foreach方法实现了迭代器的Iterable。下面是具体的实现。 + + def foreach[U](f: Elem => U): Unit = { + val it = iterator + while (it.hasNext) f(it.next()) + } + +许多Iterable 的子类覆写了Iteable的foreach标准实现,因为它们提供了更多有效的实现。记住,由于性能问题,foreach是Traversable所有操作能够实现的基础。 + +Iterable有两个方法返回迭代器:grouped和sliding。然而,这些迭代器返回的不是单个元素,而是原容器(collection)元素的全部子序列。这些最大的子序列作为参数传给这些方法。grouped方法返回元素的增量分块,sliding方法生成一个滑动元素的窗口。两者之间的差异通过REPL的作用能够清楚看出。 + + scala> val xs = List(1, 2, 3, 4, 5) + xs: List[Int] = List(1, 2, 3, 4, 5) + scala> val git = xs grouped 3 + git: Iterator[List[Int]] = non-empty iterator + scala> git.next() + res3: List[Int] = List(1, 2, 3) + scala> git.next() + res4: List[Int] = List(4, 5) + scala> val sit = xs sliding 3 + sit: Iterator[List[Int]] = non-empty iterator + scala> sit.next() + res5: List[Int] = List(1, 2, 3) + scala> sit.next() + res6: List[Int] = List(2, 3, 4) + scala> sit.next() + res7: List[Int] = List(3, 4, 5) + +当只有一个迭代器可用时,Trait Iterable增加了一些其他方法,为了能被有效的实现的可遍历的情况。这些方法总结在下面的表中。 + +## Trait Iterable操作 + +| WHAT IT IS | WHAT IT DOES | +|--------------|--------------| +| **抽象方法:** | | +| xs.iterator | xs迭代器生成的每一个元素,以相同的顺序就像foreach一样遍历元素。 | +| **其他迭代器:** | | +| xs grouped size | 一个迭代器生成一个固定大小的容器(collection)块。 | +| xs sliding size | 一个迭代器生成一个固定大小的滑动窗口作为容器(collection)的元素。 | +| **子容器(Subcollection):** | | +| xs takeRight n | 一个容器(collection)由xs的最后n个元素组成(或,若定义的元素是无序,则由任意的n个元素组成)。 | +| xs dropRight n | 一个容器(collection)由除了xs 被取走的(执行过takeRight ()方法)n个元素外的其余元素组成。 | +| **拉链方法(Zippers):** | | +| xs zip ys | 把一对容器 xs和ys的包含的元素合成到一个iterabale。 | +| xs zipAll (ys, x, y) | 一对容器 xs 和ys的相应的元素合并到一个iterable ,实现方式是通过附加的元素x或y,把短的序列被延展到相对更长的一个上。 | +| xs.zip WithIndex | 把一对容器xs和它的序列,所包含的元素组成一个iterable 。 | +| **比对:** | | +| xs sameElements ys | 测试 xs 和 ys 是否以相同的顺序包含相同的元素。 | + + +在Iterable下的继承层次结构你会发现有三个traits:[Seq](https://www.scala-lang.org/api/current/scala/collection/Seq.html),[Set](https://www.scala-lang.org/api/current/scala/collection/Set.html),和 [Map](https://www.scala-lang.org/api/current/scala/collection/Map.html)。这三个Traits有一个共同的特征,它们都实现了[PartialFunction](https://www.scala-lang.org/api/current/scala/PartialFunction.html) trait以及它的应用和isDefinedAt 方法。然而,每一个trait实现的 `PartialFunction` 方法却各不相同。 + +例如序列,使用用的是位置索引,它里面的元素的总是从0开始编号。即`Seq(1, 2, 3)(1) `为2。例如sets,使用的是成员测试。例如`Set('a', 'b', 'c')('b') `算出来的是true,而`Set()('a')`为false。最后,maps使用的是选择。比如`Map('a' -> 1, 'b' -> 10, 'c' -> 100)('b')` 得到的是10。 + +接下来,我们将详细的介绍三种类型的容器(collection)。 diff --git a/_zh-cn/overviews/collections/trait-traversable.md b/_zh-cn/overviews/collections/trait-traversable.md new file mode 100644 index 0000000000..d239bcadb4 --- /dev/null +++ b/_zh-cn/overviews/collections/trait-traversable.md @@ -0,0 +1,123 @@ +--- +layout: multipage-overview +title: Trait Traversable + +discourse: false + +partof: collections +overview-name: Collections + +num: 3 +language: zh-cn +--- + +Traversable(遍历)是容器(collection)类的最高级别特性,它唯一的抽象操作是foreach: + +`def foreach[U](f: Elem => U) ` + +需要实现Traversable的容器(collection)类仅仅需要定义与之相关的方法,其他所有方法可都可以从Traversable中继承。 + +foreach方法用于遍历容器(collection)内的所有元素和每个元素进行指定的操作(比如说f操作)。操作类型是Elem => U,其中Elem是容器(collection)中元素的类型,U是一个任意的返回值类型。对f的调用仅仅是容器遍历的副作用,实际上所有函数f的计算结果都被foreach抛弃了。 + +Traversable同时定义的很多具体方法,如下表所示。这些方法可以划分为以下类别: + +- **相加操作++(addition)**表示把两个traversable对象附加在一起或者把一个迭代器的所有元素添加到traversable对象的尾部。 + +- **Map**操作有map,flatMap和collect,它们可以通过对容器中的元素进行某些运算来生成一个新的容器。 + +- **转换器(Conversion)**操作包括toArray,toList,toIterable,toSeq,toIndexedSeq,toStream,toSet,和toMap,它们可以按照某种特定的方法对一个Traversable 容器进行转换。等容器类型已经与所需类型相匹配的时候,所有这些转换器都会不加改变的返回该容器。例如,对一个list使用toList,返回的结果就是list本身。 + +- **拷贝(Copying)**操作有copyToBuffer和copyToArray。从字面意思就可以知道,它们分别用于把容器中的元素元素拷贝到一个缓冲区或者数组里。 + +- **Size info**操作包括有isEmpty,nonEmpty,size和hasDefiniteSize。Traversable容器有有限和无限之分。比方说,自然数流Stream.from(0)就是一个无限的traversable 容器。hasDefiniteSize方法能够判断一个容器是否可能是无限的。若hasDefiniteSize返回值为ture,容器肯定有限。若返回值为false,根据完整信息才能判断容器(collection)是无限还是有限。 + +- **元素检索(Element Retrieval)**操作有head,last,headOption,lastOption和find。这些操作可以查找容器的第一个元素或者最后一个元素,或者第一个符合某种条件的元素。注意,尽管如此,但也不是所有的容器都明确定义了什么是“第一个”或”最后一个“。例如,通过哈希值储存元素的哈希集合(hashSet),每次运行哈希值都会发生改变。在这种情况下,程序每次运行都可能会导致哈希集合的”第一个“元素发生变化。如果一个容器总是以相同的规则排列元素,那这个容器是有序的。大多数容器都是有序的,但有些不是(例如哈希集合)-- 排序会造成一些额外消耗。排序对于重复性测试和辅助调试是不可或缺的。这就是为什么Scala容器中的所有容器类型都把有序作为可选项。例如,带有序性的HashSet就是LinkedHashSet。 + +- **子容器检索(sub-collection Retrieval)**操作有tail,init,slice,take,drop,takeWhilte,dropWhile,filter,filteNot和withFilter。它们都可以通过范围索引或一些论断的判断返回某些子容器。 + +- **拆分(Subdivision)**操作有splitAt,span,partition和groupBy,它们用于把一个容器(collection)里的元素分割成多个子容器。 + +- **元素测试(Element test)**包括有exists,forall和count,它们可以用一个给定论断来对容器中的元素进行判断。 + +- **折叠(Folds)**操作有foldLeft,foldRight,/:,:\,reduceLeft和reduceRight,用于对连续性元素的二进制操作。 + +- **特殊折叠(Specific folds)**包括sum, product, min, max。它们主要用于特定类型的容器(数值或比较)。 + +- **字符串(String)**操作有mkString,addString和stringPrefix,可以将一个容器通过可选的方式转换为字符串。 + +- **视图(View)**操作包含两个view方法的重载体。一个view对象可以当作是一个容器客观地展示。接下来将会介绍更多有关视图内容。 + +## Traversable对象的操作 + +| WHAT IT IS |WHAT IT DOES | +|------------------------|------------------------------| +| **抽象方法:** | | +| xs foreach f | 对xs中的每一个元素执行函数f | +| **加运算(Addition):** | | +| xs ++ ys | 生成一个由xs和ys中的元素组成容器。ys是一个TraversableOnce容器,即Taversable类型或迭代器。 +| **Maps:** | | +| xs map f | 通过函数xs中的每一个元素调用函数f来生成一个容器。 | +| xs flatMap f | 通过对容器xs中的每一个元素调用作为容器的值函数f,在把所得的结果连接起来作为一个新的容器。 | +| xs collect f | 通过对每个xs中的符合定义的元素调用偏函数f,并把结果收集起来生成一个集合。 | +| **转换(Conversions):** | | +| xs.toArray | 把容器转换为一个数组 | +| xs.toList | 把容器转换为一个list | +| xs.toIterable | 把容器转换为一个迭代器。 | +| xs.toSeq | 把容器转换为一个序列 | +| xs.toIndexedSeq | 把容器转换为一个索引序列 | +| xs.toStream | 把容器转换为一个延迟计算的流。 | +| xs.toSet | 把容器转换为一个集合(Set)。 | +| xs.toMap | 把由键/值对组成的容器转换为一个映射表(map)。如果该容器并不是以键/值对作为元素的,那么调用这个操作将会导致一个静态类型的错误。 | +| **拷贝(Copying):** | | +| xs copyToBuffer buf | 把容器的所有元素拷贝到buf缓冲区。 | +| xs copyToArray(arr, s, n) | 拷贝最多n个元素到数组arr的坐标s处。参数s,n是可选项。 | +| **大小判断(Size info):** | | +| xs.isEmpty | 测试容器是否为空。 | +| xs.nonEmpty | 测试容器是否包含元素。 | +| xs.size | 计算容器内元素的个数。 | +| xs.hasDefiniteSize | 如果xs的大小是有限的,则为true。 | +| **元素检索(Element Retrieval):** | | +| xs.head | 返回容器内第一个元素(或其他元素,若当前的容器无序)。 | +| xs.headOption | xs选项值中的第一个元素,若xs为空则为None。 | +| xs.last | 返回容器的最后一个元素(或某个元素,如果当前的容器无序的话)。 | +| xs.lastOption | xs选项值中的最后一个元素,如果xs为空则为None。 | +| xs find p | 查找xs中满足p条件的元素,若存在则返回第一个元素;若不存在,则为空。 | +| **子容器(Subcollection):** | | +| xs.tail | 返回由除了xs.head外的其余部分。 | +| xs.init | 返回除xs.last外的其余部分。 | +| xs slice (from, to) | 返回由xs的一个片段索引中的元素组成的容器(从from到to,但不包括to)。 | +| xs take n | 由xs的第一个到第n个元素(或当xs无序时任意的n个元素)组成的容器。 | +| xs drop n | 由除了xs take n以外的元素组成的容器。 | +| xs takeWhile p | 容器xs中最长能够满足断言p的前缀。 | +| xs dropWhile p | 容器xs中除了xs takeWhile p以外的全部元素。 | +| xs filter p | 由xs中满足条件p的元素组成的容器。 | +| xs withFilter p | 这个容器是一个不太严格的过滤器。子容器调用map,flatMap,foreach和withFilter只适用于xs中那些的满足条件p的元素。 | +| xs filterNot p | 由xs中不满足条件p的元素组成的容器。 | +| **拆分(Subdivision):** | | +| xs splitAt n | 把xs从指定位置的拆分成两个容器(xs take n和xs drop n)。 | +| xs span p | 根据一个断言p将xs拆分为两个容器(xs takeWhile p, xs.dropWhile p)。 | +| xs partition p | 把xs分割为两个容器,符合断言p的元素赋给一个容器,其余的赋给另一个(xs filter p, xs.filterNot p)。 | +| xs groupBy f | 根据判别函数f把xs拆分一个到容器(collection)的map中。 | +| **条件元素(Element Conditions):** | | +| xs forall p | 返回一个布尔值表示用于表示断言p是否适用xs中的所有元素。 | +| xs exists p | 返回一个布尔值判断xs中是否有部分元素满足断言p。 | +| xs count p | 返回xs中符合断言p条件的元素个数。 | +| **折叠(Fold):** | | +| (z /: xs)(op) | 在xs中,对由z开始从左到右的连续元素应用二进制运算op。 | +| (xs :\ z)(op) | 在xs中,对由z开始从右到左的连续元素应用二进制运算op | +| xs.foldLeft(z)(op) | 与(z /: xs)(op)相同。 | +| xs.foldRight(z)(op) | 与 (xs :\ z)(op)相同。 | +| xs reduceLeft op | 非空容器xs中的连续元素从左至右调用二进制运算op。 | +| xs reduceRight op | 非空容器xs中的连续元素从右至左调用二进制运算op。 | +| **特殊折叠(Specific Fold):** | | +| xs.sum | 返回容器xs中数字元素的和。 | +| xs.product | xs返回容器xs中数字元素的积。 | +| xs.min | 容器xs中有序元素值中的最小值。 | +| xs.max | 容器xs中有序元素值中的最大值。 | +| **字符串(String):** | | +| xs addString (b, start, sep, end) | 把一个字符串加到StringBuilder对象b中,该字符串显示为将xs中所有元素用分隔符sep连接起来并封装在start和end之间。其中start,end和sep都是可选的。 | +| xs mkString (start, sep, end) | 把容器xs转换为一个字符串,该字符串显示为将xs中所有元素用分隔符sep连接起来并封装在start和end之间。其中start,end和sep都是可选的。 | +| xs.stringPrefix | 返回一个字符串,该字符串是以容器名开头的xs.toString。 | +| **视图(View):** | | +| xs.view | 通过容器xs生成一个视图。 | +| xs view (from, to) | 生成一个表示在指定索引范围内的xs元素的视图。 | diff --git a/_zh-cn/overviews/collections/views.md b/_zh-cn/overviews/collections/views.md new file mode 100644 index 0000000000..a7e076c5eb --- /dev/null +++ b/_zh-cn/overviews/collections/views.md @@ -0,0 +1,127 @@ +--- +layout: multipage-overview +title: 视图 + +discourse: false + +partof: collections +overview-name: Collections + +num: 14 +language: zh-cn +--- + +各种容器类自带一些用于开发新容器的方法。例如map、filter和++。我们将这类方法称为转换器(transformers),喂给它们一个或多个容器,它们就会输入一个新容器。 + +有两个主要途径实现转换器(transformers)。一个途径叫紧凑法,就是一个容器及其所有单元构造成这个转换器(transformers)。另一个途径叫松弛法或惰性法(lazy),就是一个容器及其所有单元仅仅是构造了结果容器的代理,并且结果容器的每个单元都是按单一需求构造的。 + +作为一个松弛法转换器的例子,分析下面的 lazy map操作: + + def lazyMap[T, U](coll: Iterable[T], f: T => U) = new Iterable[U] { + def iterator = coll.iterator map f + } + +注意lazyMap构造了一个没有遍历容器coll(collection coll)所有单元的新容器Iterable。当需要时,函数f 可作用于一个该新容器的迭代器单元。 + +除了Stream的转换器是惰性实现的外,Scala的其他容器默认都是用紧凑法实现它们的转换器。 +然而,通常基于容器视图,可将容器转换成惰性容器,反之亦可。视图是代表一些基容器但又可以惰性得构成转换器(transformers)的一种特殊容器。 + +从容器转换到其视图,可以使用容器相应的视图方法。如果xs是个容器,那么xs.view就是同一个容器,不过所有的转换器都是惰性的。若要从视图转换回紧凑型容器,可以使用强制性方法。 + +让我们看一个例子。假设你有一个带有int型数据的vector对象,你想用map函数对它进行两次连续的操作 + + scala> val v = Vector(1 to 10: _*) + v: scala.collection.immutable.Vector[Int] = + Vector(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + scala> v map (_ + 1) map (_ * 2) + res5: scala.collection.immutable.Vector[Int] = + Vector(4, 6, 8, 10, 12, 14, 16, 18, 20, 22) + +在最后一条语句中,表达式`v map (_ + 1) ` 构建了一个新的vector对象,该对象被map第二次调用`(_ * 2)`而转换成第3个vector对象。很多情况下,从map的第一次调用构造一个中间结果有点浪费资源。上述示例中,将map的两次操作结合成一次单一的map操作执行得会更快些。如果这两次操作同时可行,则可亲自将它们结合成一次操作。但通常,数据结构的连续转换出现在不同的程序模块里。融合那些转换将会破坏其模块性。更普遍的做法是通过把vector对象首先转换成其视图,然后把所有的转换作用于该视图,最后强制将视图转换成vector对象,从而避开出现中间结果这种情况。 + + scala> (v.view map (_ + 1) map (_ * 2)).force + res12: Seq[Int] = Vector(4, 6, 8, 10, 12, 14, 16, 18, 20, 22) + +让我们按这个步骤一步一步再做一次: + + scala> val vv = v.view + vv: scala.collection.SeqView[Int,Vector[Int]] = + SeqView(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + + v.view 给出了SeqView对象,它是一个延迟计算的Seq。SeqView有两个参数,第一个是整型(Int)表示视图单元的类型。第二个Vector[Int]数组表示当需要强制将视图转回时构造函数的类型。 + +将第一个map 转换成视图可得到: + + scala> vv map (_ + 1) + res13: scala.collection.SeqView[Int,Seq[_]] = SeqViewM(...) + +map的结果是输出`SeqViewM(...)`的值。实质是记录函数`map (_ + 1)`应用在vector v数组上的封装。除非视图被强制转换,否则map不会被执行。然而,`SeqView `后面的 `‘’M‘’`表示这个视图包含一个map操作。其他字母表示其他延迟操作。比如`‘’S‘’`表示一个延迟的slice操作,而`‘’R‘’`表示reverse操作。现在让我们将第二个map操作作用于最后的结果。 + + scala> res13 map (_ * 2) + res14: scala.collection.SeqView[Int,Seq[_]] = SeqViewMM(...) + +现在得到了包含2个map操作的`SeqView`对象,这将输出两个`‘’M‘’: SeqViewMM(...)`。最后强制转换最后结果: + + scala> res14.force res15: Seq[Int] = Vector(4, 6, 8, 10, 12, 14, 16, 18, 20, 22) + +两个存储函数应用于强制操作的执行部分并构造一个新的矢量数组。这样,没有中间数据结构是必须的。 + +需要注意的是静态类型的最终结果是Seq对象而不是Vector对象。跟踪类型后我们看到一旦第一个延迟map被应用,就会得到一个静态类型的`SeqViewM[Int, Seq[_]`。就是说,应用于特定序列类型的矢量数组的"knowledge"会被丢失。一些类的视图的实现需要大量代码,于是Scala 容器链接库仅主要为一般的容器类型而不是特殊功能(一个例外是数组:将数组操作延迟会再次给予静态类型数组的结果)的实现提供视图。 + +有2个理由使您考虑使用视图。首先是性能。你已经看到,通过转换容器为视图可以避免中间结果。这些节省是非常重要的。就像另一个例子,考虑到在一个单词列表找到第一个回文问题。回文就是顺读或倒读都一样的单词。以下是必要的定义: + + def isPalindrome(x: String) = x == x.reverse + def findPalidrome(s: Seq[String]) = s find isPalindrome + +现在,假设你有一个很长序列的单词表,你想在这个序列的第一百万个字内找到回文。你能复用findPalidrome么?当然,你可以写: + + findPalindrome(words take 1000000) + +这很好地解决了两个方面问题:提取序列的第一个百万单词,找到一个回文结构。但缺点是,它总是构建由一百万个字组成的中间序列,即使该序列的第一个单词已经是一个回文。所以可能,999 '999个单词在根本没被检查就复制到中间的结果(数据结构中)。很多程序员会在这里放弃转而编写给定参数前缀的寻找回文的自定义序列。但对于视图(views),这没必要。简单地写: + + findPalindrome(words.view take 1000000) + +这同样是一个很好的分选,但不是一个序列的一百万个元素,它只会构造一个轻量级的视图对象。这样,你无需在性能和模块化之间衡量取舍。 + +第二个案例适用于遍历可变序列的视图。许多转换器函数在那些视图提供视窗给部分元素可以非常规更新的原始序列。通过一个示例看看这种情形。让我们假定有一个数组arr: + + scala> val arr = (0 to 9).toArray + arr: Array[Int] = Array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9) + +你可以在arr数组视图的一部分里创建一个子窗体。 + + scala> val subarr = arr.view.slice(3, 6) + subarr: scala.collection.mutable.IndexedSeqView[ + Int,Array[Int]] = IndexedSeqViewS(...) + +这里给出了一个视图subarr指向从数组arr的第三个元素开始的5个元素组成的子数组。这个视图没有拷贝这些元素,而只是提供了它们的一个映射。现在,假设你有一个修改序列元素的方法。例如,下面的negate方法将对给定整数序列的所有元素取反操作: + + scala> def negate(xs: collection.mutable.Seq[Int]) = + for (i <- 0 until xs.length) xs(i) = -xs(i) + negate: (xs: scala.collection.mutable.Seq[Int])Unit + +假定现在你要对数组arr里从第3个元素开始的5个元素取反操作。你能够使用negate方法来做么?使用视图,就这么简单: + + scala> negate(subarr) + scala> arr + res4: Array[Int] = Array(0, 1, 2, -3, -4, -5, 6, 7, 8, 9) + +看看发生什么了,negate方法改变了从数组arr截取元素生成的数组subarr里面的所有元素。你再次看到视图(views)在保持模块化方面的功效。上面的代码完美地分离了使用方法时如何安排下标顺序和使用什么方法的问题。 + +看了这些漂亮的视图应用示例你可能会困惑于为什么怎么还是会有 strict型容器存在了?一个原因是 lazy型容器性能不总是优于strict型容器的。对于较小的容器在视图里创建和关闭应用附加开销通常是大于从避免中间数据结构的增益。一个更重要的原因是,如果延迟操作有副作用,可能导致视图非常混乱。 + +这里有一个使用2.8版本以前的Scala的几个用户的例子。在这些版本中, Range类型是延迟型的。所以它表现的效果就像一个视图。人们试图创造一些对象像这样: + + val actors = for (i <- 1 to 10) yield actor { ... } + +令他们吃惊的是,没有对象被执行。甚至在后面括号里的代码里无法创建和启动对象方法。对于为什么什么都没发生,记住,对上述表达式等价于map应用: + + val actors = (1 to 10) map (i => actor { ... }) + +由于先前的范围由(1~10)表现得像一个视图,map的结果又是一个视图。那就是,没有元素计算,并且,因此,没有对象的构建!对象会在整个表达的范围内被强制创建,但这并不就是对象要完成的工作。 + +为了避免这样的疑惑,Scala 2.8版容器链接库有了更严格的规定。除streams 和 views 外所有容器都是strict型的。只有一种途径将strict型容器转换成lazy型,那就是采用视图(view)方法。而唯一可逆的途径(from lazy to strict)就是采用强制。因此在Scala 2.8版里actors 对象上面的定义会像预期的那样,这将创建和启动10个actors对象。回到先前疑惑处,你可以增加一个明确的视图方法调用: + + val actors = for (i <- (1 to 10).view) yield actor { ... } + +总之,视图是协调性能和模块化的一个强大工具。但为了不被延迟利弊评估方面的纠缠,应该在2个方面对视图进行约束。要么你将在容器转换器不产生副作用的纯粹的功能代码里使用视图。要么你将它们应用在所有的修改都是明确的可变容器。最好的规避就是混合视图和操作,创建新的根接口,同时消除片面影响。 diff --git a/_zh-cn/overviews/core/actors-migration-guide.md b/_zh-cn/overviews/core/actors-migration-guide.md new file mode 100644 index 0000000000..e9a9b4b1ff --- /dev/null +++ b/_zh-cn/overviews/core/actors-migration-guide.md @@ -0,0 +1,465 @@ +--- +layout: singlepage-overview +title: Scala Actors迁移指南 + +partof: actor-migration + +language: zh-cn + +discourse: false +--- + +**Vojin Jovanovic 和 Philipp Haller 著** + +## 概述 + +从Scala的2.11.0版本开始,Scala的Actors库已经过时了。早在Scala2.10.0的时候,默认的actor库即是Akka。 + +为了方便的将Scala Actors迁移到Akka,我们提供了Actor迁移工具包(AMK)。通过在一个项目的类路径中添加scala-actors-migration.jar,AMK包含了一个针对Scala Actors扩展。此外,Akka 2.1包含一些特殊功能,比如ActorDSL singleton,可以实现更简单的转换功能,使Scala Actors代码变成Akka代码。本章内容的目的是用来指导用户完成迁移过程,并解释如何使用AMK。 + +本指南包括以下内容:在“迁移工具的局限性”章节中,我们在此概述了迁移工具的主要局限性。在“迁移概述”章节中我们描述了迁移过程和谈论了Scala的变化分布,使得迁移成为一种可能。最后,在“一步一步指导迁移到Akka”章节里,我们展示了一些迁移工作的例子,以及各个步骤,如果需要从Scala Actors迁移至Akka's actors,本节是推荐阅读的。 + +免责声明:并发代码是臭名昭著的,当出现bug时很难调试和修复。由于两个actor的不同实现,这种差异导致可能出现错误。迁移过程没一步后都建议进行完全的代码测试。 + +## 迁移工具的局限性 + +由于Akka和Scala的actor模型的完整功能不尽相同导致两者之间不能平滑地迁移。下面的列表解释了很难迁移的部分行为: + +1. 依靠终止原因和双向行为链接方法 - Scala和Akka actors有不同的故障处理和actor monitoring模型。在Scala actors模型中,如果一个相关联部分异常终止,相关联的actors终止。如果终止是显式跟踪(通过self.trapExit),actor可以从失败的actor收到终止的原因。通过Akka这个功能不能迁移到AMK。AMK允许迁移的只是[Akka monitoring](http://doc.akka.io/docs/akka/2.1.0/general/supervision.html#What_Lifecycle_Monitoring_Means)机制。Monitoring不同于连接,因为它是单向(unindirectional)的并且终止的原因是现在已知的。如果仅仅是monitoring机制是无法满足需求的,迁移的链接必须推迟到最后一刻(步骤5的迁移)。然后,当迁移到Akka,用户必须创建一个[监督层次(supervision hierarchy)](http://doc.akka.io/docs/akka/2.1.0/general/supervision.html),处理故障。 + +2. 使用restart方法——Akka不提供显式的重启actors,因此上述例子我们不能提供平滑迁移。用户必须更改系统,所以没有使用重启方法(restart method)。 + +3. 使用getState方法 - Akka actors没有显式状态,此功能无法迁移。用户代码必须没有getState调用。 + +4. 实例化后没有启动actors - Akka actors模型会在实例化后自动启动actors,所以用户不需要重塑系统来显式的在实例化后启动actors。 + +5. mailboxSize方法不存在Akka中,因此不能迁移。这种方法很少使用,很容易被删除。 + +## 迁移概述 + +### 迁移工具 + +在Scal 2.10.0 actors 是在[Scala distribution](http://www.scala-lang.org/downloads)中作为一个单独包(scala-actors.jar)存在的,并且他们的接口已被弃用。这种分布也包含在Akka actors的akka-actor.jar里。AMK同时存在Scala actors 和 akka-actor.jar之中。未来的主要版本的Scala将不包含Scala actors和AMK。 + +开始迁移,用户需要添加scala-actors.jar和scala-actors-migration.jar来构建他们的项目。添加scala-actors.jar和scala-actors-migration.jar允许使用下面描述的AMK。这些jar位于[Scala Tools](https://oss.sonatype.org/content/groups/scala-tools/org/scala-lang/)库和[Scala distribution](http://www.scala-lang.org/downloads)库中。 + +### 一步一步来迁移 + +Actor迁移工具使用起来应该有5步骤。每一步都设计为引入的基于代码的最小变化。在前四个迁移步骤的代码中将使用Scala actors来实现,并在该步完成后运行所有的系统测试。然而,方法和类的签名将被转换为与Akka相似。迁移工具在Scal方面引入了一种新的actor类型(ActWithStash)和强制执行actors的ActorRef接口。 + +该结果同样强制通过一个特殊的方法在ActorDSL 对象上创建actors。在这些步骤可以每次迁移一个actor。这降低了在同一时刻引入多个bug的可能性,同样降低了bug的复杂程度。 + +在Scala方面迁移完成后,用户应该改变import语句并变成使用Akka库。在Akka方面,ActorDSL和ActWithStash允许对Scala Actors和他们的生态系的react construct进行建模。这个步骤迁移所有actors到Akka的后端,会在系统中引入bug。一旦代码迁移到Akka,用户将能够使用Akka的所有的功能的。 + +### 一步一步指导迁移到Akka + +在这一章中,我们将通过actor迁移的5个步骤。在每一步之后的代码都要为可能的错误进行检测。在前4个步骤中可以一边迁移一个actor和一边测试功能。然而,最后一步迁移所有actors到Akka后它只能作为一个整体进行测试。在这个步骤之后系统应该具有和之前一样相同的功能,不过它将使用Akka actor库。 + +### 步骤1——万物皆是Actor + +Scala actors库提供了公共访问多个类型的actors。他们被组织在类层次结构和每个子类提供了稍微更丰富的功能。为了进一步的使迁移步骤更容易,我们将首先更改Actor类型系统中的每一个actor。这种迁移步骤很简单,因为Actor类位于层次结构的底部,并提供了广泛的功能。 + +来自Scala库的Actors应根据以下规则进行迁移: + +1. class MyServ extends Reactor[T] -> class MyServ extends Actor + +注意,反应器提供了一个额外的类型参数代表了类型的消息收到。如果用户代码中使用这些信息,那么一个需要:i)应用模式匹配与显式类型,或者ii)做一个向下的消息来自任何泛型T。 + +1. class MyServ extends ReplyReactor -> class MyServ extends Actor + +2. class MyServ extends DaemonActor -> class MyServ extends Actor + +为了为DaemonActor提供配对功能,将下列代码添加到类的定义。 + + override def scheduler: IScheduler = DaemonScheduler + +### 步骤2 - 实例化 + +在Akka中,actors可以访问只有通过ActorRef接口。ActorRef的实例可以通过在ActorDSL对象上调用actor方法或者通过调用ActorRefFactory实例的actorOf方法来获得。在Scala的AMK工具包中,我们提供了Akka ActorRef和ActorDSL的一个子集,该子集实际上是Akka库的一个单例对象(singleton object)。 + +这一步的迁移使所有actors访问通过ActorRefs。首先,我们现实如何迁移普通模式的实例化Sacla Actors。然后,我们将展示如何分别克服问题的ActorRef和Actor的不同接口。 + +#### Actor实例化 + +actor实例的转换规则(以下规则需要import scala.actors.migration._): + +1. 构造器调用实例化 + + val myActor = new MyActor(arg1, arg2) + myActor.start() + +应该被替换 + + ActorDSL.actor(new MyActor(arg1, arg2)) + +2. 用于创建Actors的DSL(译注:领域专用语言(Domain Specific Language)) + + val myActor = actor { + // actor 定义 + } +应该被替换 + + val myActor = ActorDSL.actor(new Actor { + def act() { + // actor 定义 + } + }) + +3. 从Actor Trait扩展来的对象 + + object MyActor extends Actor { + // MyActor 定义 + } + MyActor.start() +应该被替换 + + class MyActor extends Actor { + // MyActor 定义 + } + + object MyActor { + val ref = ActorDSL.actor(new MyActor) + } +所有的MyActor地想都应该被替换成MyActor.ref。 + +需要注意的是Akka actors在实例化的同时开始运行。actors创建并开始在迁移的系统的情况下,actors在不同的位置以及改变这可能会影响系统的行为,用户需要更改代码,以使得actors在实例化后立即开始执行。 + +远程actors也需要被获取作为ActorRefs。为了得到一个远程actor ActorRef需使用方法selectActorRef。 + +#### 不同的方法签名(signatures) + +至此为止我们已经改变了所有的actor实例化,返回ActorRefs,然而,我们还没有完成迁移工作。有不同的接口在ActorRefs和Actors中,因此我们需要改变在每个迁移实例上触发的方法。不幸的是,Scala Actors提供的一些方法不能迁移。对下列方法的用户需要找到一个解决方案: + +1. getState()——Akka中的actors 默认情况下由其监管actors(supervising actors)负责管理和重启。在这种情况下,一个actor的状态是不相关的。 + +2. restart() - 显式的重启一个Scala actor。在Akka中没有相应的功能。 + +所有其他Actor方法需要转换为两个ActorRef中的方法。转换是通过下面描述的规则。请注意,所有的规则需要导入以下内容: + + import scala.concurrent.duration._ + import scala.actors.migration.pattern.ask + import scala.actors.migration._ + import scala.concurrent._ +额外规则1-3的作用域定义在无限的时间需要一个隐含的超时。然而,由于Akka不允许无限超时,我们会使用100年。例如: + + implicit val timeout = Timeout(36500 days) + +规则: + +1. !!(msg: Any): Future[Any] 被?替换。这条规则会改变一个返回类型到scala.concurrent.Future这可能导致类型不匹配。由于scala.concurrent.Future比过去的返回值具有更广泛的功能,这种类型的错误可以很容易地固定在与本地修改: + + actor !! message -> respActor ? message + +2. !![A] (msg: Any, handler: PartialFunction[Any, A]): Future[A] 被?取代。处理程序可以提取作为一个单独的函数,并用来生成一个future对象结果。处理的结果应给出另一个future对象结果,就像在下面的例子: + + val handler: PartialFunction[Any, T] = ... // handler + actor !! (message, handler) -> (respActor ? message) map handler + +3. !? (msg: Any):任何被?替换都将阻塞在返回的future对象上 + + actor !? message -> + Await.result(respActor ? message, Duration.Inf) + +4. !? (msec: Long, msg: Any): Option[Any]任何被?替换都将显式的阻塞在future对象 + + actor !? (dur, message) -> + val res = respActor.?(message)(Timeout(dur milliseconds)) + val optFut = res map (Some(_)) recover { case _ => None } + Await.result(optFut, Duration.Inf) + +这里没有提到的公共方法是为了actors DSL被申明为公共的。他们只能在定义actor时使用,所以他们的这一步迁移是不相关的。 + +###第3步 - 从Actor 到 ActWithStash + +到目前为止,所有的控制器都继承自Actor trait。我们通过指定的工厂方法来实例化控制器,所有的控制器都可以通过接口ActorRef 来进行访问。现在我们需要把所有的控制器迁移的AMK 的 ActWithStash 类上。这个类的行为方式和Scala的Actor几乎完全一致,它提供了另外一些方法,对应于Akka的Actor trait。这使得控制器更易于逐步的迁移到Akka。 + +为了达到这个目的,所有的从Actor继承的类,按照下列的方式,需要改为继承自ActWithStash: + + class MyActor extends Actor -> class MyActor extends ActWithStash + +经过这样修改以后,代码会无法通过编译。因为ActWithStash中的receive 方法不能在act中像原来那样使用。要使代码通过编译,需要在所有的 receive 调用中加上类型参数。例如: + + receive { case x: Int => "Number" } -> + receive[String] { case x: Int => "Number" } + +另外,要使代码通过编译,还要在act方法前加上 override关键字,并且定义一个空的receive方法。act方法需要被重写,因为它在ActWithStash 的实现中模拟了Akka的消息处理循环。需要修改的地方请看下面的例子: + + class MyActor extends ActWithStash { + + // 空的 receive 方法 (现在还没有用) + def receive = {case _ => } + + override def act() { + // 原来代码中的 receive 方法改为 react。 + } + } +ActWithStash 的实例中,变量trapExit 的缺省值是true。如果希望改变,可以在初始化方法中把它设置为false。 + +远程控制器在ActWithStash 下无法直接使用,register('name, this)方法需要被替换为: + + registerActorRef('name, self) + +在后面的步骤中, registerActorRef 和 alive 方法的调用与其它方法一样。 + +现在,用户可以测试运行,整个系统的运行会和原来一样。ActWithStash 和Actor 拥有相同的基本架构,所以系统的运行会与原来没有什么区别。 + +### 第4步 - 去掉act 方法 + +在这一节,我们讨论怎样从ActWithStash中去掉act方法,以及怎样修改其他方法,使它与Akka更加契合. 这一环节会比较繁杂,所以我们建议最好一次只修改一个控制器。在Scala中,控制器的行为主要是在act方法的中定义。逻辑上来说,控制器是一个并发执行act方法的过程,执行完成后过程终止。在Akka中,控制器用一个全局消息处理器来依次处理它的的消息队列中的消息。这个消息处理器是一个receive函数返回的偏函数(partial function),该偏函数被应用与每一条消息上。 + +因为ActWithStash中Akka方法的行为依赖于移除的act方法,所以我们首先要做的是去掉act方法。然后,我们需要按照给定的规则修改scala.actors.Actor中每个方法的。 + +#### 怎样去除act 方法 + +在下面的列表中,我们给出了通用消息处理模式的修改规则。这个列表并不包含所有的模式,它只是覆盖了其中一些通用的模式。然而用户可以通过参考这些规则,通过扩展简单规则,将act方法移植到Akka。 + +嵌套调用react/reactWithin需要注意:消息处理偏函数需要做结构扩展,使它更接近Akka模式。尽管这种修改会很复杂,但是它允许任何层次的嵌套被移植。下面有相关的例子。 + +在复杂控制流中使用receive/receiveWithin需要注意:这个移植会比较复杂,因为它要求重构act方法。在消息处理偏函数中使用react 和 andThen可以使receive的调用模型化。下面是一些简单的例子。 + +1. 如果在act方法中有一些代码在第一个包含react的loop之前被执行,那么这些代码应该被放在preStart方法中。 + + def act() { + //初始化的代码放在这里 + loop { + react { ... } + } + } +应该被替换 + + override def preStart() { + //初始化的代码放在这里 + } + + def act() { + loop { + react{ ... } + } + } +其他的模式,如果在第一个react 之前有一些代码,也可以使用这个规则。 + +2. 当act 的形式为:一个简单loop循环嵌套react,用下面的方法。 + + def act() = { + loop { + react { + // body + } + } + } +应该被替换 + + def receive = { + // body + } + +3. 当act包含一个loopWhile 结构,用下面的方法。 + + def act() = { + loopWhile(c) { + react { + case x: Int => + // do task + if (x == 42) { + c = false + } + } + } + } +应该被替换 + + def receive = { + case x: Int => + // do task + if (x == 42) { + context.stop(self) + } + } + +4. 当act包含嵌套的react,用下面的规则: + + def act() = { + var c = true + loopWhile(c) { + react { + case x: Int => + // do task + if (x == 42) { + c = false + } else { + react { + case y: String => + // do nested task + } + } + } + } + } +应该被替换 + + def receive = { + case x: Int => + // do task + if (x == 42) { + context.stop(self) + } else { + context.become(({ + case y: String => + // do nested task + }: Receive).andThen(x => { + unstashAll() + context.unbecome() + }).orElse { case x => stash(x) }) + } + } + +5. reactWithin方法使用下面的修改规则: + + loop { + reactWithin(t) { + case TIMEOUT => // timeout processing code + case msg => // message processing code + } + } +应该被替换 + + import scala.concurrent.duration._ + + context.setReceiveTimeout(t millisecond) + def receive = { + case ReceiveTimeout => // timeout processing code + case msg => // message processing code + } + +6. 在Akka中,异常处理用另一种方式完成。如果要模拟Scala控制器的方式,那就用下面的方法 + + def act() = { + loop { + react { + case msg => + // 可能会失败的代码 + } + } + } + + override def exceptionHandler = { + case x: Exception => println("got exception") + } +应该被替换 + + def receive = PFCatch({ + case msg => + // 可能会失败的代码 + }, { case x: Exception => println("got exception") }) + PFCatch 的定义 + + class PFCatch(f: PartialFunction[Any, Unit], + handler: PartialFunction[Exception, Unit]) + extends PartialFunction[Any, Unit] { + + def apply(x: Any) = { + try { + f(x) + } catch { + case e: Exception if handler.isDefinedAt(e) => + handler(e) + } + } + + def isDefinedAt(x: Any) = f.isDefinedAt(x) + } + + object PFCatch { + def apply(f: PartialFunction[Any, Unit], + handler: PartialFunction[Exception, Unit]) = + new PFCatch(f, handler) + } + +PFCatch并不包含在AMK之中,所以它可以保留在移植代码中,AMK将会在下一版本中被删除。当整个移植完成后,错误处理也可以改由Akka来监管。 + +#### 修改Actor的方法 + +当我们移除了act方法以后,我们需要替换在Akka中不存在,但是有相似功能的方法。在下面的列表中,我们给出了两者的区别和替换方法: + +1. exit()/exit(reason) - 需要由 context.stop(self) 替换 + +2. receiver - 需要由 self 替换 + +3. reply(msg) - 需要由 sender ! msg 替换 + +4. link(actor) - 在Akka中,控制器之间的链接一部分由[supervision](http://doc.akka.io/docs/akka/2.1.0/general/supervision.html#What_Supervision_Means)来完成,一部分由[actor monitoring](http://doc.akka.io/docs/akka/2.1.0/general/supervision.html#What_Lifecycle_Monitoring_Means)来完成。在AMK中,我们只支持监测方法。因此,这部分Scala功能可以被完整的移植。 + +linking 和 watching 之间的区别在于:watching actor总是接受结束通知。然而,不像Scala的Exit消息包含结束的原因,Akka的watching 返回Terminated(a: ActorRef)消息,只包含ActorRef。获取结束原因的功能无法被移植。在Akka中,这一步骤可以在第4步之后,通过组织控制器的监管层级 [supervision hierarchy](http://doc.akka.io/docs/akka/2.1.0/general/supervision.html)来完成。 + +如果watching actors收到的消息不撇陪结束消息,控制器会被终止并抛出DeathPactException异常。注意就算watching actors正常的结束,也会发生这种情况。在Scala中,linked actors只要一方不正常的终止,另一方就会以相同的原因终止。 + +如果系统不能单独的用 watch actors来 移植,用户可以像原来那样用link和exit(reason)来使用。然而,因为act()重载了Exit消息,需要做如下的修改: + + case Exit(actor, reason) => + println("sorry about your " + reason) + ... +应该被替换 + + case t @ Terminated(actorRef) => + println("sorry about your " + t.reason) + ... +注意:在Scala和Akka的actor之间有另一种细微的区别:在Scala, link/watch 到已经终止的控制器不会有任何影响。在Akka中,看管已经终止的控制器会导致发送终止消息。这会在系统移植的第5 步导致不可预料的结果。 + +### 第5步 - Akka后端的移植 + +到目前为止,用户代码已经做好了移植到Akka actors的准备工作。现在我们可以把Scala actors迁移到Akka actor上。为了完成这一目标,需要配置build,去掉scala-actors.jar 和 scala-actors-migration.jar,把 akka-actor.jar 和 typesafe-config.jar加进来。AMK只能在Akka actor 2.1下正常工作,Akka actor 2.1已经包含在分发包 [Scala distribution](http://www.scala-lang.org/downloads)中, 可以用这样的方法配置。 + +经过这一步骤以后,因为包名的不同和API之间的细微差别,编译会失败。我们必须将每一个导入的actor从scala 修改为Akka。下列是部分需要修改的包名: + + scala.actors._ -> akka.actor._ + scala.actors.migration.ActWithStash -> akka.actor.ActorDSL._ + scala.actors.migration.pattern.ask -> akka.pattern.ask + scala.actors.migration.Timeout -> akka.util.Timeout + +当然,ActWithStash 中方法的声明 def receive = 必须加上前缀override。 + +在Scala actor中,stash 方法需要一个消息做为参数。例如: + + def receive = { + ... + case x => stash(x) + } + +在Akka中,只有当前处理的消息可以被隐藏(stashed)。因此,上面的例子可以替换为: + + def receive = { + ... + case x => stash() + } + +#### 添加Actor System + +Akka actor 组织在[Actor systems](http://doc.akka.io/docs/akka/2.1.0/general/actor-systems.html)系统中。每一个被实例化的actor必须属于某一个ActorSystem。因此,要添加一个ActorSystem 实例作为每个actor 实例调用的第一个参数。下面给出了例子。 + +为了完成该转换,你需要有一个actor system 实例。例如: + + val system = ActorSystem("migration-system") + +然后,做如下转换: + + ActorDSL.actor(...) -> ActorDSL.actor(system)(...) + +如果对actor 的调用都使用同一个ActorSystem ,那么它可以作为隐式参数来传递。例如: + + ActorDSL.actor(...) -> + import project.implicitActorSystem + ActorDSL.actor(...) + +当所有的主线程和actors结束后,Scala程序会终止。迁移到Akka后,当所有的主线程结束,所有的actor systems关闭后,程序才会结束。Actor systems 需要在程序退出前明确的中止。这需要通过在Actor system中调用shutdown 方法来完成。 + +#### 远程 Actors + +当代码迁移到Akka,远程actors就不再工作了。 registerActorFor 和 alive 方法需要被移除。 在Akka中,远程控制通过配置独立的完成。更多细节请参考[Akka remoting documentation](http://doc.akka.io/docs/akka/2.1.0/scala/remoting.html)。 + +#### 样例和问题 + +这篇文档中的所有程序片段可以在[Actors Migration test suite](http://github.com/scala/actors-migration/tree/master/src/test/)中找到,这些程序做为测试文件,前缀为actmig。 + +这篇文档和Actor移植组件由 [Vojin Jovanovic](http://people.epfl.ch/vojin.jovanovic)和[Philipp Haller](http://lampwww.epfl.ch/~phaller/)编写。 + +如果你发现任何问题或不完善的地方,请把它们报告给 [Scala Bugtracker](https://github.com/scala/actors-migration/issues)。 diff --git a/_zh-cn/overviews/core/actors.md b/_zh-cn/overviews/core/actors.md new file mode 100644 index 0000000000..bdcfc6bbcb --- /dev/null +++ b/_zh-cn/overviews/core/actors.md @@ -0,0 +1,308 @@ +--- +layout: singlepage-overview +title: The Scala Actors API + +partof: actors + +language: zh-cn + +discourse: false +--- + +**Philipp Haller 和 Stephen Tu 著** + +## 简介 + +本指南介绍了Scala 2.8和2.9中`scala.actors`包的API。这个包的内容因为逻辑上相通,所以放到了同一个类型的包内。这个trait在每个章节里面都会有所涉及。这章的重点在于这些traits所定义的各种方法在运行状态时的行为,由此来补充现有的Scala基础API。 + +注意:在Scala 2.10版本中这个Actors库将是过时的,并且在未来Scala发布的版本中将会被移除。开发者应该使用在`akka.actor`包中[Akka](http://akka.io/) actors来替代它。想了解如何将代码从Scala actors迁移到Akka请参考[Actors 迁移指南](http://docs.scala-lang.org/overviews/core/actors-migration-guide.html)章节。 + +## Actor trait:Reactor, ReplyReactor和Actor + +### Reactor trait + +Reactor 是所有`actor trait`的父级trait。扩展这个trait可以定义actor,其具有发送和接收消息的基本功能。 + +Reactor的行为通过实现其act方法来定义。一旦调用start方法启动Reactor,这个act方法便会执行,并返回这个Reactor对象本身。start方法是具有等幂性的,也就是说,在一个已经启动了的actor对象上调用它(start方法)是没有作用的。 + +Reactor trait 有一个Msg 的类型参数,这个参数指明这个actor所能接收的消息类型。 + +调用Reactor的!方法来向接收者发送消息。用!发送消息是异步的,这样意味着不会等待消息被接收——它在发送消息后便立刻往下执行。例如:`a ! msg`表示向`a`发送`msg`。每个actor都有各自的信箱(mailbox)作为缓冲来存放接收到的消息,直至这些消息得到处理。 + +Reactor trait中也定义了一个forward方法,这个方法继承于OutputChannel。它和!(感叹号,发送方法)有同样的作用。Reactor的SubTrait(子特性)——特别是`ReplyReactor trait`——覆写了此方法,使得它能够隐式地回复目标。(详细地看下面的介绍) + +一个Reactor用react方法来接收消息。react方法需要一个PartialFunction[Msg, Unit]类型的参数,当消息到达actor的邮箱之后,react方法根据这个参数来确定如何处理消息。在下面例子中,当前的actor等待接收一个“Hello”字符串,然后打印一句问候。 + + react { + case "Hello" => println("Hi there") + } + +调用react没有返回值。因此,在接收到一条消息后,任何要执行的代码必须被包含在传递给react方法的偏函数(partial function)中。举个例子,通过嵌套两个react方法调用可以按顺序接收到两条消息: + + react { + case Get(from) => + react { + case Put(x) => from ! x + } + } + +Reactor trait 也提供了控制结构,简化了react方法的代码。 + +### 终止和执行状态 + +当Reactor的act方法完整执行后, Reactor则随即终止执行。Reactor也可以显式地使用exit方法来终止自身。exit方法的返回值类型为Nothing,因为它总是会抛出异常。这个异常仅在内部使用,并且不应该去捕捉这个异常。 + +一个已终止的Reactor可以通过它的restart方法使它重新启动。对一个未终止的Reactor调用restart方法则会抛出`IllegalStateException`异常。重新启动一个已终止的actor则会使它的act方法重新运行。 + +Reactor定义了一个getState方法,这个方法可以将actor当前的运行状态作为Actor.State枚举的一个成员返回。一个尚未运行的actor处于`Actor.State.New`状态。一个能够运行并且不在等待消息的actor处于`Actor.State.Runnable`状态。一个已挂起,并正在等待消息的actor处于`Actor.State.Suspended`状态。一个已终止的actor处于`Actor.State.Terminated`状态。 + +### 异常处理 + +exceptionHandler成员允许定义一个异常处理程序,其在Reactor的整个生命周期均可用。 + + def exceptionHandler: PartialFunction[Exception, Unit] + +exceptionHandler返回一个偏函数,它用来处理其他没有被处理的异常。每当一个异常被传递到Reactor的act方法体之外时,这个成员函数就被应用到该异常,以允许这个actor在它结束前执行清理代码。注意:`exceptionHandler`的可见性为protected。 + +用exceptionHandler来处理异常并使用控制结构对与react的编程是非常有效的。每当exceptionHandler返回的偏函数处理完一个异常后,程序会以当前的后续闭包(continuation closure)继续执行。 + + loop { + react { + case Msg(data) => + if (cond) // 数据处理代码 + else throw new Exception("cannot process data") + } + } + +假设Reactor覆写了exceptionHandler,在处理完一个在react方法体内抛出的异常后,程序将会执行下一个循环迭代。 + +### ReplyReactor trait + +`ReplyReactor trait`扩展了`Reactor[Any]`并且增加或覆写了以下方法: + +!方法被覆写以获得一个当前actor对象(发送方)的引用,并且,这个发送方引用和实际的消息一起被传递到接收actor的信箱(mail box)中。接收方通过其sender方法访问消息的发送方(见下文)。 + +forward方法被覆写以获得一个引用,这个引用指向正在被处理的消息的发送方。引用和实际的消息一起作为当前消息的发送方传递。结果,forward方法允许代表不同于当前actor对象的actor对象转发消息。 + +增加的sender方法返回正被处理的消息的发送方。考虑到一个消息可能已经被转发,发送方可能不会返回实际发送消息的actor对象。 + +增加的reply方法向最后一个消息的发送方回复消息。reply方法也被用作回复一个同步消息发送或者一个使用future的消息发送(见下文)。 + +增加的!?方法提供同步消息发送。调用!?方法会引起发送方actor对象等待,直到收到一个响应,然后返回这个响应。重载的变量有两个。这个双参数变量需要额外的超时参数(以毫秒计),并且,它的返回类型是Option[Any]而不是Any。如果发送方在指定的超时期间没有收到一个响应,!?方法返回None,否则它会返回由Some包裹的响应。 + +增加的!!方法与同步消息发送的相似点在于,它们都允许从接收方传递一个响应。然而,它们返回Future实例,而不是阻塞发送中的actor对象直到接收响应。一旦Future对象可用,它可以被用来重新获得接收方的响应,还可以在不阻塞发送方的情况下,用于获知响应是否可用。重载的变量有两个。双参数变量需要额外的PartialFunction[Any,A]类型的参数。这个偏函数用于对接收方响应进行后处理。本质上,!!方法返回一个future对象,一旦响应被接收,这个future对象把偏函数应用于响应。future对象的结果就是后处理的结果。 + +增加的reactWithin方法允许在一段给定的时间段内接收消息。相对于react方法,这个方法需要一个额外的msec参数,用来指示在这个时间段(以毫秒计)直到匹配指定的TIMEOUT模式为止(TIMEOUT是包scala.actors中的用例对象(case object))。例如: + +reactWithin(2000) { case Answer(text) => // process text case TIMEOUT => println("no answer within 2 seconds") } + +reactWithin方法也允许以非阻塞方式访问信箱。当指定一个0毫秒的时间段时,首先会扫描信箱以找到一个匹配消息。如果在第一次扫描后没有匹配的消息,这个TIMEOUT模式将会匹配。例如,这使得接收某些消息会比其他消息有较高的优先级: + +reactWithin(0) { case HighPriorityMsg => // ... case TIMEOUT => react { case LowPriorityMsg => // ... } } + +在上述例子中,即使在信箱里有一个先到达的低优先级的消息,actor对象也会首先处理下一个高优先级的消息。actor对象只有在信箱里没有高优先级消息时才会首先处理一个低优先级的消息。 + +另外,ReplyReactor 增加了`Actor.State.TimedSuspended`执行状态。一个使用`reactWithin`方法等待接收消息而挂起的actor对象,处在` Actor.State.TimedSuspended `状态。 + +### Actor trait + +Actor trait扩展了`ReplyReactor`并增加或覆写了以下成员: + +增加的receive方法的行为类似react方法,但它可以返回一个结果。这可以在它的类型上反映——它的结果是多态的:def receive[R](f: PartialFunction[Any, R]): R。然而,因为actor对象挂起并等待消息时,receive方法会阻塞底层线程(underlying thread),使用receive方法使actor对象变得更加重量级。直到receive方法的调用返回,阻塞的线程将不能够执行其他actor对象。 + +增加的link和unlink方法允许一个actor对象将自身链接到另一个actor对象,或将自身从另一个actor对象断开链接。链接可以用来监控或对另一个actor对象的终止做出反应。特别要注意的是,正如在Actor trait的API文档中的解释,链接影响调用exit方法的行为。 + +trapExit成员允许对链接的actor对象的终止做出反应而无关其退出的原因(即,无关是否正常退出)。如果一个actor对象的trapExit成员被设置为true,则这个actor对象会因链接的actor对象而永远不会终止。相反,每当其中一个链接的actor对象个终止了,它将会收到类型为Exit的消息。这个Exit case class 有两个成员:from指终止的actor对象;reason指退出原因。 + +### 终止和执行状态 + +当终止一个actor对象的执行时,可以通过调用以下exit方法的变体,显式地设置退出原因: + + def exit(reason: AnyRef): Nothing +当一个actor对象以符号'normal以外的原因退出,会向所有链接到它的atocr对象传递其退出原因。如果一个actor对象由于一个未捕获异常终止,它的退出原因则为一个UncaughtException case class的实例。 + +Actor trait增加了两个新的执行状态。使用receive方法并正在等待接收消息的actor处在`Actor.State.Blocked`状态。使用receiveWithin方法并正在等待接收消息的actor处在`Actor.State.TimedBlocked`状态。 + +## 控制结构 + +Reactor trait定义了控制结构,它简化了无返回的react操作的编程。一般来说,一个react方法调用并不返回。如果actor对象随后应当执行代码,那么,或者显式传递actor对象的后续代码给react方法,或者可以使用下述控制结构,达到隐藏这些延续代码的目的。 + +最基础的控制结构是andThen,它允许注册一个闭包。一旦actor对象的所有其他代码执行完毕,闭包就会被执行。 + + actor { + { + react { + case "hello" => // 处理 "hello" + }: Unit + } andThen { + println("hi there") + } + } + +例如,上述actor实例在它处理了“hello”消息之后,打印一句问候。虽然调用react方法不会返回,我们仍然可以使用andThen来注册这段输出问候的代码(作为actor的延续)。 + +注意:在react方法的调用(: Unit)中存在一种类型归属。总而言之,既然表达式的结果经常可以被忽略,react方法的结果就可以合法地作为Unit类型来处理。andThen方法无法成为Nothing类型(react方法的结果类型)的一个成员,所以在这里有必要这样做。把react方法的结果类型当作Unit,允许实现一个隐式转换的应用,这个隐式转换使得andThen成员可用。 + +API还提供一些额外的控制结构: + +loop { ... }。无限循环,在每一次迭代中,执行括号中的代码。调用循环体内的react方法,actor对象同样会对消息做出反应。而后,继续执行这个循环的下次迭代。 + +loopWhile (c) { ... }。当条件c返回true,执行括号中的代码。调用循环体中的react方法和使用loop时的效果一样。 + +continue。继续执行当前的接下来的后续闭包(continuation closure)。在loop或loopWhile循环体内调用continue方法将会使actor对象结束当前的迭代并继续执行下次迭代。如果使用andThen注册了当前的后续代码,这个闭包会作为第二个参数传给andThen,并以此继续执行。 + +控制结构可以在Reactor对象的act方法中,以及在act方法(传递地)调用的方法中任意处使用。对于用actor{...}这样的缩略形式创建的actor,控制结构可以从Actor对象导入。 + +### Future + +ReplyReactor和Actor trait支持发送带有结果的消息(!!方法),其立即返回一个future实例。一个future即Future trait的一个实例,即可以用来重新获取一个send-with-future消息的响应的句柄。 + +一个send-with-future消息的发送方可以通过应用future来等待future的响应。例如,使用val fut = a !! msg 语句发送消息,允许发送方等待future的结果。如:val res = fut()。 + +另外,一个Future可以在不阻塞的情况下,通过isSet方法来查询并获知其结果是否可用。 + +send-with-future的消息并不是获得future的唯一的方法。future也可以通过future方法计算而得。下述例子中,计算体会被并行地启动运行,并返回一个future实例作为其结果: + + val fut = Future { body } + // ... + fut() // 等待future + +能够通过基于actor的标准接收操作(例如receive方法等)来取回future的结果,使得future实例在actor上下文中变得特殊。此外,也能够通过使用基于事件的操作(react方法和ractWithin方法)。这使得一个actor实例在等待一个future实例结果时不用阻塞它的底层线程。 + +通过future的inputChannel,使得基于actor的接收操作方法可用。对于一个类型为`Future[T]`的future对象而言,它的类型是`InputChannel[T]`。例如: + + val fut = a !! msg + // ... + fut.inputChannel.react { + case Response => // ... + } + +## Channel(通道) + +channnel可以用来对发送到同一actor的不同类型消息的处理进行简化。channel的层级被分为OutputChannel和InputChannel。 + +OutputChannel可用于发送消息。OutputChannel的out方法支持以下操作。 + +out ! msg。异步地向out方法发送msg。当msg直接发送给一个actor,一个发送中的actor的引用会被传递。 + +out forward msg。异步地转发msg给out方法。当msg被直接转发给一个actor,发送中的actor会被确定。 + +out.receiver。返回唯一的actor,其接收发送到out channel(通道)的消息。 + +out.send(msg, from)。异步地发送msg到out,并提供from作为消息的发送方。 + +注意:OutputChannel trait有一个类型参数,其指定了可以被发送到channel(通道)的消息类型(使用!、forward和send)。这个类型参数是逆变的: + + trait OutputChannel[-Msg] + +Actor能够从InputChannel接收消息。就像OutputChannel,InputChannel trait也有一个类型参数,用于指定可以从channel(通道)接收的消息类型。这个类型参数是协变的: + + trait InputChannel[+Msg] + +An` InputChannel[Msg] `in支持下列操作。 + +in.receive { case Pat1 => ... ; case Patn => ... }(以及类似的 in.receiveWithin)。从in接收一个消息。在一个输入channel(通道)上调用receive方法和actor的标准receive操作具有相同的语义。唯一的区别是,作为参数被传递的偏函数具有PartialFunction[Msg, R]类型,此处R是receive方法的返回类型。 + +in.react { case Pat1 => ... ; case Patn => ... }(以及类似的in.reactWithin)通过基于事件的react操作,从in方法接收一个消息。就像actor的react方法,返回类型是Nothing。这意味着此方法的调用不会返回。就像之前的receive操作,作为参数传递的偏函数有一个更具体的类型:PartialFunction[Msg, Unit] + +### 创建和共享channel + +channel通过使用具体的Channel类创建。它同时扩展了InputChannel和OutputChannel。使channel在多个actor的作用域(Scope)中可见,或者将其在消息中发送,都可以实现channel的共享。 + +下面的例子阐述了基于作用域(scope)的共享。 + + actor { + var out: OutputChannel[String] = null + val child = actor { + react { + case "go" => out ! "hello" + } + } + val channel = new Channel[String] + out = channel + child ! "go" + channel.receive { + case msg => println(msg.length) + } + } + +运行这个例子将输出字符串“5”到控制台。注意:子actor对out(一个OutputChannel[String])具有唯一的访问权。而用于接收消息的channel的引用则被隐藏了。然而,必须要注意的是,在子actor向输出channel发送消息之前,确保输出channel被初始化到一个具体的channel。通过使用“go”消息来完成消息发送。当使用channel.receive来从channel接收消息时,因为消息是String类型的,可以使用它提供的length成员。 + +另一种共享channel的可行的方法是在消息中发送它们。下面的例子对此作了阐述。 + + case class ReplyTo(out: OutputChannel[String]) + + val child = actor { + react { + case ReplyTo(out) => out ! "hello" + } + } + + actor { + val channel = new Channel[String] + child ! ReplyTo(channel) + channel.receive { + case msg => println(msg.length) + } + } + +ReplyTo case class是一个消息类型,用于分派一个引用到OutputChannel[String]。当子actor接收一个ReplyTo消息时,它向它的输出channel发送一个字符串。第二个actor则像以前一样接收那个channel上的消息。 + +## Scheduler + +scheduler用于执行一个Reactor实例(或子类型的一个实例)。Reactor trait引入了scheduler成员,其返回常用于执行Reactor实例的scheduler。 + + def scheduler: IScheduler + +运行时系统通过使用在IScheduler trait中定义的execute方法之一,向scheduler提交任务来执行actor。只有在完整实现一个新的scheduler时(但没有必要),此trait的大多数其他方法才是相关的。 + +默认的scheduler常用于执行Reactor实例,而当所有的actor完成其执行时,Actor则会检测环境。当这发生时,scheduler把它自己关闭(终止scheduler使用的任何线程)。然而,一些scheduler,比如SingleThreadedScheduler(位于scheduler包)必须要通过调用它们的shutdown方法显式地关闭。 + +创建自定义scheduler的最简单方法是通过扩展SchedulerAdapter,实现下面的抽象成员: + + def execute(fun: => Unit): Unit + +典型情况下,一个具体的实现将会使用线程池来执行它的按名参数fun。 + +## 远程Actor + +这一段描述了远程actor的API。它的主要接口是scala.actors.remote包中的RemoteActor对象。这个对象提供各种方法来创建和连接到远程actor实例。在下面的代码段中我们假设所有的RemoteActor成员都已被导入,所使用的完整导入列表如下: + + import scala.actors._ + import scala.actors.Actor._ + import scala.actors.remote._ + import scala.actors.remote.RemoteActor._ + +### 启动远程Actor + +远程actor由一个Symbol唯一标记。在这个远程Actor所执行JVM上,这个符号对于JVM实例是唯一的。由名称'myActor标记的远程actor可按如下方法创建。 + + class MyActor extends Actor { + def act() { + alive(9000) + register('myActor, self) + // ... + } + } + +记住:一个名字一次只能标记到一个单独的(存活的)actor。例如,想要标记一个actorA为'myActor,然后标记另一个actorB为'myActor。这种情况下,必须等待A终止。这个要求适用于所有的端口,因此简单地将B标记到不同的端口来作为A是不能满足要求的。 + +### 连接到远程Actor + +连接到一个远程actor也同样简单。为了获得一个远程Actor的远程引用(运行于机器名为myMachine,端口为8000,名称为'anActor),可按下述方式使用select方法: + + val myRemoteActor = select(Node("myMachine", 8000), 'anActor) + +从select函数返回的actor具有类型AbstractActor,这个类型本质上提供了和通常actor相同的接口,因此支持通常的消息发送操作: + + myRemoteActor ! "Hello!" + receive { + case response => println("Response: " + response) + } + myRemoteActor !? "What is the meaning of life?" match { + case 42 => println("Success") + case oops => println("Failed: " + oops) + } + val future = myRemoteActor !! "What is the last digit of PI?" + +记住:select方法是惰性的,它不实际初始化任何网络连接。仅当必要时(例如,调用!时),它会单纯地创建一个新的,准备好初始化新网络连接的AbstractActor实例。 diff --git a/_zh-cn/overviews/core/architecture-of-scala-collections.md b/_zh-cn/overviews/core/architecture-of-scala-collections.md new file mode 100644 index 0000000000..df77d22150 --- /dev/null +++ b/_zh-cn/overviews/core/architecture-of-scala-collections.md @@ -0,0 +1,543 @@ +--- +layout: singlepage-overview +title: Scala容器类体系结构 + +partof: collections-architecture + +language: zh-cn + +discourse: false +--- + +**Martin Odersky 和 Lex Spoon 著** + +本篇详细的介绍了Scala 容器类(collections)框架。通过与 [Scala 2.8 的 Collection API](http://docs.scala-lang.org/overviews/collections/introduction.html) 的对比,你会了解到更多框架的内部运作方式,同时你也将学习到如何通过几行代码复用这个容器类框架的功能来定义自己的容器类。 + +[Scala 2.8 容器API](http://docs.scala-lang.org/overviews/collections/introduction.html) 中包含了大量的 容器(collection)操作,这些操作在不同的许多容器类上表现为一致。假设,为每种 Collection 类型都用不同的方法代码实现,那么将导致代码的异常臃肿,很多代码将会仅仅是别处代码的拷贝。随着时间的推移,这些重复的代码也会带来不一致的问题,试想,相同的代码,在某个地方被修改了,而另外的地方却被遗漏了。而新的 容器类(collections)框架的设计原则目标就是尽量的避免重复,在尽可能少的地方定义操作(理想情况下,只在一处定义,当然也会有例外的情况存在)。设计中使用的方法是,在 Collection 模板中实现大部分的操作,这样就可以灵活的从独立的基类和实现中继承。后面的部分,我们会来详细阐述框架的各组成部分:模板(templates)、类(classes)以及trait(译注:类似于java里接口的概念),也会说明他们所支持的构建原则。 + +## Builders + +Builder类概要: + + package scala.collection.mutable + + class Builder[-Elem, +To] { + def +=(elem: Elem): this.type + def result(): To + def clear(): Unit + def mapResult[NewTo](f: To => NewTo): Builder[Elem, NewTo] = ... + } + +几乎所有的 Collection 操作都由遍历器(traversals)和构建器 (builders)来完成。Traversal 用可遍历类的foreach方法来实现,而构建新的 容器(collections)是由构建器类的实例来完成。上面的代码就是对这个类的精简描述。 + +我们用 b += x 来表示为构建器 b 加上元素 x。也可以一次加上多个元素,例如: b += (x, y) 及 b ++= x ,这类似于缓存(buffers)的工作方式(实际上,缓存就是构建器的增强版)。构建器的 result() 方法会返回一个collection。在获取了结果之后,构建器的状态就变成未定义,调用它的 clear() 方法可以把状态重置成空状态。构建器是通用元素类型,它适用于元素,类型,及它所返回的Collection。 + +通常,一个builder可以使用其他的builder来组合一个容器的元素,但是如果想要把其他builder返回的结果进行转换,例如,转成另一种类型,就需要使用Builder类的mapResult方法。假设,你有一个数组buffer,名叫 buf。一个ArrayBuffer的builder 的 result() 返回它自身。如果想用它去创建一个新的ArrayBuffer的builder,就可以使用 mapResult : + + scala> val buf = new ArrayBuffer[Int] + buf: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer() + + scala> val bldr = buf mapResult (_.toArray) + bldr: scala.collection.mutable.Builder[Int,Array[Int]] + = ArrayBuffer() + +结果值 bldr,是使用 buf 来收集元素的builder。当调用 bldr 的result时,其实是调用的 buf 的result,结果是返回的buf本身。接着这个数组buffer用 _.toArray 映射成了一个数组,结果 bldr 也就成了一个数组的 builder. + +## 分解(factoring out)通用操作 + +### TraversableLike类概述 + + package scala.collection + + class TraversableLike[+Elem, +Repr] { + def newBuilder: Builder[Elem, Repr] // deferred + def foreach[U](f: Elem => U) // deferred + ... + def filter(p: Elem => Boolean): Repr = { + val b = newBuilder + foreach { elem => if (p(elem)) b += elem } + b.result + } + } + +Collection库重构的主要设计目标是在拥有自然类型的同时又尽可能的共享代码实现。Scala的Collection 遵从“结果类型相同”的原则:只要可能,容器上的转换方法最后都会生成相同类型的Collection。例如,过滤操作对各种Collection类型都应该产生相同类型的实例。在List上应用过滤器应该获得List,在Map上应用过滤器,应该获得Map,如此等等。在下面的章节中,会告诉大家该原则的实现方法。 + +Scala的 Collection 库通过在 trait 实现中使用通用的构建器(builders)和遍历器(traversals)来避免代码重复、实现“结果类型相同”的原则。这些Trait的名字都有Like后缀。例如:IndexedSeqLike 是 IndexedSeq 的 trait 实现,再如,TraversableLike 是 Traversable 的 trait 实现。和普通的 Collection 只有一个类型参数不同,trait实现有两个类型参数。他们不仅参数化了容器的成员类型,也参数化了 Collection 所代表的类型,就像下面的 Seq[I] 或 List[T]。下面是TraversableLike开头的描述: + + trait TraversableLike[+Elem, +Repr] { ... } + +类型参数Elem代表Traversable的元素类型,参数Repr代表它自身。Repr上没有限制,甚至,Repr可以是非Traversable子类的实例。这就意味这,非容器子类的类,例如String和Array也可以使用所有容器实现trait中包含的操作。 + +以过滤器为例,这个操作只在TraversableLike定义了一次,就使得它适用于所有的容器类(collections)。通过查看前面 TraversableLike类的概述中相关的代码描述,我们知道,该trait声明了两个抽象方法,newBuilder 和 foreach,这些抽象方法在具体的collection类中实现。过滤器也是用这两个方法,通过相同的方式来实现的。首先,它用 newBuiler 方法构造一个新的builder,类型为 Repr 的类型。然后,使用 foreach 来遍历当前 collection 中的所有元素。一旦某个元素 x 满足谓词 p (即,p(x)为真),那么就把x加入到builder中。最后,用 builder 的 result 方法返回类型同 Repr 的 collection,里面的元素就是上面收集到的满足条件的所有元素。 + +容器(collections)上的映射操作就更复杂。例如:如果 f 是一个以一个String类型为参数并返回一个Int类型的函数,xs 是一个 List[String],那么在xs上使用该映射函数 f 应该会返回 List[Int]。同样,如果 ys 是一个 Array[String],那么通过 f 映射,应该返回 Array[Int]。 这里的难点在于,如何实现这样的效果而又不用分别针对 list 和 array 写重复的代码。TraversableLike 类的 newBuilder/foreach 也完成不了这个任务,因为他们需要生成一个完全相同类型的容器(collection)类型,而映射需要的是生成一个相同类型但内部元素类型却不同的容器。 + +很多情况下,甚至像map的构造函数的结果类型都可能是不那么简单的,因而需要依靠于其他参数类型,比如下面的例子: + + scala> import collection.immutable.BitSet + import collection.immutable.BitSet + + scala> val bits = BitSet(1, 2, 3) + bits: scala.collection.immutable.BitSet = BitSet(1, 2, 3) + + scala> bits map (_ * 2) + res13: scala.collection.immutable.BitSet = BitSet(2, 4, 6) + + scala> bits map (_.toFloat) + res14: scala.collection.immutable.Set[Float] + = Set(1.0, 2.0, 3.0) + +在一个BitSet上使用倍乘映射 _*2,会得到另一个BitSet。然而,如果在相同的BitSet上使用映射函数 (_.toFloat) 结果会得到一个 Set[Float]。这样也很合理,因为 BitSet 中只能放整型,而不能存放浮点型。 + +因此,要提醒大家注意,映射(map)的结果类型是由传进来的方法的类型决定的。如果映射函数中的参数会得到Int类型的值,那么映射的结果就是 BitSet。但如果是其他类型,那么映射的结果就是 Set 类型。后面会让大家了解 Scala 这种灵活的类型适应是如何实现的。 + +类似 BitSet 的问题不是唯一的,这里还有在map类型上应用map函数的交互式例子: + + scala> Map("a" -> 1, "b" -> 2) map { case (x, y) => (y, x) } + res3: scala.collection.immutable.Map[Int,java.lang.String] + = Map(1 -> a, 2 -> b) + + scala> Map("a" -> 1, "b" -> 2) map { case (x, y) => y } + res4: scala.collection.immutable.Iterable[Int] + = List(1, 2) + +第一个函数用于交换两个键值对。这个函数映射的结果是一个类似的Map,键和值颠倒了。事实上,地一个表达式产生了一个键值颠倒的map类型(在原map可颠倒的情况下)。然而,第二个函数,把键值对映射成一个整型,即成员变成了具体的值。在这种情况下,我们不可能把结果转换成Map类型,因此处理成,把结果转换成Map的一个可遍历的超类,这里是List。 + +你可能会问,哪为什么不强制让映射都返回相同类型的Collection呢?例如:BitSet上的映射只能接受整型到整型的函数,而Map上的映射只能接受键值对到键值对的函数。但这种约束从面向对象的观点来看是不能接受的,它会破坏里氏替换原则(Liskov substitution principle),即:Map是可遍历类,因此所有在可遍历类上的合法的操作都必然在Map中合法。 + +Scala通过重载来解决这个问题:Scala中的重载并非简单的复制Java的实现(Java的实现不够灵活),它使用隐式参数所提供的更加系统化的重载方式。 + +TraversableLike 中映射(map)的实现: + + def map[B, That](p: Elem => B) + (implicit bf: CanBuildFrom[B, That, This]): That = { + val b = bf(this) + for (x <- this) b += f(x) + b.result + } + +上面的代码展示了TraversableLike如何实现映射的trait。看起来非常类似于TraversableLike类的过滤器的实现。主要的区别在于,过滤器使用TraversableLike类的抽象方法 newBuilder,而映射使用的是Builder工场,它作为CanBuildFrom类型的一个额外的隐式参数传入。 + +CanBuildFrom trait: + + package scala.collection.generic + + trait CanBuildFrom[-From, -Elem, +To] { + // 创建一个新的构造器(builder) + def apply(from: From): Builder[Elem, To] + } + +上面的代码是 trait CanBuildFrom 的定义,它代表着构建者工场。它有三个参数:Elem是要创建的容器(collection)的元素的类型,To是要构建的容器(collection)的类型,From是该构建器工场适用的类型。通过定义适合的隐式定义的构建器工场,你就可以构建出符合你需要的类型转换行为。以 BitSet 类为例,它的伴生对象包含一个 CanBuildFrom[BitSet, Int, BitSet] 类型的构建器工场。这就意味着,当在一个 BitSet 上执行操作的时候,你可以创建另一个元素类型为整型的 BitSet。如果你需要的类型不同,那么,你还可以使用其他的隐式构建器工场,它们在Set的伴生对象中实现。下面就是一个更通用的构建器,A是通用类型参数: + + CanBuildFrom[Set[_], A, Set[A]] + +这就意味着,当操作一个任意Set(用现有的类型 Set[] 表述),我们可以再次创建一个 Set,并且无需关心它的元素类型A是什么。给你两个 CanBuildFrom 的隐式实例,你有可以利用 Scala 的隐式解析(implicit resolution)规则去挑选出其中最契合的一个。 + +所以说,隐式解析(implicit resolution)为类似映射的比较棘手的Collection操作提供了正确的静态类型。但是动态类型又怎么办呢?特别是,假设你有一个List,作为静态类型它有遍历方法,你在它上面使用一些映射(map)方法: + + scala> val xs: Iterable[Int] = List(1, 2, 3) + xs: Iterable[Int] = List(1, 2, 3) + + scala> val ys = xs map (x => x * x) + ys: Iterable[Int] = List(1, 4, 9) + +上述ys的静态类型是可遍历的(Iterable)类型。但是它的动态类型仍然必须是List类型的!此行为是间接被实现的。在CanBuildFrom的apply方法被作为参数传递源容器中。大多数的builder工厂仿制traversables(除建造工厂意外所有的叶子类型(leaf classes))将调用转发到集合的方法genericBuilder。反过来genericBuilder方法调用属于在定义它收集的建设者。所以Scala使用静态隐式解析,以解决map类型的限制问题,以及分派挑选对应于这些约束最佳的动态类型。 + +## 集成新容器 + +如果想要集成一个新的容器(Collection)类,以便受益于在正确类型上预定义的操作,需要做些什么呢?在下面几页中,将通过两个例子来进行演示。 + +### 集成序列(Sequence) + +RNA(核糖核酸)碱基(译者注:RNA链即很多不同RNA碱基的序列,RNA参考资料:http://zh.wikipedia.org/wiki/RNA): + + abstract class Base + case object A extends Base + case object T extends Base + case object G extends Base + case object U extends Base + + object Base { + val fromInt: Int => Base = Array(A, T, G, U) + val toInt: Base => Int = Map(A -> 0, T -> 1, G -> 2, U -> 3) + } + +假设需要为RNA链建立一个新的序列类型,这些RNA链是由碱基A(腺嘌呤)、T(胸腺嘧啶)、G(鸟嘌呤)、U(尿嘧啶)组成的序列。如上述列出的RNA碱基,很容易建立碱基的定义。 + +每个碱基都定义为一个具体对象(case object),该对象继承自一个共同的抽象类Base(碱基)。这个Base类具有一个伴生对象(companion object),该伴生对象定义了描述碱基和整数(0到3)之间映射的2个函数。可以从例子中看到,有两种不同的方式来使用容器(Collection)来实现这些函数。toInt函数通过一个从Base值到整数之间的映射(map)来实现。而它的逆函数fromInt则通过数组来实现。以上这些实现方法都基于一个事实,即“映射和数组都是函数”。因为他们都继承自Function1 trait。 + +下一步任务,便是为RNA链定义一个类。从概念上来看,一个RNA链就是一个简单的Seq[Base]。然而,RNA链可以很长,所以值的去花点时间来简化RNA链的表现形式。因为只有4种碱基,所以每个碱基可以通过2个比特位来区别。因此,在一个integer中,可以保存16个由2位比特标示的碱基。即构造一个Seq[Base]的特殊子类,并使用这种压缩的表示(packed representation)方式。 + +#### RNA链类的第一个版本 + + import collection.IndexedSeqLike + import collection.mutable.{Builder, ArrayBuffer} + import collection.generic.CanBuildFrom + + final class RNA1 private (val groups: Array[Int], + val length: Int) extends IndexedSeq[Base] { + + import RNA1._ + + def apply(idx: Int): Base = { + if (idx < 0 || length <= idx) + throw new IndexOutOfBoundsException + Base.fromInt(groups(idx / N) >> (idx % N * S) & M) + } + } + + object RNA1 { + + // 表示一组所需要的比特数 + private val S = 2 + + // 一个Int能够放入的组数 + private val N = 32 / S + + // 分离组的位掩码(bitmask) + private val M = (1 << S) - 1 + + def fromSeq(buf: Seq[Base]): RNA1 = { + val groups = new Array[Int]((buf.length + N - 1) / N) + for (i <- 0 until buf.length) + groups(i / N) |= Base.toInt(buf(i)) << (i % N * S) + new RNA1(groups, buf.length) + } + + def apply(bases: Base*) = fromSeq(bases) + } + +上面的RNA链类呈现出这个类的第一个版本,它将在以后被细化。类RNA1有一个构造函数,这个构造函数将int数组作为第一个参数。而这个数组包含打包压缩后的RNA数据,每个数组元素都有16个碱基,而最后一个元素则只有一部分有数据。第二个参数是长度,指定了数组中(和序列中)碱基的总数。RNA1类扩展了IndexedSeq[Base]。而IndexedSeq来自scala.collection.immutable,IndexedSeq定义了两个抽象方法:length和apply。这方法些需要在具体的子类中实现。类RNA1通过定义一个相同名字的参数字段来自动实现length。同时,通过类RNA1中给出的代码实现了索引方法apply。实质上,apply方法首先从数组中提取出一个整数值,然后再对这个整数中使用右移位(>>)和掩码(&)提取出正确的两位比特。私有常数S、N来自RNA1的伴生对象,S指定了每个包的尺寸(也就是2),N指定每个整数的两位比特包的数量,而M则是一个比特掩码,分离出一个字(word)的低S位。 + +注意,RNA1类的构造函数是一个私有函数。这意味着用户端无法通过调用new函数来创建RNA1序列的实例。这是有意义的,因为这能对用户隐藏RNA1序列包装数组的实现。如果用户端无法看到RNA序列的具体实现,以后任何时候,就可以做到改变RNA序列具体实现的同时,不影响到用户端代码。换句话说,这种设计实现了RNA序列的接口和实现之间解藕。然而,如果无法通过new来创建一个RNA序列,那就必须存在其他方法来创建它,否则整个类就变得毫无用处。事实上,有两种建立RNA序列的替代途径,两者都由RNA1的伴生对象(companion object)提供。第一个途径是fromSeq方法,这个方法将一个给定的碱基序列(也就是一个Seq[Base]类型的值)转换成RNA1类的实例。fromSeq方法将所有其序列参数内的碱基打包进一个数组。然后,将这个数组以及原序列的长度作为参数,调用RNA1的私有构造函数。这利用了一个事实:一个类的私有构造函数对于其伴生对象(companion object)是可见的。 + +创建RNA1实例的第二种途径由RNA1对象中的apply方法提供。它使用一个可变数量的Base类参数,并简单地将其作为序列指向fromSeq方法。这里是两个创建RNA实例的实际方案。 + + scala> val xs = List(A, G, T, A) + xs: List[Product with Base] = List(A, G, T, A) + + scala> RNA1.fromSeq(xs) + res1: RNA1 = RNA1(A, G, T, A) + + scala> val rna1 = RNA1(A, U, G, G, T) + rna1: RNA1 = RNA1(A, U, G, G, T) + +## 控制RNA类型中方法的返回值 + +这里有一些和RNA1抽象之间更多的交互操作 + + scala> rna1.length + res2: Int = 5 + + scala> rna1.last + res3: Base = T + + scala> rna1.take(3) + res4: IndexedSeq[Base] = Vector(A, U, G) + +前两个返回值正如预期,但最后一个——从rna1中获得前3个元素——的返回值则未必如预期。实际上,我们知道一个IndexedSeq[Base]作为返回值的静态类型而一个Vector作为返回值的动态类型,但我们更想看到一个RNA1的值。但这是无法做到的,因为之前在RNA1类中所做的一切仅仅是让RNA1扩展IndexedSeq。换句话说,IndexedSeq类具有一个take方法,其返回一个IndexedSeq。并且,这个方法是根据 IndexedSeq 的默认是用Vector来实现的。所以,这就是上一个交互中最后一行上所能看到的。 + +#### RNA链类的第二个版本 + + final class RNA2 private ( + val groups: Array[Int], + val length: Int + ) extends IndexedSeq[Base] with IndexedSeqLike[Base, RNA2] { + + import RNA2._ + + override def newBuilder: Builder[Base, RNA2] = + new ArrayBuffer[Base] mapResult fromSeq + + def apply(idx: Int): Base = // as before + } + +现在,明白了本质之后,下一个问题便是如何去改变它们。一种途径便是覆写(override)RNA1类中的take方法,可能如下所示: + + def take(count: Int): RNA1 = RNA1.fromSeq(super.take(count)) + +这对take函数有效,但drop、filter或者init又如何呢?事实上,序列(Sequence)中有超过50个方法同样返回序列。为了保持一致,所有这些方法都必须被覆写。这看起来越来越不像一个有吸引力的选择。幸运的是,有一种更简单的途径来达到同样的效果。RNA类不仅需要继承自IndexedSeq类,同时继承自它的实现trait(特性)IndexedSeqLike。如上面的RNA2所示。新的实现在两个方面与之前不同。第一个,RNA2类现在同样扩展自IndexedSeqLike[Base, RNA2]。这个IndexedSeqLike trait(特性)以可扩展的方式实现了所有IndexedSeq的具体方法。比如,如take、drop、filer或init的返回值类型即是传给IndexedSeqLike类的第二个类型参数,也就是说,在RNA2中的是RNA2本身。 + +为了能够做,IndexedSeqLike将自身建立在newBuilder抽象上,这个抽象能够创建正确类型的builder。IndexedSeqLike trait(特性)的子类必须覆写newBuilder以返回一个它们自身类型的容器。在RNA2类中,newBuilder方法返回一个Builder[Base, RNA2]类型的builder。 + +为了构造这个builder,首先创建一个ArrayBuffer,其自身就是一个Builder[Base, ArrayBuffer]。然后通过调用其mapResult方法来将这个ArrayBuffer转换为一个RNA2 builder。mapResult方法需要一个从ArrayBuffer到RNA2的转换函数来作为其参数。转换函数仅仅提供RNA2.fromSeq,其将一个任意的碱基序列转换为RNA2值(之前提到过,数组缓冲是一种序列,所以RNA2.fromSeq可对其使用)。 + +如果忘记声明newBuilder,将会得到一个如下的错误信息: + + RNA2.scala:5: error: overriding method newBuilder in trait + TraversableLike of type => scala.collection.mutable.Builder[Base,RNA2]; + method newBuilder in trait GenericTraversableTemplate of type + => scala.collection.mutable.Builder[Base,IndexedSeq[Base]] has + incompatible type + class RNA2 private (val groups: Array[Int], val length: Int) ^ + + one error found(发现一个错误) + +错误信息非常地长,并且很复杂,体现了容器(Collection)库错综复杂的组合。所以,最好忽略有关这些方法来源的信息,因为在这种情况下,它更多得是分散人的精力。而剩下的,则说明需要声明一个具有返回类型Builder[Base, RNA2]的newBuilder方法,但无法找到一个具有返回类型Builder[Base,IndexedSeq[Base]]的newBuilder方法。后者并不覆写前者。第一个方法——返回值类型为Builder[Base, RNA2]——是一个抽象方法,其在RNA2类中通过传递RNA2的类型参数给IndexedSeqLike,来以这种类型实例化。第二个方法的返回值类型为Builder[Base,IndexedSeq[Base]]——是由继承后的IndexedSeq类提供的。换句话说,如果没有声明一个以第一个返回值类型为返回值的newBuilder,RNA2类就是非法的。 + +改善了RNA2类中的实现之后,take、drop或filter方法现在便会按照预期执行: + + scala> val rna2 = RNA2(A, U, G, G, T) + rna2: RNA2 = RNA2(A, U, G, G, T) + + scala> rna2 take 3 + res5: RNA2 = RNA2(A, U, G) + + scala> rna2 filter (U !=) + res6: RNA2 = RNA2(A, G, G, T) + +### 使用map(映射)和friends(友元) + +然而,在容器中存在没有被处理的其他类别的方法。这些方法就不总会返回容器类型。它们可能返回同一类型的容器,但包含不同类型的元素。典型的例子就是map方法。如果s是一个Int的序列(Seq[Int]),f是将Int转换为String的方法,那么,s.map(f)将返回一个String的序列(Seq[String])。这样,元素类型在接收者和结果之间发生了改变,但容器的类型还是保持一致。 + +有一些其他的方法的行为与map类似,比如说flatMap、collect等,但另一些则不同。例如:++这个追加方法,它也可能因参数返回一个不同类型的结果——向Int类型的列表拼接一个String类型的列表将会得到一个Any类型的列表。至于这些方法如何适应RNA链,理想情况下应认为,在RNA链上进行碱基到碱基的映射将产生另外一个RNA链。(译者注:碱基为RNA链的“元素”) + + scala> val rna = RNA(A, U, G, G, T) + rna: RNA = RNA(A, U, G, G, T) + + scala> rna map { case A => T case b => b } + res7: RNA = RNA(T, U, G, G, T) + +同样,用 ++ 方法来拼接两个RNA链应该再次产生另外一个RNA链。 + + scala> rna ++ rna + res8: RNA = RNA(A, U, G, G, T, A, U, G, G, T) + +另一方面,在RNA链上进行碱基(类型)到其他类型的映射无法产生另外一个RNA链,因为新的元素有错误的类型。它只能产生一个序列而非RNA链。同样,向RNA链追加非Base类型的元素可以产生一个普通序列,但无法产生另一个RNA链。 + + scala> rna map Base.toInt + res2: IndexedSeq[Int] = Vector(0, 3, 2, 2, 1) + + scala> rna ++ List("missing", "data") + res3: IndexedSeq[java.lang.Object] = + Vector(A, U, G, G, T, missing, data) + +这就是在理想情况下应认为结果。但是,RNA2类并不提供这样的处理。事实上,如果你用RNA2类的实例来运行前两个例子,结果则是: + + scala> val rna2 = RNA2(A, U, G, G, T) + rna2: RNA2 = RNA2(A, U, G, G, T) + + scala> rna2 map { case A => T case b => b } + res0: IndexedSeq[Base] = Vector(T, U, G, G, T) + + scala> rna2 ++ rna2 + res1: IndexedSeq[Base] = Vector(A, U, G, G, T, A, U, G, G, T) + +所以,即使生成的容器元素类型是Base,map和++的结果也永远不会是RNA链。如需改善,则需要仔细查看map方法的签名(或++,它也有类似的方法签名)。map的方法最初在scala.collection.TraversableLike类中定义,具有如下签名: + + def map[B, That](f: A => B) + (隐含CBF:CanBuildFrom[修订版,B]): + +这里的A是一个容器元素的类型,而Repr是容器本身的类型,即传递给实现类(例如 TraversableLike和IndexedSeqLike)的第二个参数。map方法有两个以上的参数,B和That。参数B表示映射函数的结果类型,同时也是新容器中的元素类型。That作为map的结果类型。所以,That表示所创建的新容器的类型。 + +对于That类型如何确定,事实上,它是根据隐式参数cbf(CanBuildFrom[Repr,B,That]类型)被链接到其他类型。这些隐式CanBuildFrom由独立的容器类定义。大体上,CanBuildFrom[From,Elem,To]类型的值可以描述为:“有这么一种方法,由给定的From类型的容器,使用Elem类型,建立To的容器。” + +#### RNA链类的最终版本 + + final class RNA private (val groups: Array[Int], val length: Int) + extends IndexedSeq[Base] with IndexedSeqLike[Base, RNA] { + + import RNA._ + + // 在IndexedSeq中必须重新实现newBuilder + override protected[this] def newBuilder: Builder[Base, RNA] = + RNA.newBuilder + + // 在IndexedSeq中必须实现apply + def apply(idx: Int): Base = { + if (idx < 0 || length <= idx) + throw new IndexOutOfBoundsException + Base.fromInt(groups(idx / N) >> (idx % N * S) & M) + } + + // (可选)重新实现foreach, + // 来提高效率 + override def foreach[U](f: Base => U): Unit = { + var i = 0 + var b = 0 + while (i < length) { + b = if (i % N == 0) groups(i / N) else b >>> S + f(Base.fromInt(b & M)) + i += 1 + } + } + } + +#### RNA伴生对象的最终版本 + + object RNA { + + private val S = 2 // group中的比特(bit)数 + private val M = (1 << S) - 1 // 用于隔离group的比特掩码 + private val N = 32 / S // 一个Int中的group数 + + def fromSeq(buf: Seq[Base]): RNA = { + val groups = new Array[Int]((buf.length + N - 1) / N) + for (i <- 0 until buf.length) + groups(i / N) |= Base.toInt(buf(i)) << (i % N * S) + new RNA(groups, buf.length) + } + + def apply(bases: Base*) = fromSeq(bases) + + def newBuilder: Builder[Base, RNA] = + new ArrayBuffer mapResult fromSeq + + implicit def canBuildFrom: CanBuildFrom[RNA, Base, RNA] = + new CanBuildFrom[RNA, Base, RNA] { + def apply(): Builder[Base, RNA] = newBuilder + def apply(from: RNA): Builder[Base, RNA] = newBuilder + } + } + +现在在RNA2序列链上的 map 和 ++ 的行为变得更加清晰了。由于没有能够创建RNA2序列的CanBuildFrom实例,因此在从trait InexedSeq继承的伴生对象上得到的CanBuildFrom成为了第二选择。这隐式地创建了IndexedSeqs,也是在应用map到RNA2的时候发生的情况。 + +为了解决这个缺点,你需要在RNA类的同伴对象里定义CanBuildFrom的隐式实例。该实例的类型应该是CanBuildFrom [RNA, Base, RNA] 。即,这个实例规定,给定一个RNA链和新元素类型Base,可以建立另一个RNA链容器。上述有关RNA链的两个代码以及其伴生对象展示了细节。相较于类RNA2有两个重要的区别。首先, newBuilder的实现,从RNA类移到了它的伴生对象中。RNA类中新的newBuilder方法只是转发这个定义。其次,在RNA对象现在有个隐式的CanBuildFrom值。要创建这样的对象你需要在CanBuildFrom trait中定义两个apply方法。同时,为容器RNA创建一个新的builder,但参数列表不同。在apply()方法只是简单地以正确的类型创建builder。相比之下,apply(from)方法将原来的容器作为参数。在适应动态类型的builder的返回值与接收者的动态类型一致非常有用。在RNA的情况下,这不会起作用,因为RNA是final类,所以静态类型的RNA任何接收者同时具有RNA作为其动态类型。这就是为什么apply(from)也只是简单地调用newBuilder ,忽略其参数。 + +这样,RNA类(final)以原本的类型实现了所有容器方法。它的实现需要一些协议支持。从本质上讲,你需要知道newBuilder 工厂放在哪里以及canBuildFrom的隐式实现。在有利方面,能够以相对较少的代码,得到大量自动定义的方法。另外,如果不打算在容器中扩展take,drop,map或++这样操作,可以不写额外的代码,并在类RNA1所示的实现上结束工作。 + +到目前为止,讨论集中在以定义新序列所需的最少方法来获得特定类型。但在实践中,可能需要在序列上添加新的功能,或重写现有的方法,以获得更好的效果。其中一个例子就是重写RNA类的foreach方法。foreach是RNA本身的一个重要方法,因为它实现了遍历容器。此外,容器的许多其他方法的实现依赖foreach。因此,投入一些精力来做优化方法的实现有意义。IndexedSeq的foreach方法的标准实现仅仅使用aplly来选取容器的中的第i个元素(i从0到容器长度-1)。因此,对RNA链的每一个元素,标准实现选择一个数组元素,并从中解开一个碱基(base)。而RNA类上重写的foreach要聪明得多。对于每一个选定的数组元素,它立刻对其中所包含的所有碱基应用给定的方法。因此,数组选择和位拆包的工作大大减少。 + +### 整合 sets与 map + +在第二个实例中,将介绍如何将一个新的map类型整合到容器框架中的。其方式是通过使用关键字“Patricia trie”,实现以String作为类型的可变映射(mutable map)。术语“Patricia“实际上就是"Practical Algorithm to Retrieve Information Coded in Alphanumeric."(检索字母数字编码信息的实用算法) 的缩写。思想是以树的形式存储一个set或者map,在这种树中,后续字符作为子树可以用唯一确定的关键字查找。例如,一个 Patricia trie存储了三个字符串 "abc", "abd", "al", "all", "xy" 。如下: + +patricia 树的例子: + +![patricia.png](/resources/images/patricia.png) + +为了能够在trie中查找与字符串”abc“匹配的节点,只要沿着标记为”a“的子树,查找到标记为”b“的子树,最后到达标记为”c“的子树。如果 Patricia trie作为map使用,键所对应的值保存在一个可通过键定位的节点上。如果作为set,只需保存一个标记,说明set中存在这个节点。 + +使用Patricia tries的prefix map实现方式: + + import collection._ + + class PrefixMap[T] + extends mutable.Map[String, T] + with mutable.MapLike[String, T, PrefixMap[T]] { + + var suffixes: immutable.Map[Char, PrefixMap[T]] = Map.empty + var value: Option[T] = None + + def get(s: String): Option[T] = + if (s.isEmpty) value + else suffixes get (s(0)) flatMap (_.get(s substring 1)) + + def withPrefix(s: String): PrefixMap[T] = + if (s.isEmpty) this + else { + val leading = s(0) + suffixes get leading match { + case None => + suffixes = suffixes + (leading -> empty) + case _ => + } + suffixes(leading) withPrefix (s substring 1) + } + + override def update(s: String, elem: T) = + withPrefix(s).value = Some(elem) + + override def remove(s: String): Option[T] = + if (s.isEmpty) { val prev = value; value = None; prev } + else suffixes get (s(0)) flatMap (_.remove(s substring 1)) + + def iterator: Iterator[(String, T)] = + (for (v <- value.iterator) yield ("", v)) ++ + (for ((chr, m) <- suffixes.iterator; + (s, v) <- m.iterator) yield (chr +: s, v)) + + def += (kv: (String, T)): this.type = { update(kv._1, kv._2); this } + + def -= (s: String): this.type = { remove(s); this } + + override def empty = new PrefixMap[T] + } + +Patricia tries支持非常高效的查找和更新。另一个良好的特点是,支持通过前缀查找子容器。例如,在上述的patricia tree中,你可以从树根处按照“a”链接进行查找,获得所有以”a“为开头的键所组成的子容器。 + +依据这些思想,来看一下作为Patricia trie的映射实现方式。这种map称为PrefixMap。PrefixMap提供了withPrefix方法,这个方法根据给定的前缀查找子映射(submap),其包含了所有匹配该前缀的键。首先,使用键来定义一个prefix map,执行如下。 + + scala> val m = PrefixMap("abc" -> 0, "abd" -> 1, "al" -> 2, + "all" -> 3, "xy" -> 4) + m: PrefixMap[Int] = Map((abc,0), (abd,1), (al,2), (all,3), (xy,4)) + +然后,在m中调用withPrefix方法将产生另一个prefix map: + + scala> m withPrefix "a" + res14: PrefixMap[Int] = Map((bc,0), (bd,1), (l,2), (ll,3)) + +上面展示的代码表明了PrefixMap的定义方式。这个类以关联值T参数化,并扩展mutable.Map[String, T] 与 mutable.MapLike[String, T, PrefixMap[T]]。在RNA链的例子中对序列的处理也使用了这种模式,然后,为转换(如filter)继承实现类(如MapLike),用以获得正确的结果类型。 + +一个prefix map节点中含有两个可变字段:suffixes与value。value字段包含关联此节点的任意值,其初始化为None。suffixes字段包含从字符到PrefixMap值的映射(map),其初始化为空的map。 + +为什么要选择一种不可变map作为suffixes的实现方式?既然PrefixMap在整体上也是可变的,使用可变映射(map)是否更符合标准吗?这个问题的答案是,因为仅包含少量元素的不可变map,在空间和执行时间都非常高效。例如,包含有少于5个元素的map代表一个单独的对象。相比之下,就标准的可变map中的HashMap来说,即使为空时,也至少占用80bytes的空间。因此,如果普遍使用小容器,不可变的容器就优于可变的容器。在Patricia tries的例子中,预想除了树顶端节点之外,大部分节点仅包含少量的successor。所以在不可变映射(map)中存储这些successor效率很可能会更高。 + +现在看看映射(map)中第一个方法的实现:get。算法如下:为了获取prefix map里面和空字符串相关的值,简单地选取存储在树根节点上的任意值。另外,如果键字符串非空,尝试选取字符串的首字符匹配的子映射。如果产生一个map,继续去寻找map里面首个字符的之后的剩余键字符串。如果选取失败,键没有存储在map里面,则返回None。使用flatmap可以优雅地表示任意值上的联合选择。当对任意值ov,以及闭包f(其转而会返回任的值)进行应用时,如果ov和f都返回已定义的值,那么ov flatmap f将执行成功,否则ov flatmap f将返回None。 + +可变映射(map)之后的两个方法的实现是+=和-=。在prefixmap的实现中,它们按照其它两种方法定义:update和remove。 + +remove方法和get方法非常类似,除了在返回任何关联值之前,保存这个值的字段被设置为None。update方法首先会调用 withPrefix 方法找到需要被更新的树节点,然后将给定的值赋值给该节点的value字段。withPrefix 方法遍历整个树,如果在这个树中没有发现以这些前缀字符为节点的路径,它会根据需要创建子映射(sub-map)。 + +可变map最后一个需要实现的抽象方法是iterator。这个方法需要创建一个能够遍历map中所有键值对的迭代器iterator。对于任何给出的 prefix map,iterator 由如下几部分组成:首先,如果这个map中,在树根节点的value字段包含一个已定义的值Some(x),那么("", x)应为从iterator返回的第一个元素。此外,iterator需要串联存储在suffixes字段上的所有submap的iterator,并且还需要在这些返回的iterator每个键字符串的前面加上一个字符。进一步说,如果m是通过一个字符chr链接到根节点的submap ,并且(s, v)是一个从m.iterator返回的元素,那么这个根节点的iterator 将会转而返回(chr +: s, v)。在PrefixMap的iterator方法的实现中,这个逻辑可以非常简明地用两个for表达式实现。第一个for表达式在value.iterate上迭代。这表明Option值定义一个迭代器方法,如果Option值为None,则不返回任何元素。如果Option值为Some(x),则返回一个确切的元素x。 + +prefix map的伴生对象: + + import scala.collection.mutable.{Builder, MapBuilder} + import scala.collection.generic.CanBuildFrom + + object PrefixMap extends { + def empty[T] = new PrefixMap[T] + + def apply[T](kvs: (String, T)*): PrefixMap[T] = { + val m: PrefixMap[T] = empty + for (kv <- kvs) m += kv + m + } + + def newBuilder[T]: Builder[(String, T), PrefixMap[T]] = + new MapBuilder[String, T, PrefixMap[T]](empty) + + implicit def canBuildFrom[T] + : CanBuildFrom[PrefixMap[_], (String, T), PrefixMap[T]] = + new CanBuildFrom[PrefixMap[_], (String, T), PrefixMap[T]] { + def apply(from: PrefixMap[_]) = newBuilder[T] + def apply() = newBuilder[T] + } + } + +请注意,在PrefixMap中没有newBuilder方法的定义。这是没有必要的,因为maps和sets有默认的构造器,即MapBuilder类的实例。对可变映射来说,其默认的构造器初始时是一个空映射,然后使用映射的+= 方法连续增加元素。可变集合也类似。非可变映射和非可变集合的默认构造器则不同,它们使用无损的元素添加方法+,而非+=方法。 + +然而,为了构建合适的集合或映射,你都需要从一个空的集合或映射开始。empty方法提供了这样的功能,它是PrefixMap中最后定义的方法,该方法简单的返回一个新的PrefixMap。 + +现在我们来看看PrefixMap的伴生对象。事实上,并不是非要定义这种伴生对象,类PrefixMap自己就可以很好的完成它的功能。PrefixMap 对象的主要作用,是定义一些方便的工厂方法。它也定义了一个 CanBuildFrom,该方法可以让输入工作完成的更好。 + +其中有两个方法值得一提,它们是 empty 和 apply。同样的方法,在Scala的容器框架中的其他容器中都存在,因此在PrefixMap中定义它们也很合理。用这两种方法,你可以像写其他容器一样的编写PrefixMap: + + scala> PrefixMap("hello" -> 5, "hi" -> 2) + res0: PrefixMap[Int] = Map((hello,5), (hi,2)) + + scala> PrefixMap.empty[String] + res2: PrefixMap[String] = Map() + +另一个PrefixMap对象的成员是内置CanBuildFrom实例。它和上一节定义的CanBuildFrom目的相同:使得类似map等方法能返回最合适的类型。以PrefixMap的键值对映射函数为例,只要该函数生成串型和另一种类型组成的键值对,那么结果又会是一个PrefixMap。这里有一个例子: + + scala> res0 map { case (k, v) => (k + "!", "x" * v) } + res8: PrefixMap[String] = Map((hello!,xxxxx), (hi!,xx)) + +给出的函数参数是一个PrefixMap res0的键值对绑定,最终生成串型值对。map的结果是一个PrefixMap,只是String类型替代了int类型。如果在PrefixMap中没有内置的canBuildFrom ,那么结果将是一个普通的可变映射,而不是一个PrefixMap。 + +### 小结 + +总而言之,如果你想要将一个新的collection类完全的融入到框架中,需要注意以下几点: + +1. 决定容器应该是可变的,还是非可变的。 +2. 为容器选择正确的基类trait +3. 确保容器继承自适合的trait实现,这样它就能具有大多数的容器操作。 +4. 如果你想要map及类似的操作去返回你的容器类型的实例,那么就需要在类的伴生对象中提供一个隐式CanBuildFrom。 + +你现在已经了解Scala容器如何构建和如何构建新的容器类型。由于Scala丰富的抽象支持,新容器类型无需写代码就可以拥有大量的方法实现。 + +### 致谢 + +这些页面的素材改编自,由Odersky,Spoon和Venners编写的[Scala编程](http://www.artima.com/shop/programming_in_scala)第2版 。感谢Artima 对于出版的大力支持。 diff --git a/_zh-cn/overviews/core/futures.md b/_zh-cn/overviews/core/futures.md new file mode 100644 index 0000000000..0abecdd2e5 --- /dev/null +++ b/_zh-cn/overviews/core/futures.md @@ -0,0 +1,503 @@ +--- +layout: singlepage-overview +title: Future和Promise + +partof: futures + +language: zh-cn + +discourse: false +--- + +**Philipp Haller, Aleksandar Prokopec, Heather Miller, Viktor Klang, Roland Kuhn, and Vojin Jovanovic 著** + +## 简介 + +Future提供了一套高效便捷的非阻塞并行操作管理方案。其基本思想很简单,所谓Future,指的是一类占位符对象,用于指代某些尚未完成的计算的结果。一般来说,由Future指代的计算都是并行执行的,计算完毕后可另行获取相关计算结果。以这种方式组织并行任务,便可以写出高效、异步、非阻塞的并行代码。 + +默认情况下,future和promise并不采用一般的阻塞操作,而是依赖回调进行非阻塞操作。为了在语法和概念层面更加简明扼要地使用这些回调,Scala还提供了flatMap、foreach和filter等算子,使得我们能够以非阻塞的方式对future进行组合。当然,future仍然支持阻塞操作——必要时,可以阻塞等待future(不过并不鼓励这样做)。 + +## Future + +所谓Future,是一种用于指代某个尚未就绪的值的对象。而这个值,往往是某个计算过程的结果: + +- 若该计算过程尚未完成,我们就说该Future未就位; +- 若该计算过程正常结束,或中途抛出异常,我们就说该Future已就位。 + +Future的就位分为两种情况: + +- 当Future带着某个值就位时,我们就说该Future携带计算结果成功就位。 +- 当Future因对应计算过程抛出异常而就绪,我们就说这个Future因该异常而失败。 + +Future的一个重要属性在于它只能被赋值一次。一旦给定了某个值或某个异常,future对象就变成了不可变对象——无法再被改写。 + +创建future对象最简单的方法是调用future方法,该future方法启用异步(asynchronous)计算并返回保存有计算结果的futrue,一旦该future对象计算完成,其结果就变的可用。 + +注意_Future[T]_ 是表示future对象的类型,而future是方法,该方法创建和调度一个异步计算,并返回随着计算结果而完成的future对象。 + +这最好通过一个例子予以说明。 + +假设我们使用某些流行的社交网络的假定API获取某个用户的朋友列表,我们将打开一个新对话(session),然后发送一个请求来获取某个特定用户的好友列表。 + + import scala.concurrent._ + import ExecutionContext.Implicits.global + + val session = socialNetwork.createSessionFor("user", credentials) + val f: Future[List[Friend]] = Future { + session.getFriends() + } + +以上,首先导入scala.concurrent 包使得Future类型和future构造函数可见。我们将马上解释第二个导入。 + +然后我们初始化一个session变量来用作向服务器发送请求,用一个假想的 createSessionFor 方法来返回一个List[Friend]。为了获得朋友列表,我们必须通过网络发送一个请求,这个请求可能耗时很长。这能从调用getFriends方法得到解释。为了更好的利用CPU,响应到达前不应该阻塞(block)程序的其他部分执行,于是在计算中使用异步。future方法就是这样做的,它并行地执行指定的计算块,在这个例子中是向服务器发送请求和等待响应。 + +一旦服务器响应,future f 中的好友列表将变得可用。 + +未成功的尝试可能会导致一个异常(exception)。在下面的例子中,session的值未被正确的初始化,于是在future的计算中将抛出NullPointerException,future f 不会圆满完成,而是以此异常失败。 + + val session = null + val f: Future[List[Friend]] = Future { + session.getFriends + } + +`import ExecutionContext.Implicits.global` 上面的线条导入默认的全局执行上下文(global execution context),执行上下文执行执行提交给他们的任务,也可把执行上下文看作线程池,这对于future方法来说是必不可少的,因为这可以处理异步计算如何及何时被执行。我们可以定义自己的执行上下文,并在future上使用它,但是现在只需要知道你能够通过上面的语句导入默认执行上下文就足够了。 + +我们的例子是基于一个假定的社交网络API,此API的计算包含发送网络请求和等待响应。提供一个涉及到你能试着立即使用的异步计算的例子是公平的。假设你有一个文本文件,你想找出一个特定的关键字第一次出现的位置。当磁盘正在检索此文件内容时,这种计算可能会陷入阻塞,因此并行的执行该操作和程序的其他部分是合理的(make sense)。 + + val firstOccurrence: Future[Int] = Future { + val source = scala.io.Source.fromFile("myText.txt") + source.toSeq.indexOfSlice("myKeyword") + } + +### Callbacks(回调函数) + +现在我们知道如何开始一个异步计算来创建一个新的future值,但是我们没有展示一旦此结果变得可用后如何来使用,以便我们能够用它来做一些有用的事。我们经常对计算结果感兴趣而不仅仅是它的副作用。 + +在许多future的实现中,一旦future的client对future的结果感兴趣,它不得不阻塞它自己的计算直到future完成——然后才能使用future的值继续它自己的计算。虽然这在Scala的Future API(在后面会展示)中是允许的,但是从性能的角度来看更好的办法是一种完全非阻塞的方法,即在future中注册一个回调,future完成后这个回调称为异步回调。如果当注册回调时future已经完成,则回调可能是异步执行的,或在相同的线程中循序执行。 + +注册回调最通常的形式是使用OnComplete方法,即创建一个`Try[T] => U`类型的回调函数。如果future成功完成,回调则会应用到Success[T]类型的值中,否则应用到` Failure[T] `类型的值中。 + + `Try[T]` 和`Option[T]`或 `Either[T, S]`相似,因为它是一个可能持有某种类型值的单子。然而,它是特意设计来保持一个值或某个可抛出(throwable)对象。`Option[T]` 既可以是一个值(如:`Some[T]`)也可以是完全无值(如:`None`),如果`Try[T]`获得一个值则它为`Success[T]` ,否则为`Failure[T]`的异常。 `Failure[T]` 获得更多的关于为什么这儿没值的信息,而不仅仅是None。同时也可以把`Try[T]`看作一种特殊版本的`Either[Throwable, T]`,专门用于左值为可抛出类型(Throwable)的情形。 + +回到我们的社交网络的例子,假设我们想要获取我们最近的帖子并显示在屏幕上,我们通过调用getRecentPosts方法获得一个返回值List[String]——一个近期帖子的列表文本: + + val f: Future[List[String]] = Future { + session.getRecentPosts + } + + f onComplete { + case Success(posts) => for (post <- posts) println(post) + case Success(posts) => for (post <- posts) println(post) + } + +onComplete方法一般在某种意义上它允许客户处理future计算出的成功或失败的结果。对于仅仅处理成功的结果,onSuccess 回调使用如下(该回调以一个偏函数(partial function)为参数): + + val f: Future[List[String]] = Future { + session.getRecentPosts + } + + f onSuccess { + case posts => for (post <- posts) println(post) + } + +对于处理失败结果,onFailure回调使用如下: + + val f: Future[List[String]] = Future { + session.getRecentPosts + } + + f onFailure { + case t => println("An error has occured: " + t.getMessage) + } + + f onSuccess { + case posts => for (post <- posts) println(post) + } + +如果future失败,即future抛出异常,则执行onFailure回调。 + +因为偏函数具有 isDefinedAt方法, onFailure方法只有在特定的Throwable类型对象中被定义才会触发。下面例子中的onFailure回调永远不会被触发: + + val f = Future { + 2 / 0 + } + + f onFailure { + case npe: NullPointerException => + println("I'd be amazed if this printed out.") + } + +回到前面查找某个关键字第一次出现的例子,我们想要在屏幕上打印出此关键字的位置: + + val firstOccurrence: Future[Int] = Future { + val source = scala.io.Source.fromFile("myText.txt") + source.toSeq.indexOfSlice("myKeyword") + } + + firstOccurrence onSuccess { + case idx => println("The keyword first appears at position: " + idx) + } + + firstOccurrence onFailure { + case t => println("Could not process file: " + t.getMessage) + } + + onComplete,、onSuccess 和 onFailure 方法都具有Unit的结果类型,这意味着不能链接使用这些方法的回调。注意这种设计是为了避免暗示而刻意为之的,因为链接回调也许暗示着按照一定的顺序执行注册回调(回调注册在同一个future中是无序的)。 + +也就是说,我们现在应讨论论何时调用callback。因为callback需要future的值是可用的,所有回调只能在future完成之后被调用。然而,不能保证callback在完成future的线程或创建callback的线程中被调用。反而, 回调(callback)会在future对象完成之后的一些线程和一段时间内执行。所以我们说回调(callback)最终会被执行。 + +此外,回调(callback)执行的顺序不是预先定义的,甚至在相同的应用程序中callback的执行顺序也不尽相同。事实上,callback也许不是一个接一个连续的调用,但是可能会在同一时间同时执行。这意味着在下面的例子中,变量totalA也许不能在计算上下文中被设置为正确的大写或者小写字母。 + + @volatile var totalA = 0 + + val text = Future { + "na" * 16 + "BATMAN!!!" + } + + text onSuccess { + case txt => totalA += txt.count(_ == 'a') + } + + text onSuccess { + case txt => totalA += txt.count(_ == 'A') + } + +以上,这两个回调(callbacks)可能是一个接一个地执行的,这样变量totalA得到的预期值为18。然而,它们也可能是并发执行的,于是totalA最终可能是16或2,因为+= 是一个不可分割的操作符(即它是由一个读和一个写的步骤组成,这样就可能使其与其他的读和写任意交错执行)。 + +考虑到完整性,回调的使用情景列在这儿: + +- 在future中注册onComplete回调的时候要确保最后future执行完成之后调用相应的终止回调。 + +- 注册onSuccess或者onFailure回调时也和注册onComplete一样,不同之处在于future执行成功或失败分别调用onSuccess或onSuccess的对应的闭包。 + +- 注册一个已经完成的future的回调最后将导致此回调一直处于执行状态(1所隐含的)。 + +- 在future中注册多个回调的情况下,这些回调的执行顺序是不确定的。事实上,这些回调也许是同时执行的,然而,特定的ExecutionContext执行可能导致明确的顺序。 + +- 在一些回调抛出异常的情况下,其他的回调的执行不受影响。 + +- 在一些情况下,回调函数永远不能结束(例如,这些回调处于无限循环中),其他回调可能完全不会执行。在这种情况下,对于那些潜在的阻塞回调要使用阻塞的构造(例子如下)。 + +- 一旦执行完,回调将从future对象中移除,这样更适合JVM的垃圾回收机制(GC)。 + +### 函数组合(Functional Composition)和For解构(For-Comprehensions) + +尽管前文所展示的回调机制已经足够把future的结果和后继计算结合起来的,但是有些时候回调机制并不易于使用,且容易造成冗余的代码。我们可以通过一个例子来说明。假设我们有一个用于进行货币交易服务的API,我们想要在有盈利的时候购进一些美元。让我们先来看看怎样用回调来解决这个问题: + + val rateQuote = Future { + connection.getCurrentValue(USD) + } + + rateQuote onSuccess { case quote => + val purchase = Future { + if (isProfitable(quote)) connection.buy(amount, quote) + else throw new Exception("not profitable") + } + + purchase onSuccess { + case _ => println("Purchased " + amount + " USD") + } + } + +首先,我们创建一个名为rateQuote的future对象并获得当前的汇率。在服务器返回了汇率且该future对象成功完成了之后,计算操作才会从onSuccess回调中执行,这时我们就可以开始判断买还是不买了。所以我们创建了另一个名为purchase的future对象,用来在可盈利的情况下做出购买决定,并在稍后发送一个请求。最后,一旦purchase运行结束,我们会在标准输出中打印一条通知消息。 + +这确实是可行的,但是有两点原因使这种做法并不方便。其一,我们不得不使用onSuccess,且不得不在其中嵌套purchase future对象。试想一下,如果在purchase执行完成之后我们可能会想要卖掉一些其他的货币。这时我们将不得不在onSuccess的回调中重复这个模式,从而可能使代码过度嵌套,过于冗长,并且难以理解。 + +其二,purchase只是定义在局部范围内--它只能被来自onSuccess内部的回调响应。这也就是说,这个应用的其他部分看不到purchase,而且不能为它注册其他的onSuccess回调,比如说卖掉些别的货币。 + +为解决上述的两个问题,futures提供了组合器(combinators)来使之具有更多易用的组合形式。映射(map)是最基本的组合器之一。试想给定一个future对象和一个通过映射来获得该future值的函数,映射方法将创建一个新Future对象,一旦原来的Future成功完成了计算操作,新的Future会通过该返回值来完成自己的计算。你能够像理解容器(collections)的map一样来理解future的map。 + +让我们用map的方法来重构一下前面的例子: + + val rateQuote = Future { + connection.getCurrentValue(USD) + } + + val purchase = rateQuote map { quote => + if (isProfitable(quote)) connection.buy(amount, quote) + else throw new Exception("not profitable") + } + + purchase onSuccess { + case _ => println("Purchased " + amount + " USD") + } + +通过对rateQuote的映射我们减少了一次onSuccess的回调,更重要的是避免了嵌套。这时如果我们决定出售一些货币就可以再次使用purchase方法上的映射了。 + +可是如果isProfitable方法返回了false将会发生些什么?会引发异常?这种情况下,purchase的确会因为异常而失败。不仅仅如此,想象一下,链接的中断和getCurrentValue方法抛出异常会使rateQuote的操作失败。在这些情况下映射将不会返回任何值,而purchase也会会自动的以和rateQuote相同的异常而执行失败。 + +总之,如果原Future的计算成功完成了,那么返回的Future将会使用原Future的映射值来完成计算。如果映射函数抛出了异常则Future也会带着该异常完成计算。如果原Future由于异常而计算失败,那么返回的Future也会包含相同的异常。这种异常的传导方式也同样适用于其他的组合器(combinators)。 + +使之能够在For-comprehensions原则下使用,是设计Future的目的之一。也正是因为这个原因,Future还拥有flatMap,filter和foreach等组合器。其中flatMap方法可以构造一个函数,它可以把值映射到一个姑且称为g的新future,然后返回一个随g的完成而完成的Future对象。 + +让我们假设我们想把一些美元兑换成瑞士法郎。我们必须为这两种货币报价,然后再在这两个报价的基础上确定交易。下面是一个在for-comprehensions中使用flatMap和withFilter的例子: + + val usdQuote = Future { connection.getCurrentValue(USD) } + val chfQuote = Future { connection.getCurrentValue(CHF) } + + val purchase = for { + usd <- usdQuote + chf <- chfQuote + if isProfitable(usd, chf) + } yield connection.buy(amount, chf) + + purchase onSuccess { + case _ => println("Purchased " + amount + " CHF") + } + +purchase只有当usdQuote和chfQuote都完成计算以后才能完成-- 它以其他两个Future的计算值为前提所以它自己的计算不能更早的开始。 + +上面的for-comprhension将被转换为: + + val purchase = usdQuote flatMap { + usd => + chfQuote + .withFilter(chf => isProfitable(usd, chf)) + .map(chf => connection.buy(amount, chf)) + } + +这的确是比for-comprehension稍微难以把握一些,但是我们这样分析有助于您更容易的理解flatMap的操作。FlatMap操作会把自身的值映射到其他future对象上,并随着该对象计算完成的返回值一起完成计算。在我们的例子里,flatMap用usdQuote的值把chfQuote的值映射到第三个futrue对象里,该对象用于发送一定量瑞士法郎的购入请求。只有当通过映射返回的第三个future对象完成了计算,purchase才能完成计算。 + +这可能有些难以置信,但幸运的是faltMap操作在for-comprhensions模式以外很少使用,因为for-comprehensions本身更容易理解和使用。 + +再说说filter,它可以用于创建一个新的future对象,该对象只有在满足某些特定条件的前提下才会得到原始future的计算值,否则就会抛出一个NoSuchElementException的异常而失败。调用了filter的future,其效果与直接调用withFilter完全一样。 + +作为组合器的collect同filter之间的关系有些类似容器(collections)API里的那些方法之间的关系。 + +值得注意的是,调用foreach组合器并不会在计算值可用的时候阻塞当前的进程去获取计算值。恰恰相反,只有当future对象成功计算完成了,foreach所迭代的函数才能够被异步的执行。这意味着foreach与onSuccess回调意义完全相同。 + +由于Future trait(译注: trait有点类似java中的接口(interface)的概念)从概念上看包含两种类型的返回值(计算结果和异常),所以组合器会有一个处理异常的需求。 + +比方说我们准备在rateQuote的基础上决定购入一定量的货币,那么`connection.buy`方法需要知道购入的数量和期望的报价值,最终完成购买的数量将会被返回。假如报价值偏偏在这个节骨眼儿改变了,那buy方法将会抛出一个`QuoteChangedExecption`,并且不会做任何交易。如果我们想让我们的Future对象返回0而不是抛出那个该死的异常,那我们需要使用recover组合器: + + val purchase: Future[Int] = rateQuote map { + quote => connection.buy(amount, quote) + } recover { + case QuoteChangedException() => 0 + } + +这里用到的recover能够创建一个新future对象,该对象当计算完成时持有和原future对象一样的值。如果执行不成功则偏函数的参数会被传递给使原Future失败的那个Throwable异常。如果它把Throwable映射到了某个值,那么新的Future就会成功完成并返回该值。如果偏函数没有定义在Throwable中,那么最终产生结果的future也会失败并返回同样的Throwable。 + +组合器recoverWith能够创建一个新future对象,当原future对象成功完成计算时,新future对象包含有和原future对象相同的计算结果。若原future失败或异常,偏函数将会返回造成原future失败的相同的Throwable异常。如果此时Throwable又被映射给了别的future,那么新Future就会完成并返回这个future的结果。recoverWith同recover的关系跟flatMap和map之间的关系很像。 + +fallbackTo组合器生成的future对象可以在该原future成功完成计算时返回结果,如果原future失败或异常返回future参数对象的成功值。在原future和参数future都失败的情况下,新future对象会完成并返回原future对象抛出的异常。正如下面的例子中,本想打印美元的汇率,但是在获取美元汇率失败的情况下会打印出瑞士法郎的汇率: + + val usdQuote = Future { + connection.getCurrentValue(USD) + } map { + usd => "Value: " + usd + "$" + } + val chfQuote = Future { + connection.getCurrentValue(CHF) + } map { + chf => "Value: " + chf + "CHF" + } + + al anyQuote = usdQuote fallbackTo chfQuote + + anyQuote onSuccess { println(_) } + +组合器andThen的用法是出于纯粹的side-effecting目的。经andThen返回的新Future无论原Future成功或失败都会返回与原Future一模一样的结果。一旦原Future完成并返回结果,andThen后跟的代码块就会被调用,且新Future将返回与原Future一样的结果,这确保了多个andThen调用的顺序执行。正如下例所示,这段代码可以从社交网站上把近期发出的帖子收集到一个可变集合里,然后把它们都打印在屏幕上: + + val allposts = mutable.Set[String]() + + Future { + session.getRecentPosts + } andThen { + case Success(posts) => allposts ++= posts + } andThen { + case _ => + clearAll() + for (post <- allposts) render(post) + } + +综上所述,Future的组合器功能是纯函数式的,每种组合器都会返回一个与原Future相关的新Future对象。 + +### 投影(Projections) + +为了确保for解构(for-comprehensions)能够返回异常,futures也提供了投影(projections)。如果原future对象失败了,失败的投影(projection)会返回一个带有Throwable类型返回值的future对象。如果原Future成功了,失败的投影(projection)会抛出一个NoSuchElementException异常。下面就是一个在屏幕上打印出异常的例子: + + val f = Future { + 2 / 0 + } + for (exc <- f.failed) println(exc) + +下面的例子不会在屏幕上打印出任何东西: + + val f = Future { + 4 / 2 + } + for (exc <- f.failed) println(exc) + +### Future的扩展 + +用更多的实用方法来对Futures API进行扩展支持已经被提上了日程,这将为很多外部框架提供更多专业工具。 + +## Blocking + +正如前面所说的,在future的blocking非常有效地缓解性能和预防死锁。虽然在futures中使用这些功能方面的首选方式是Callbacks和combinators,但在某些处理中也会需要用到blocking,并且它也是被Futures and Promises API所支持的。 + +在之前的并发交易(concurrency trading)例子中,在应用的最后有一处用到block来确定是否所有的futures已经完成。这有个如何使用block来处理一个future结果的例子: + + import scala.concurrent._ + import scala.concurrent.duration._ + + def main(args: Array[String]) { + val rateQuote = Future { + connection.getCurrentValue(USD) + } + + val purchase = rateQuote map { quote => + if (isProfitable(quote)) connection.buy(amount, quote) + else throw new Exception("not profitable") + } + + Await.result(purchase, 0 nanos) + } + +在这种情况下这个future是不成功的,这个调用者转发出了该future对象不成功的异常。它包含了失败的投影(projection)-- 阻塞(blocking)该结果将会造成一个NoSuchElementException异常在原future对象被成功计算的情况下被抛出。 + +相反的,调用`Await.ready`来等待这个future直到它已完成,但获不到它的结果。同样的方式,调用那个方法时如果这个future是失败的,它将不会抛出异常。 + +The Future trait实现了Awaitable trait还有其`ready()`和`result()`方法。这些方法不能被客户端直接调用,它们只能通过执行环境上下文来进行调用。 + +为了允许程序调用可能是阻塞式的第三方代码,而又不必实现Awaitable特质,原函数可以用如下的方式来调用: + + blocking { + potentiallyBlockingCall() + } + +这段blocking代码也可以抛出一个异常。在这种情况下,这个异常会转发给调用者。 + +## 异常(Exceptions) + +当异步计算抛出未处理的异常时,与那些计算相关的futures就失败了。失败的futures存储了一个Throwable的实例,而不是返回值。Futures提供onFailure回调方法,它用一个PartialFunction去表示一个Throwable。下列特殊异常的处理方式不同: + +`scala.runtime.NonLocalReturnControl[_]` --此异常保存了一个与返回相关联的值。通常情况下,在方法体中的返回结构被调用去抛出这个异常。相关联的值将会存储到future或一个promise中,而不是一直保存在这个异常中。 + +ExecutionException-当因为一个未处理的中断异常、错误或者`scala.util.control.ControlThrowable`导致计算失败时会被存储起来。这种情况下,ExecutionException会为此具有未处理的异常。这些异常会在执行失败的异步计算线程中重新抛出。这样做的目的,是为了防止正常情况下没有被客户端代码处理过的那些关键的、与控制流相关的异常继续传播下去,同时告知客户端其中的future对象是计算失败的。 + +更精确的语义描述请参见 [NonFatal]。 + +## Promises + +到目前为止,我们仅考虑了通过异步计算的方式创建future对象来使用future的方法。尽管如此,futures也可以使用promises来创建。 + +如果说futures是为了一个还没有存在的结果,而当成一种只读占位符的对象类型去创建,那么promise就被认为是一个可写的,可以实现一个future的单一赋值容器。这就是说,promise通过这种success方法可以成功去实现一个带有值的future。相反的,因为一个失败的promise通过failure方法就会实现一个带有异常的future。 + +一个promise p通过p.future方式返回future。 这个futrue对象被指定到promise p。根据这种实现方式,可能就会出现p.future与p相同的情况。 + +考虑下面的生产者 - 消费者的例子,其中一个计算产生一个值,并把它转移到另一个使用该值的计算。这个传递中的值通过一个promise来完成。 + + import scala.concurrent.{ Future, Promise } + import scala.concurrent.ExecutionContext.Implicits.global + + val p = Promise[T]() + val f = p.future + + val producer = Future { + val r = produceSomething() + p success r + continueDoingSomethingUnrelated() + } + + val consumer = Future { + startDoingSomething() + f onSuccess { + case r => doSomethingWithResult() + } + } + +在这里,我们创建了一个promise并利用它的future方法获得由它实现的Future。然后,我们开始了两种异步计算。第一种做了某些计算,结果值存放在r中,通过执行promise p,这个值被用来完成future对象f。第二种做了某些计算,然后读取实现了future f的计算结果值r。需要注意的是,在生产者完成执行`continueDoingSomethingUnrelated()` 方法这个任务之前,消费者可以获得这个结果值。 + +正如前面提到的,promises具有单赋值语义。因此,它们仅能被实现一次。在一个已经计算完成的promise或者failed的promise上调用success方法将会抛出一个IllegalStateException异常。 + +下面的这个例子显示了如何fail a promise。 + + val p = promise[T] + val f = p.future + + val producer = Future { + val r = someComputation + if (isInvalid(r)) + p failure (new IllegalStateException) + else { + val q = doSomeMoreComputation(r) + p success q + } + } + +如上,生产者计算出一个中间结果值r,并判断它的有效性。如果它不是有效的,它会通过返回一个异常实现promise p的方式fails the promise,关联的future f是failed。否则,生产者会继续它的计算,最终使用一个有效的结果值实现future f,同时实现 promise p。 + +Promises也能通过一个complete方法来实现,这个方法采用了一个`potential value Try[T]`,这个值要么是一个类型为`Failure[Throwable]`的失败的结果值,要么是一个类型为`Success[T]`的成功的结果值。 + +类似success方法,在一个已经完成(completed)的promise对象上调用failure方法和complete方法同样会抛出一个IllegalStateException异常。 + +应用前面所述的promises和futures方法的一个优点是,这些方法是单一操作的并且是没有副作用(side-effects)的,因此程序是具有确定性的(deterministic)。确定性意味着,如果该程序没有抛出异常(future的计算值被获得),无论并行的程序如何调度,那么程序的结果将会永远是一样的。 + +在一些情况下,客户端也许希望能够只在promise没有完成的情况下完成该promise的计算(例如,如果有多个HTTP请求被多个不同的futures对象来执行,并且客户端只关心地一个HTTP应答(response),该应答对应于地一个完成该promise的future)。因为这个原因,future提供了tryComplete,trySuccess和tryFailure方法。客户端需要意识到调用这些的结果是不确定的,调用的结果将以来从程序执行的调度。 + +completeWith方法将用另外一个future完成promise计算。当该future结束的时候,该promise对象得到那个future对象同样的值,如下的程序将打印1: + + val f = Future { 1 } + val p = promise[Int] + + p completeWith f + + p.future onSuccess { + case x => println(x) + } + +当让一个promise以异常失败的时候,三总子类型的Throwable异常被分别的处理。如果中断该promise的可抛出(Throwable)一场是`scala.runtime.NonLocalReturnControl`,那么该promise将以对应的值结束;如果是一个Error的实例,`InterruptedException`或者`scala.util.control.ControlThrowable`,那么该可抛出(Throwable)异常将会封装一个ExecutionException异常,该ExectionException将会让该promise以失败结束。 + +通过使用promises,futures的onComplete方法和future的构造方法,你能够实现前文描述的任何函数式组合组合器(compition combinators)。让我们来假设一下你想实现一个新的组合起,该组合器首先使用两个future对象f和,产生第三个future,该future能够用f或者g来完成,但是只在它能够成功完成的情况下。 + +这里有个关于如何去做的实例: + + def first[T](f: Future[T], g: Future[T]): Future[T] = { + val p = promise[T] + + f onSuccess { + case x => p.trySuccess(x) + } + + g onSuccess { + case x => p.trySuccess(x) + } + + p.future + } + +注意,在这种实现方式中,如果f与g都不是成功的,那么`first(f, g)`将不会实现(即返回一个值或者返回一个异常)。 + +## 工具(Utilities) + +为了简化在并发应用中处理时序(time)的问题,`scala.concurrent`引入了Duration抽象。Duration不是被作为另外一个通常的时间抽象存在的。他是为了用在并发(concurrency)库中使用的,Duration位于`scala.concurrent`包中。 + +Duration是表示时间长短的基础类,其可以是有限的或者无限的。有限的duration用FiniteDuration类来表示,并通过时间长度`(length)`和`java.util.concurrent.TimeUnit`来构造。无限的durations,同样扩展了Duration,只在两种情况下存在,`Duration.Inf`和`Duration.MinusInf`。库中同样提供了一些Durations的子类用来做隐式的转换,这些子类不应被直接使用。 + +抽象的Duration类包含了如下方法: + +到不同时间单位的转换`(toNanos, toMicros, toMillis, toSeconds, toMinutes, toHours, toDays and toUnit(unit: TimeUnit))`。 +durations的比较`(<,<=,>和>=)`。 +算术运算符`(+, -, *, / 和单值运算_-)` +duration的最大最小方法`(min,max)`。 +测试duration是否是无限的方法`(isFinite)`。 +Duration能够用如下方法实例化`(instantiated)`: + +隐式的通过Int和Long类型转换得来 `val d = 100 millis`。 +通过传递一个`Long length`和`java.util.concurrent.TimeUnit`。例如`val d = Duration(100, MILLISECONDS)`。 +通过传递一个字符串来表示时间区间,例如 `val d = Duration("1.2 µs")`。 +Duration也提供了unapply方法,因此可以i被用于模式匹配中,例如: + + import scala.concurrent.duration._ + import java.util.concurrent.TimeUnit._ + + // instantiation + val d1 = Duration(100, MILLISECONDS) // from Long and TimeUnit + val d2 = Duration(100, "millis") // from Long and String + val d3 = 100 millis // implicitly from Long, Int or Double + val d4 = Duration("1.2 µs") // from String + + // pattern matching + val Duration(length, unit) = 5 millis diff --git a/_zh-cn/overviews/core/implicit-classes.md b/_zh-cn/overviews/core/implicit-classes.md new file mode 100644 index 0000000000..12c6f5f555 --- /dev/null +++ b/_zh-cn/overviews/core/implicit-classes.md @@ -0,0 +1,83 @@ +--- +layout: singlepage-overview +title: Implicit Classes + +partof: implicit-classes + +language: zh-cn + +discourse: false +--- + +**Josh Suereth 著** + +## 介绍 + +Scala 2.10引入了一种叫做隐式类的新特性。隐式类指的是用implicit关键字修饰的类。在对应的作用域内,带有这个关键字的类的主构造函数可用于隐式转换。 + +隐式类型是在[SIP-13](http://docs.scala-lang.org/sips/pending/implicit-classes.html)中提出的。 + +## 用法 + +创建隐式类时,只需要在对应的类前加上implicit关键字。比如: + + object Helpers { + implicit class IntWithTimes(x: Int) { + def times[A](f: => A): Unit = { + def loop(current: Int): Unit = + if(current > 0) { + f + loop(current - 1) + } + loop(x) + } + } + } + +这个例子创建了一个名为IntWithTimes的隐式类。这个类包含一个int值和一个名为times的方法。要使用这个类,只需将其导入作用域内并调用times方法。比如: + + scala> import Helpers._ + import Helpers._ + + scala> 5 times println("HI") + HI + HI + HI + HI + HI + +使用隐式类时,类名必须在当前作用域内可见且无歧义,这一要求与隐式值等其他隐式类型转换方式类似。 + +## 限制条件 + +隐式类有以下限制条件: + +1. 只能在别的trait/类/对象内部定义。 + +```` + object Helpers { + implicit class RichInt(x: Int) // 正确! + } + implicit class RichDouble(x: Double) // 错误! +```` + +2. 构造函数只能携带一个非隐式参数。 +```` + implicit class RichDate(date: java.util.Date) // 正确! + implicit class Indexer[T](collecton: Seq[T], index: Int) // 错误! + implicit class Indexer[T](collecton: Seq[T])(implicit index: Index) // 正确! +```` + +虽然我们可以创建带有多个非隐式参数的隐式类,但这些类无法用于隐式转换。 + +3. 在同一作用域内,不能有任何方法、成员或对象与隐式类同名。 + +注意:这意味着隐式类不能是case class。 + + object Bar + implicit class Bar(x: Int) // 错误! + + val x = 5 + implicit class x(y: Int) // 错误! + + implicit case class Baz(x: Int) // 错误! diff --git a/_zh-cn/overviews/core/string-interpolation.md b/_zh-cn/overviews/core/string-interpolation.md new file mode 100644 index 0000000000..91fed4b818 --- /dev/null +++ b/_zh-cn/overviews/core/string-interpolation.md @@ -0,0 +1,121 @@ +--- +layout: singlepage-overview +title: 字符串插值 + +partof: string-interpolation + +language: zh-cn + +discourse: false +--- + +**Josh Suereth 著** + +## 简介 + +自2.10.0版本开始,Scala提供了一种新的机制来根据数据生成字符串:字符串插值。字符串插值允许使用者将变量引用直接插入处理过的字面字符中。如下例: + + val name="James" + println(s"Hello,$name")//Hello,James + +在上例中, s"Hello,$name" 是待处理字符串字面,编译器会对它做额外的工作。待处理字符串字面通过“号前的字符来标示(例如:上例中是s)。字符串插值的实现细节在 [SIP-11](http://docs.scala-lang.org/sips/pending/string-interpolation.html) 中有全面介绍。 + +## 用法 + +Scala 提供了三种创新的字符串插值方法:s,f 和 raw. + +### s 字符串插值器 + +在任何字符串前加上s,就可以直接在串中使用变量了。你已经见过这个例子: + + val name="James" + println(s"Hello,$name")//Hello,James +此例中,$name嵌套在一个将被s字符串插值器处理的字符串中。插值器知道在这个字符串的这个地方应该插入这个name变量的值,以使输出字符串为Hello,James。使用s插值器,在这个字符串中可以使用任何在处理范围内的名字。 + +字符串插值器也可以处理任意的表达式。例如: + + println(s"1+1=${1+1}") +将会输出字符串1+1=2。任何表达式都可以嵌入到${}中。 + +### f 插值器 + +在任何字符串字面前加上 f,就可以生成简单的格式化串,功能相似于其他语言中的 printf 函数。当使用 f 插值器的时候,所有的变量引用都应当后跟一个printf-style格式的字符串,如%d。看下面这个例子: + + val height=1.9d + val name="James" + println(f"$name%s is $height%2.2f meters tall")//James is 1.90 meters tall +f 插值器是类型安全的。如果试图向只支持 int 的格式化串传入一个double 值,编译器则会报错。例如: + + val height:Double=1.9d + + scala>f"$height%4d" + :9: error: type mismatch; + found : Double + required: Int + f"$height%4d" + ^ +f 插值器利用了java中的字符串数据格式。这种以%开头的格式在 [Formatter javadoc] 中有相关概述。如果在具体变量后没有%,则格式化程序默认使用 %s(串型)格式。 + +### raw 插值器 + +除了对字面值中的字符不做编码外,raw 插值器与 s 插值器在功能上是相同的。如下是个被处理过的字符串: + + scala>s"a\nb" + res0:String= + a + b +这里,s 插值器用回车代替了\n。而raw插值器却不会如此处理。 + + scala>raw"a\nb" + res1:String=a\nb +当不想输入\n被转换为回车的时候,raw 插值器是非常实用的。 + +除了以上三种字符串插值器外,使用者可以自定义插值器。 + +### 高级用法 + +在Scala中,所有处理过的字符串字面值都进行了简单编码转换。任何时候编译器遇到一个如下形式的字符串字面值: + + id"string content" +它都会被转换成一个StringContext实例的call(id)方法。这个方法在隐式范围内仍可用。只需要简单得 +建立一个隐类,给StringContext实例增加一个新方法,便可以定义我们自己的字符串插值器。如下例: + + //注意:为了避免运行时实例化,我们从AnyVal中继承。 + //更多信息请见值类的说明 + implicit class JsonHelper(val sc:StringContext) extends AnyVal{ + def json(args:Any*):JSONObject=sys.error("TODO-IMPLEMENT") + } + + def giveMeSomeJson(x:JSONObject):Unit=... + + giveMeSomeJson(json"{name:$name,id:$id}") +在这个例子中,我们试图通过字符串插值生成一个JSON文本语法。隐类 JsonHelper 作用域内使用该语法,且这个JSON方法需要一个完整的实现。只不过,字符串字面值格式化的结果不是一个字符串,而是一个JSON对象。 + +当编译器遇到"{name:$name,id:$id"}",它将会被重写成如下表达式: + + new StringContext("{name:",",id:","}").json(name,id) + +隐类则被重写成如下形式 + + new JsonHelper(new StringContext("{name:",",id:","}")).json(name,id) + +所以,JSON方法可以访问字符串的原生片段而每个表达式都是一个值。这个方法的一个简单但又令人迷惑的例子: + + implicit class JsonHelper(val sc:StringContext) extends AnyVal{ + def json(args:Any*):JSONObject={ + val strings=sc.parts.iterator + val expressions=args.iterator + var buf=new StringBuffer(strings.next) + while(strings.hasNext){ + buf append expressions.next + buf append strings.next + } + parseJson(buf) + } + } + +被处理过的字符串的每部分都是StringContext的成员。每个表达式的值都将传入到JSON方法的args参数。JSON方法接受这些值并合成一个大字符串,然后再解析成JSON格式。有一种更复杂的实现可以避免合成字符串的操作,它只是简单的直接通过原生字符串和表达式值构建JSON。 + +## 限制 + +字符串插值目前对模式匹配语句不适用。此特性将在2.11版本中生效。 diff --git a/_zh-cn/overviews/core/value-classes.md b/_zh-cn/overviews/core/value-classes.md new file mode 100644 index 0000000000..85014abdb8 --- /dev/null +++ b/_zh-cn/overviews/core/value-classes.md @@ -0,0 +1,260 @@ +--- +layout: singlepage-overview +title: Value Classes and Universal Traits + +partof: value-classes + +language: zh-cn + +discourse: false +--- + +**Mark Harrah 著** + +## 引言 + +Value classes是在[SIP-15](http://docs.scala-lang.org/sips/pending/value-classes.html)中提出的一种通过继承AnyVal类来避免运行时对象分配的新机制。以下是一个最简的value class。 + + class Wrapper(val underlying: Int) extends AnyVal + +它仅有一个被用作运行时底层表示的公有val参数。在编译期,其类型为Wrapper,但在运行时,它被表示为一个Int。Value class可以带有def定义,但不能再定义额外的val、var,以及内嵌的trait、class或object: + + class Wrapper(val underlying: Int) extends AnyVal { + def foo: Wrapper = new Wrapper(underlying * 19) + } + +Value class只能继承universal traits,但其自身不能再被继承。所谓universal trait就是继承自Any的、只有def成员,且不作任何初始化工作的trait。继承自某个universal trait的value class同时继承了该trait的方法,但是(调用这些方法)会带来一定的对象分配开销。例如: + + trait Printable extends Any { + def print(): Unit = println(this) + } + class Wrapper(val underlying: Int) extends AnyVal with Printable + + val w = new Wrapper(3) + w.print() // 这里实际上会生成一个Wrapper类的实例 + +本文后续篇幅将介绍相关用例和与对象分配时机相关的细节,并给出一些有关value class自身限制的具体实例。 + +## 扩展方法 + +关于value类的一个用例,是将它们和隐含类联合([SIP-13](http://docs.scala-lang.org/sips/pending/implicit-classes.html))以获得免分配扩展方法。使用隐含类可以提供便捷的语法来定义扩展方法,同时 value 类移除运行时开销。一个好的例子是在标准库里的RichInt类。RichInt 继承自Int类型并附带一些方法。由于它是一个 value类,使用RichInt 方法时不需要创建一个RichInt 的实例。 + +下面有关RichInt的代码片段示范了RichInt是如何继承Int来允许3.toHexString的表达式: + + implicit class RichInt(val self: Int) extends AnyVal { + def toHexString: String = java.lang.Integer.toHexString(self) + } + +在运行时,表达式3.toHexString 被优化并等价于静态对象的方法调用 (RichInt$.MODULE$.extension$toHexString(3)),而不是创建一个新实例对象,再调用其方法。 + +## 正确性 + +关于value类的另一个用例是:不增加运行时开销的同时,获得数据类型的类型安全。例如,一个数据类型片断代表一个距离 ,如: + + class Meter(val value: Double) extends AnyVal { + def +(m: Meter): Meter = new Meter(value + m.value) + } + +代码:对两个距离进行相加,例如: + + val x = new Meter(3.4) + val y = new Meter(4.3) + val z = x + y + +实际上不会分配任何Meter实例,而是在运行时仅使用原始双精浮点数(double) 。 + +注意:在实践中,可以使用条件类(case)and/or 扩展方法来让语句更清晰。 + +## 必须进行分配的情况 + +由于JVM不支持value类,Scala 有时需要真正实例化value类。详细细节见[SIP-15]。 + +### 分配概要 + +value类在以下情况下,需要真正实例化: + +1. value类作为另一种类型使用时。 +2. value类被赋值给数组。 +3. 执行运行时类型测试,例如模式匹配。 + +### 分配细节 + +无论何时,将value类作为另一种类型进行处理时(包括universal trait),此value类实例必须被实例化。例如,value类Meter : + + trait Distance extends Any + case class Meter(val value: Double) extends AnyVal with Distance + +接收Distance类型值的方法需要一个正真的Meter实例。下面的例子中,Meter类真正被实例化。 + + def add(a: Distance, b: Distance): Distance = ... + add(Meter(3.4), Meter(4.3)) + +如果替换add方法的签名: + + def add(a: Meter, b: Meter): Meter = ... + +那么就不必进行分配了。此规则的另一个例子是value类作为类型参数使用。例如:即使是调用identity方法,也必须创建真正的Meter实例。 + + def identity[T](t: T): T = t + identity(Meter(5.0)) + +必须进行分配的另一种情况是:将它赋值给数组。即使这个数组就是value类数组,例如: + + val m = Meter(5.0) + val array = Array[Meter](m) + +数组中包含了真正的Meter 实例,并不只是底层基本类型double。 + +最后是类型测试。例如,模式匹配中的处理以及asInstanceOf方法都要求一个真正的value类实例: + + case class P(val i: Int) extends AnyVal + + val p = new P(3) + p match { // 在这里,新的P实例被创建 + case P(3) => println("Matched 3") + case P(x) => println("Not 3") + } + +## 限制 + +目前Value类有一些限制,部分原因是JVM不提供value类概念的原生支持。value类的完整实现细节及其限制见[SIP-15]。 + +### 限制概要 + +一个value类 ... + +1. ... 必须只有一个public的构造函数。并有且只有一个public的,类型不为value类的val参数。 +2. ... 不能有特殊的类型参数. +3. ... 不能有嵌套或本地类、trait或对象。 +4. ... 不能定义equals或hashCode方法。 +5. ... 必须是一个顶级类,或静态访问对象的一个成员 +6. ... 仅能有def为成员。尤其是,成员不能有惰性val、val或者var 。 +7. ... 不能被其它类继承。 + +### 限制示例 + +本章节列出了许多限制下具体影响, 而在“必要分配”章节已提及的部分则不再敖述。 + +构造函数不允许有多个参数: + + class Complex(val real: Double, val imag: Double) extends AnyVal + +则Scala编译器将生成以下的错误信息: + + Complex.scala:1: error: value class needs to have exactly one public val parameter + (Complex.scala:1: 错误:value类只能有一个public的val参数。) + (译者注:鉴于实际中编译器输出的可能是英文信息,在此提供双语。) + class Complex(val real: Double, val imag: Double) extends AnyVal + ^ + +由于构造函数参数必须是val,而不能是一个按名(by-name)参数: + + NoByName.scala:1: error: `val' parameters may not be call-by-name + (NoByName.scala:1: 错误: `val' 不能为 call-by-name) + class NoByName(val x: => Int) extends AnyVal + ^ + +Scala不允许惰性val作为构造函数参数, 所以value类也不允许。并且不允许多个构造函数。 + + class Secondary(val x: Int) extends AnyVal { + def this(y: Double) = this(y.toInt) + } + + Secondary.scala:2: error: value class may not have secondary constructors + (Secondary.scala:2: 错误:value类不能有第二个构造函数。) + def this(y: Double) = this(y.toInt) + ^ + +value class不能将惰性val或val作为成员,也不能有嵌套类、trait或对象。 + + class NoLazyMember(val evaluate: () => Double) extends AnyVal { + val member: Int = 3 + lazy val x: Double = evaluate() + object NestedObject + class NestedClass + } + + Invalid.scala:2: error: this statement is not allowed in value class: private[this] val member: Int = 3 + (Invalid.scala:2: 错误: value类中不允许此表达式:private [this] val member: Int = 3) + val member: Int = 3 + ^ + Invalid.scala:3: error: this statement is not allowed in value class: lazy private[this] var x: Double = NoLazyMember.this.evaluate.apply() + (Invalid.scala:3: 错误:value类中不允许此表达式: lazy private[this] var x: Double = NoLazyMember.this.evaluate.apply()) + lazy val x: Double = evaluate() + ^ + Invalid.scala:4: error: value class may not have nested module definitions + (Invalid.scala:4: 错误: value类中不能定义嵌套模块) + object NestedObject + ^ + Invalid.scala:5: error: value class may not have nested class definitions + (Invalid.scala:5: 错误:value类中不能定义嵌套类) + class NestedClass + ^ + +注意:value类中也不允许出现本地类、trait或对象,如下: + + class NoLocalTemplates(val x: Int) extends AnyVal { + def aMethod = { + class Local + ... + } + } + +在目前value类实现的限制下,value类不能嵌套: + + class Outer(val inner: Inner) extends AnyVal + class Inner(val value: Int) extends AnyVal + + Nested.scala:1: error: value class may not wrap another user-defined value class + (Nested.scala:1:错误:vlaue类不能包含另一个用户定义的value类) + class Outer(val inner: Inner) extends AnyVal + ^ + +此外,结构类型不能使用value类作为方法的参数或返回值类型。 + + class Value(val x: Int) extends AnyVal + object Usage { + def anyValue(v: { def value: Value }): Value = + v.value + } + + Struct.scala:3: error: Result type in structural refinement may not refer to a user-defined value class + (Struct.scala:3: 错误: 结构细化中的结果类型不适用于用户定义的value类) + def anyValue(v: { def value: Value }): Value = + ^ + +value类不能继承non-universal trait,并且其本身不能被继承: + + trait NotUniversal + class Value(val x: Int) extends AnyVal with notUniversal + class Extend(x: Int) extends Value(x) + + Extend.scala:2: error: illegal inheritance; superclass AnyVal + is not a subclass of the superclass Object + of the mixin trait NotUniversal + (Extend.scala:2: 错误:非法继承:父类AnyVal不是一个父类对象(混入trait NotUniversal)的子类) + class Value(val x: Int) extends AnyVal with NotUniversal + ^ + Extend.scala:3: error: illegal inheritance from final class Value + (Extend.scala:3: 错误: 从Value类(final类)非法继承) + class Extend(x: Int) extends Value(x) + ^ + +第二条错误信息显示:虽然value类没有显式地用final关键字修饰,但依然认为value类是final类。 + +另一个限制是:一个类仅支持单个参数的话,则value类必须是顶级类,或静态访问对象的成员。这是由于嵌套value类需要第二个参数来引用封闭类。所以不允许下述代码: + + class Outer { + class Inner(val x: Int) extends AnyVal + } + + Outer.scala:2: error: value class may not be a member of another class + (Outer.scala:2: 错误:value类不能作为其它类的成员) + class Inner(val x: Int) extends AnyVal + ^ + +但允许下述代码,因为封闭对象是顶级类: + + object Outer { + class Inner(val x: Int) extends AnyVal + } diff --git a/_zh-cn/overviews/parallel-collections/architecture.md b/_zh-cn/overviews/parallel-collections/architecture.md new file mode 100644 index 0000000000..b2aeb118b2 --- /dev/null +++ b/_zh-cn/overviews/parallel-collections/architecture.md @@ -0,0 +1,63 @@ +--- +layout: multipage-overview +title: 并行集合库的架构 + +discourse: false + +partof: parallel-collections +overview-name: Parallel Collections + +num: 5 +language: zh-cn +--- + +像正常的顺序集合库那样,Scala的并行集合库包含了大量的由不同并行集合实现的一致的集合操作。并且像顺序集合库那样,scala的并行集合库通过并行集合“模板”实现了大部分操作,从而防止了代码重复。“模板”只需要定义一次就可以通过不同的并行集合被灵活地继承。 + +这种方法的好处是大大缓解了维护和可扩展性。对于维护--所有的并行集合通过继承一个有单一实现的并行集合,维护变得更容易和更健壮;bug修复传播到类层次结构,而不需要复制实现。出于同样的原因,整个库变得更易于扩展--新的集合类可以简单地继承大部分的操作。 + +### 核心抽象 + +上述的”模板“特性实现的多数并行操作都是根据两个核心抽象--分割器和组合器。 + +#### 分割器 + +Spliter的工作,正如其名,它把一个并行集合分割到了它的元素的非重要分区里面。基本的想法是将集合分割成更小的部分直到他们小到足够在序列上操作。 + + trait Splitter[T] extends Iterator[T] { + def split: Seq[Splitter[T]] + } + +有趣的是,分割器是作为迭代器实现的,这意味着除了分割,他们也被框架用来遍历并行集合(也就是说,他们继承了迭代器的标准方法,如next()和hasNext())。这种“分割迭代器”的独特之处是它的分割方法把自身(迭代器类型的分割器)进一步分割成额外的分割器,这些新的分割器能遍历到整个并行集合的不相交的元素子集。类似于正常的迭代器,分割器在调用分割方法后失效。 + +一般来说,集合是使用分割器(Splitters)分成大小大致相同的子集。在某些情况下,任意大小的分区是必须的,特别是在并行序列上,PreciseSplitter(精确的分割器)是很有用的,它是继承于Splitter和另外一个实现了精确分割的方法--psplit. + +#### 组合器 + +组合器被认为是一个来自于Scala序列集合库的广义构造器。每一个并行集合都提供一个单独的组合器,同样,每一个序列集合也提供一个构造器。 + +而对于序列集合,元素可以被增加到一个构造器中去,并且集合可以通过调用结果方法生成,对于并行集合,组合器有一个叫做combine的方法,它调用其他的组合器进行组合并产生包含两个元素的并集的新组合器。当调用combine方法后,这两个组合器都会变成无效的。 + +trait Combiner[Elem, To] extends Builder[Elem, To] { + def combine(other: Combiner[Elem, To]): Combiner[Elem, To] +} +这两个类型参数Elem,根据上下文分别表示元素类型和结果集合的类型。 + +注意:鉴于两个组合器,c1和c2,在c1=c2为真(意味它们是同一个组合器),调用c1.combine(c2)方法总是什么都不做并且简单的返回接收的组合器c1。 + +### 层级 + +Scala的并行集合吸收了很多来自于Scala的(序列)集合库的设计灵感--事实上,它反映了规则地集合框架的相应特征,如下所示。 + +![parallel-collections-hierarchy.png](/resources/images/parallel-collections-hierarchy.png) + +Scala集合的层次和并行集合库 + +当然我们的目标是尽可能紧密集成并行集合和序列集合,以允许序列集合和并行集合之间的简单替代。 + +为了能够获得一个序列集合或并行集合的引用(这样可以通过par和seq在并行集合和序列集合之间切换),两种集合类型存在一个共同的超型。这是上面所示的“通用”特征的起源,GenTraversable, GenIterable, GenSeq, GenMap and GenSet,不保证按次序或挨个的遍历。相应的序列或并行特征继承于这些。例如,一个ParSeq和Seq都是一个通用GenSeq的子类型,但是他们之间没有相互公认的继承关系。 + +更详细的讨论序列集合和并行集合器之间的层次共享,请参见技术报告。[[1](http://infoscience.epfl.ch/record/165523/files/techrep.pdf)] + +引用 + +1. [On a Generic Parallel Collection Framework, Aleksandar Prokopec, Phil Bawgell, Tiark Rompf, Martin Odersky, June 2011](http://infoscience.epfl.ch/record/165523/files/techrep.pdf) diff --git a/_zh-cn/overviews/parallel-collections/concrete-parallel-collections.md b/_zh-cn/overviews/parallel-collections/concrete-parallel-collections.md new file mode 100644 index 0000000000..793beebcda --- /dev/null +++ b/_zh-cn/overviews/parallel-collections/concrete-parallel-collections.md @@ -0,0 +1,160 @@ +--- +layout: multipage-overview +title: 具体并行集合类 + +discourse: false + +partof: parallel-collections +overview-name: Parallel Collections + +num: 2 +language: zh-cn +--- + +### 并行数组(Parallel Range) + +一个并行数组由线性、连续的数组元素序列。这意味着这些元素可以高效的被访问和修改。正因为如此,遍历元素也是非常高效的。在此意义上并行数组就是一个大小固定的数组。 + + scala> val pa = scala.collection.parallel.mutable.ParArray.tabulate(1000)(x => 2 * x + 1) + pa: scala.collection.parallel.mutable.ParArray[Int] = ParArray(1, 3, 5, 7, 9, 11, 13,... + + scala> pa reduce (_ + _) + res0: Int = 1000000 + + scala> pa map (x => (x - 1) / 2) + res1: scala.collection.parallel.mutable.ParArray[Int] = ParArray(0, 1, 2, 3, 4, 5, 6, 7,... + +在内部,分离一个并行数组[分离器](http://docs.scala-lang.org/overviews/parallel-collections/architecture.html#core_abstractions)相当于使用它们的下标迭代器更新来创建两个分离器。[组合](http://docs.scala-lang.org/overviews/parallel-collections/architecture.html#core_abstractions)稍微负责一点。因为大多数的分离方法(如:flatmap, filter, takeWhile等)我们不能预先知道元素的个数(或者数组的大小),每一次组合本质上来说是一个数组缓冲区的一种变量根据分摊时间来进行加减的操作。不同的处理器进行元素相加操作,对每个独立并列数组进行组合,然后根据其内部连结在再进行组合。在并行数组中的基础数组只有在知道元素的总数之后才能被分配和填充。基于此,变换方法比存取方法要稍微复杂一些。另外,请注意,最终数组分配在JVM上的顺序进行,如果映射操作本身是很便宜,这可以被证明是一个序列瓶颈。 + +通过调用seq方法,并行数组(parallel arrays)被转换为对应的顺序容器(sequential collections) ArraySeq。这种转换是非常高效的,因为新创建的ArraySeq 底层是通过并行数组(parallel arrays)获得的。 + +### 并行向量(Parallel Vector) + +这个并行向量是一个不可变数列,低常数因子数的访问和更新的时间。 + + scala> val pv = scala.collection.parallel.immutable.ParVector.tabulate(1000)(x => x) + pv: scala.collection.parallel.immutable.ParVector[Int] = ParVector(0, 1, 2, 3, 4, 5, 6, 7, 8, 9,... + + scala> pv filter (_ % 2 == 0) + res0: scala.collection.parallel.immutable.ParVector[Int] = ParVector(0, 2, 4, 6, 8, 10, 12, 14, 16, 18,... +不可变向量表现为32叉树,因此[分离器]通过将子树分配到每个分离器(spliter)来分离。[组合(combiners)]存储元素的向量并通过懒惰(lazily)拷贝来组合元素。因此,转换方法相对于并行数组来说可伸缩性较差。一旦串联操作在将来scala的发布版本中成为可变的,组合器将会使得串联和变量器方法更加有效率。 + +并行向量是一个连续[向量]的并行副本,因此两者之间的转换需要一定的时间。 + +### 并行范围(Parallel Range) + +一个ParRange表示的是一个有序的等差整数数列。一个并行范围(parallel range)的创建方法和一个顺序范围的创建类似。 + + scala> 1 to 3 par + res0: scala.collection.parallel.immutable.ParRange = ParRange(1, 2, 3) + + scala> 15 to 5 by -2 par + res1: scala.collection.parallel.immutable.ParRange = ParRange(15, 13, 11, 9, 7, 5) + +正如顺序范围有没有创建者(builders),平行的范围(parallel ranges)有没有组合者(combiners)。映射一个并行范围的元素来产生一个并行向量。顺序范围(sequential ranges)和并行范围(parallel ranges)能够被高效的通过seq和par方法进行转换。 + +### 并行哈希表(Parallel Hash Tables) + +并行哈希表存储在底层数组的元素,并将它们放置在由各自元素的哈希码的位置。并行不变的哈希集(set)([mutable.ParHashSet](http://www.scala-lang.org/api/2.10.0/scala/collection/parallel/mutable/ParHashSet.html))和并行不变的哈希映射([mutable.ParHashMap](http://www.scala-lang.org/api/2.10.0/scala/collection/parallel/mutable/ParHashMap.html)) 是基于哈希表的。 + + scala> val phs = scala.collection.parallel.mutable.ParHashSet(1 until 2000: _*) + phs: scala.collection.parallel.mutable.ParHashSet[Int] = ParHashSet(18, 327, 736, 1045, 773, 1082,... + + scala> phs map (x => x * x) + res0: scala.collection.parallel.mutable.ParHashSet[Int] = ParHashSet(2181529, 2446096, 99225, 2585664,... + +并行哈希表组合器元素排序是依据他们的哈希码前缀在桶(buckets)中进行的。它们通过简单地连接这些桶在一起。一旦最后的哈希表被构造出来(如:组合结果的方法被调用),基本数组分配和从不同的桶元素复制在平行于哈希表的数组不同的相邻节段。 + +连续的哈希映射和散列集合可以被转换成并行的变量使用par方法。并行哈希表内在上要求一个映射的大小在不同块的哈希表元素的数目。这意味着,一个连续的哈希表转换为并行哈希表的第一时间,表被遍历并且size map被创建,因此,第一次调用par方法的时间是和元素个数成线性关系的。进一步修改的哈希表的映射大小保持状态,所以以后的转换使用PAR和序列具有常数的复杂性。使用哈希表的usesizemap方法,映射大小的维护可以开启和关闭。重要的是,在连续的哈希表的修改是在并行哈希表可见,反之亦然。 + +### 并行散列Tries(Parallel Hash Tries) + +并行hash tries是不可变(immutable)hash tries的并行版本,这种结果可以用来高效的维护不可变集合(immutable set)和不可变关联数组(immutable map)。他们都支持类[immutable.ParHashSet](http://www.scala-lang.org/api/2.10.0/scala/collection/parallel/immutable/ParHashSet.html)和[immutable.ParHashMap](http://www.scala-lang.org/api/2.10.0/scala/collection/parallel/immutable/ParHashMap.html)。 + + scala> val phs = scala.collection.parallel.immutable.ParHashSet(1 until 1000: _*) + phs: scala.collection.parallel.immutable.ParHashSet[Int] = ParSet(645, 892, 69, 809, 629, 365, 138, 760, 101, 479,... + + scala> phs map { x => x * x } sum + res0: Int = 332833500 + +类似于平行散列哈希表,parallel hash trie在桶(buckets)里预排序这些元素和根据不同的处理器分配不同的桶(buckets) parallel hash trie的结果,这些构建subtrie是独立的。 + +并行散列试图可以来回转换的,顺序散列试图利用序列和时间常数的方法。 + +### 并行并发tries(Parallel Concurrent Tries) + +[ concurrent.triemap ](http://www.scala-lang.org/api/2.10.0/scala/collection/concurrent/TrieMap.html)是竞争对手的线程安全的地图,而[ mutable.partriemap ](http://www.scala-lang.org/api/2.10.0/scala/collection/parallel/mutable/ParTrieMap.html) 是他的并行副本。如果这个数据结构在遍历的过程中被修改了,大多数竞争对手的数据结构不能确保一致遍历,尝试确保在下一次迭代中更新是可见的。这意味着,你可以在尝试遍历的时候改变这些一致性,如下例子所示输出1到99的平方根。 + + scala> val numbers = scala.collection.parallel.mutable.ParTrieMap((1 until 100) zip (1 until 100): _*) map { case (k, v) => (k.toDouble, v.toDouble) } + numbers: scala.collection.parallel.mutable.ParTrieMap[Double,Double] = ParTrieMap(0.0 -> 0.0, 42.0 -> 42.0, 70.0 -> 70.0, 2.0 -> 2.0,... + + scala> while (numbers.nonEmpty) { + | numbers foreach { case (num, sqrt) => + | val nsqrt = 0.5 * (sqrt + num / sqrt) + | numbers(num) = nsqrt + | if (math.abs(nsqrt - sqrt) < 0.01) { + | println(num, nsqrt) + | numbers.remove(num) + | } + | } + | } + (1.0,1.0) + (2.0,1.4142156862745097) + (7.0,2.64576704419029) + (4.0,2.0000000929222947) + ... + +合成器是引擎盖下triemaps实施——因为这是一个并行数据结构,只有一个组合构建整个变压器的方法调用和所有处理器共享。 + +与所有的并行可变容器(collections),Triemaps和并行partriemaps通过调用序列或PAR方法得到了相同的存储支持,所以修改在一个在其他可见。转换发生在固定的时间。 + +### 性能特征 + +顺序类型(sequence types)的性能特点: + +| | head | tail | apply | update| prepend | append | insert | +| -------- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | +| `ParArray` | C | L | C | C | L | L | L | +| `ParVector` | eC | eC | eC | eC | eC | eC | - | +| `ParRange` | C | C | C | - | - | - | - | + +性能特征集(set)和映射类型: + +| | lookup | add | remove | +| -------- | ---- | ---- | ---- | +| **immutable** | | | | +| `ParHashSet`/`ParHashMap`| eC | eC | eC | +| **mutable** | | | | +| `ParHashSet`/`ParHashMap`| C | C | C | +| `ParTrieMap` | eC | eC | eC | + + +####Key + +上述两个表的条目,说明如下: + +|C |该操作需要的时间常数(快)| +|-----|------------------------| +|eC|该操作需要有效的常数时间,但这可能依赖于一些假设,如一个向量或分配哈希键的最大长度。| +|aC|该操作需要分期常量时间。一些调用的操作可能需要更长的时间,但是如果很多操作都是在固定的时间就可以使用每个操作的平均了。| +|Log|该操作需要collection大小的对数时间比例。| +|L|这个操作是线性的,需要collection大小的时间比例。| +|-|这个操作是不被支持的。| +第一个表处理序列类型--可变和不可变--使用以下操作:| + +| head | 选择序列的第一个元素。| +|-----|------------------------| +|tail|产生一个由除了第一个元素的所有元素组成的新的序列。| +|apply|标引,索引| +|update|对于不可变(immutable sequence)执行函数式更新(functional update),对于可变数据执行带有副作用(side effect)的更新。| +|prepend|在序列的前面添加一个元素。 针对不可变的序列,这将产生一个新的序列,针对可变序列这将修改已经存在的序列。| +|append|在序列结尾添加一个元素。针对不可变的序列,这将产生一个新的序列,针对可变序列这将修改已经存在的序列。| +|insert|在序列中的任意位置插入一个元素。这是可变序列(mutable sequence)唯一支持的操作。| + +第二个表处理可变和不可变集合(set)与关联数组(map)使用以下操作: + +|lookup| 测试一个元素是否包含在集合,或选择一个键所关联的值。| +|-----|------------------------| +|add | 新增一个元素到集合(set)或者键/值匹配映射。| +|remove|从集合(set)或者关键映射中移除元素。| +|min|集合(set)中最小的元素,或者关联数组(map)中的最小的键(key)。| diff --git a/_zh-cn/overviews/parallel-collections/configuration.md b/_zh-cn/overviews/parallel-collections/configuration.md new file mode 100644 index 0000000000..362b06920e --- /dev/null +++ b/_zh-cn/overviews/parallel-collections/configuration.md @@ -0,0 +1,59 @@ +--- +layout: multipage-overview +title: 配置并行集合 + +discourse: false + +partof: parallel-collections +overview-name: Parallel Collections + +num: 7 +language: zh-cn +--- + + +### 任务支持 + +并行集合是以操作调度的方式建模的。每一个并行集合都有一个任务支持对象作为参数,该对象负责处理器任务的调度和负载均衡。 + +任务支持对象内部有一个线程池实例的引用,并且决定着任务细分成更小任务的方式和时机。更多关于这方面的实现细节,请参考技术手册 [[1](http://infoscience.epfl.ch/record/165523/files/techrep.pdf)]。 + +并行集合的任务支持现在已经有一些可用的实现。ForkJoinTaskSupport内部使用fork-join池,并被默认用与JVM1.6以及更高的版本。ThreadPoolTaskSupport 效率更低,是JVM1.5和不支持fork-join池的JVM的回退。ExecutionContextTaskSupport 使用在scala.concurrent中默认的执行上下文实现,并且它重用在scala.concurrent使用的线程池(根据JVM版本,可能是fork join 池或线程池执行器)。执行上下文的任务支持被默认地设置到每个并行集合中,所以并行集合重用相同的fork-join池。 + +以下是一种改变并行集合的任务支持的方法: + + scala> import scala.collection.parallel._ + import scala.collection.parallel._ + + scala> val pc = mutable.ParArray(1, 2, 3) + pc: scala.collection.parallel.mutable.ParArray[Int] = ParArray(1, 2, 3) + + scala> pc.tasksupport = new ForkJoinTaskSupport(new scala.concurrent.forkjoin.ForkJoinPool(2)) + pc.tasksupport: scala.collection.parallel.TaskSupport = scala.collection.parallel.ForkJoinTaskSupport@4a5d484a + + scala> pc map { _ + 1 } + res0: scala.collection.parallel.mutable.ParArray[Int] = ParArray(2, 3, 4) + +以上代码配置并行集合使用parallelism 级别为2的fork-join池。配置并行集合使用线程池执行器: + + scala> pc.tasksupport = new ThreadPoolTaskSupport() + pc.tasksupport: scala.collection.parallel.TaskSupport = scala.collection.parallel.ThreadPoolTaskSupport@1d914a39 + + scala> pc map { _ + 1 } + res1: scala.collection.parallel.mutable.ParArray[Int] = ParArray(2, 3, 4) + +当一个并行集合被序列化,它的任务支持域免于序列化。当对一个并行集合反序列化时,任务支持域被设置为默认值——执行上下文的任务支持。 + +通过继承TaskSupport 特征并实现下列方法,可实现一个典型的任务支持: + + def execute[R, Tp](task: Task[R, Tp]): () => R + + def executeAndWaitResult[R, Tp](task: Task[R, Tp]): R + + def parallelismLevel: Int + +execute方法异步调度任务并且返回等待计算结果的未来状态。executeAndWait 方法功能一样,但只当任务完成时才返回。parallelismLevel 简单地返回任务支持用于调度任务的处理器目标数量。 + +**引用** + +1. [On a Generic Parallel Collection Framework, June 2011](http://infoscience.epfl.ch/record/165523/files/techrep.pdf) diff --git a/_zh-cn/overviews/parallel-collections/conversions.md b/_zh-cn/overviews/parallel-collections/conversions.md new file mode 100644 index 0000000000..788ca55630 --- /dev/null +++ b/_zh-cn/overviews/parallel-collections/conversions.md @@ -0,0 +1,53 @@ +--- +layout: multipage-overview +title: 并行容器的转换 + +discourse: false + +partof: parallel-collections +overview-name: Parallel Collections + +num: 3 +language: zh-cn +--- + +### 顺序容器和并行容器之间的转换 + +每个顺序容器都可以使用par方法转换成它的并行形式。某些顺序容器具有直接的并行副本。对于这些容器,转换是非常高效的(它所需的时间是个常量),因为顺序容器和并行容器具有相同的数据结构上的表现形式(其中一个例外是可变hash map和hash set,在第一次调用par进行转换时,消耗略高,但以后对par的调用将只消耗常数时间)。要注意的是,对于可变容器,如果顺序容器和并行容器共享底层数据结构,那么对顺序容器的修改也会在他的并行副本中可见。 + +| 顺序 |并行 | +|------|-----| +|可变性(mutable)| | +|Array|ParArray| +|HashMap| ParHashMap| +|HashSet| ParHashSet| +|TrieMap| ParTrieMap| +|不可变性(immutable)| | +|Vector | ParVector| +|Range | ParRange| +|HashMap | ParHashMap| +|HashSet | ParHashSet| + +其他容器,如列表(list),队列(queue)及流(stream),从“元素必须逐个访问”这个意义上来讲,天生就是顺序容器。它们可以通过将元素拷贝到类似的并行容器的方式转换成其并行形式。例如,函数式列表可以转换成标准的非可变并行序列,即并行向量。 + +所有的并行容器都可以用 seq 方法转换成其顺序形式。从并行容器转换成顺序容器的操作总是很高效(耗费常数时间)。在可变并行容器上调用seq方法,会生成一个顺序容器,并使用相同的存储空间。对其中一个容器的更新会同时反映到另一个容器上。 + +### 不同类型容器之间的转换 + +通过顺序容器和并行容器之间的正交变换,容器可以在不同容器类型之间相互转换。例如,调用toSeq会将顺序集合转变成顺序序列,而在并行集合上调用toSeq,会将它转换成一个并行序列。基本规律是:如果存在一个X的并行版本,那么toX方法会将容器转换成ParX容器。 + +下面是对所有转换方法的总结: + +|方法 | 返回值类型 | +|----------|-----------| +|toArray | Array | +|toList | List | +|toIndexedSeq | IndexedSeq | +|toStream | Stream | +|toIterator | Iterator | +|toBuffer | Buffer | +|toTraversable | GenTraverable | +|toIterable | ParIterable | +|toSeq | ParSeq | +|toSet | ParSet | +|toMap | ParMap | diff --git a/_zh-cn/overviews/parallel-collections/ctries.md b/_zh-cn/overviews/parallel-collections/ctries.md new file mode 100644 index 0000000000..d3a2dc7d53 --- /dev/null +++ b/_zh-cn/overviews/parallel-collections/ctries.md @@ -0,0 +1,112 @@ +--- +layout: multipage-overview +title: 并发字典树 + +discourse: false + +partof: parallel-collections +overview-name: Parallel Collections + +num: 4 +language: zh-cn +--- + + +对于大多数并发数据结构,如果在遍历中途数据结构发生改变,都不保证遍历的一致性。实际上,大多数可变容器也都是这样。并发字典树允许在遍历时修改Trie自身,从这个意义上来讲是特例。修改只影响后续遍历。顺序并发字典树及其对应的并行字典树都是这样处理。它们之间唯一的区别是前者是顺序遍历,而后者是并行遍历。 + +这是一个很好的性质,可以让一些算法实现起来更加的容易。常见的是迭代处理数据集元素的一些算法。在这样种场景下,不同的元素需要不同次数的迭代以进行处理。 + +下面的例子用于计算一组数据的平方根。每次循环反复更新平方根值。数据的平方根收敛,就把它从映射中删除。 + + case class Entry(num: Double) { + var sqrt = num + } + + val length = 50000 + + // 准备链表 + val entries = (1 until length) map { num => Entry(num.toDouble) } + val results = ParTrieMap() + for (e <- entries) results += ((e.num, e)) + + // 计算平方根 + while (results.nonEmpty) { + for ((num, e) <- results) { + val nsqrt = 0.5 * (e.sqrt + e.num / e.sqrt) + if (math.abs(nsqrt - e.sqrt) < 0.01) { + results.remove(num) + } else e.sqrt = nsqrt + } + } + +注意,在上面的计算平方根的巴比伦算法([3](http://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method))中,某些数据会比别的数据收敛的更快。基于这个因素,我们希望能够尽快把他们从结果中剔除,只遍历那些真正需要耗时处理的元素。 + +另一个例子是广度优先搜索算法,该算法迭代地在末端节点遍历,直到找到通往目标的路径,或遍历完所有周围节点。一个二维地图上的节点定义为Int的元组。map定义为二维布尔值数组,用来表示各个位置是否已经到达。然后,定义2个并行字典树映射,open和closed。其中,映射open保存接着需要被遍历的末端节点。映射closed保存所有已经被遍历过的节点。映射open使用恰当节点来初始化,用以从地图的一角开始搜索,并找到通往地图中心的路径。随后,并行地对映射open中的所有节点迭代遍历,直到没有节点可以遍历。每次一个节点被遍历时,将它从映射open中移除,并放置在映射closed中。一旦执行完成,输出从目标节点到初始节点的路径。 +(译者注:如扫雷,不断判断当前位置(末端节点)上下左右是否为地雷(二维布尔数组),从起始位置逐渐向外扩张。) + + val length = 1000 + + //定义节点类型 + type Node = (Int, Int); + type Parent = (Int, Int); + + //定义节点类型上的操作 + def up(n: Node) = (n._1, n._2 - 1); + def down(n: Node) = (n._1, n._2 + 1); + def left(n: Node) = (n._1 - 1, n._2); + def right(n: Node) = (n._1 + 1, n._2); + + // 创建一个map及一个target + val target = (length / 2, length / 2); + val map = Array.tabulate(length, length)((x, y) => (x % 3) != 0 || (y % 3) != 0 || (x, y) == target) + def onMap(n: Node) = n._1 >= 0 && n._1 < length && n._2 >= 0 && n._2 < length + + //open列表 - 前节点 + // closed 列表 - 已处理的节点 + val open = ParTrieMap[Node, Parent]() + val closed = ParTrieMap[Node, Parent]() + + // 加入一对起始位置 + open((0, 0)) = null + open((length - 1, length - 1)) = null + open((0, length - 1)) = null + open((length - 1, 0)) = null + + // 贪婪广度优先算法路径搜索 + while (open.nonEmpty && !open.contains(target)) { + for ((node, parent) <- open) { + def expand(next: Node) { + if (onMap(next) && map(next._1)(next._2) && !closed.contains(next) && !open.contains(next)) { + open(next) = node + } + } + expand(up(node)) + expand(down(node)) + expand(left(node)) + expand(right(node)) + closed(node) = parent + open.remove(node) + } + } + + // 打印路径 + var pathnode = open(target) + while (closed.contains(pathnode)) { + print(pathnode + "->") + pathnode = closed(pathnode) + } + println() +例如,GitHub上个人生游戏的示例,就是使用Ctries去选择性地模拟人生游戏中当前活跃的机器人([4](https://github.com/axel22/ScalaDays2012-TrieMap))。它还基于Swing实现了模拟的人生游戏的视觉化,以便很直观地观察到调整参数是如何影响执行。 + +并发字典树也支持线性化、无锁、及定时快照操作。这些操作会利用特定时间点上的所有元素来创建新并发 字典树。因此,实际上捕获了特定时间点上的字典树状态。快照操作仅仅为并发字典树生成一个新的根。子序列采用惰性更新的策略,只重建与更新相关的部分,其余部分保持原样。首先,这意味着,由于不需要拷贝元素,自动快照操作资源消耗较少。其次,写时拷贝优化策略只拷贝并发字典树的部分,后续的修改可以横向展开。readOnlySnapshot方法比Snapshot方法效率略高,但它返回的是无法修改的只读的映射。并发字典树也支持线性化,定时清除操作基于快照机制。了解更多关于并发字典树及快照的工作方式,请参阅 ([1](http://infoscience.epfl.ch/record/166908/files/ctries-techreport.pdf)) 和 ([2](http://lampwww.epfl.ch/~prokopec/ctries-snapshot.pdf)). + +并发字典树的迭代器基于快照实现。在迭代器对象被创建之前,会创建一个并发字典树的快照,所以迭代器只在字典树的快照创建时的元素中进行遍历。当然,迭代器使用只读快照。 + +size操作也基于快照。一种直接的实现方式是,size调用仅仅生成一个迭代器(也就是快照),通过遍历来计数。这种方式的效率是和元素数量线性相关的。然而,并发字典树通过缓存其不同部分优化了这个过程,由此size方法的时间复杂度降低到了对数时间。实际上这意味着,调用过一次size方法后,以后对call的调用将只需要最少量的工作。典型例子就是重新计算上次调用size之后被修改了的字典数分支。此外,并行的并发字典树的size计算也是并行的。 + +**引用** + +[缓存感知无锁并发哈希字典树][1](http://infoscience.epfl.ch/record/166908/files/ctries-techreport.pdf) +[具有高效非阻塞快照的并发字典树][2](http://lampwww.epfl.ch/~prokopec/ctries-snapshot.pdf) +[计算平方根的方法][3](http://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method) +[人生游戏模拟程序][4](https://github.com/axel22/ScalaDays2012-TrieMap)(译注:类似大富翁的棋盘游戏) diff --git a/_zh-cn/overviews/parallel-collections/custom-parallel-collections.md b/_zh-cn/overviews/parallel-collections/custom-parallel-collections.md new file mode 100644 index 0000000000..d6abd74588 --- /dev/null +++ b/_zh-cn/overviews/parallel-collections/custom-parallel-collections.md @@ -0,0 +1,193 @@ +--- +layout: multipage-overview +title: 创建自定义并行容器 + +discourse: false + +partof: parallel-collections +overview-name: Parallel Collections + +num: 6 +language: zh-cn +--- + +### 没有组合器的并行容器 + +正如可以定义没有构造器的个性化序列容器一样,我们也可以定义没有组合器的并行容器。没有组合器的结果就是容器的transformer方法(比如,map,flatMap,collect,filter,……)将会默认返回一个标准的容器类型,该类型是在继承树中与并行容器最接近的一个。举个例子,range类没有构造器,因此一个range类的元素映射会生成一个vector。 + +在接下来的例子中,我们定义了一个并行字符串容器。因为字符串在逻辑上是非可变序列,我们让并行字符串继承 immutable.ParSeq[Char]: + + class ParString(val str: String) + extends immutable.ParSeq[Char] { + +接着,我们定义非可变序列必须实现的方法: + + def apply(i: Int) = str.charAt(i) + + def length = str.length + +我们还得定义这个并行容器的序列化副本。这里,我们返回WrappedString类: + + def seq = new collection.immutable.WrappedString(str) + +最后,我们必须为并行字符串容器定义一个splitter。我们给这个splitter起名ParStringSplitter,让它继承一个序列splitter,即,SeqSplitter[Char]: + + def splitter = new ParStringSplitter(str, 0, str.length) + + class ParStringSplitter(private var s: String, private var i: Int, private val ntl: Int) + extends SeqSplitter[Char] { + + final def hasNext = i < ntl + + final def next = { + val r = s.charAt(i) + i += 1 + r + } + +上面的代码中,ntl为字符串的总长,i是当前位置,s是字符串本身。 + +除了next和hasNext方法,并行容器迭代器,或者称它为splitter,还需要序列容器迭代器中的一些其他的方法。首先,他们有一个方法叫做 remaining,它返回这个分割器尚未遍历的元素数量。其次,需要dup方法用于复制当前的分割器。 + + def remaining = ntl - i + + def dup = new ParStringSplitter(s, i, ntl) + +最后,split和psplit方法用于创建splitter,这些splitter 用来遍历当前分割器的元素的子集。split方法,它返回一个分割器的序列,它用来遍历互不相交,互不重叠的分隔器元素子集,其中没有一个是空的。如果当前分割器有1个或更少的元素,然后就返回一个序列的分隔器。psplit方法必须返回和指定sizes参数个数一致的分割器序列。如果sizes参数指定元素数量小于当前分配器,然后一个带有额外的分配器就会附加在分配器的尾部。如果sizes参数比在当前分配器的剩余元素大很多,需要更多的元素,它将为每个分配器添加一个空的分配器。最后,调用split或psplit方法使得当前分配器无效。 + + def split = { + val rem = remaining + if (rem >= 2) psplit(rem / 2, rem - rem / 2) + else Seq(this) + } + + def psplit(sizes: Int*): Seq[ParStringSplitter] = { + val splitted = new ArrayBuffer[ParStringSplitter] + for (sz <- sizes) { + val next = (i + sz) min ntl + splitted += new ParStringSplitter(s, i, next) + i = next + } + if (remaining > 0) splitted += new ParStringSplitter(s, i, ntl) + splitted + } + } + } + +综上所述,split方法是通过psplit来实现的,它常用于并行序列计算中。由于不需要psplit,并行映射、集合、迭代器的实现,通常就更容易些。 + +因此,我们得到了一个并行字符串类。它唯一的缺点是,调用类似filter等转换方法不是生成并行串,而是生成并行向量,这可能是个折中的选择 - filter方法如果生成串而非向量,代价也许是昂贵的。 + +### 带组合子的并行容器 + +假设我们想要从并行字符串中过滤掉某些字符,例如,去除其中的逗号。如上所述,调用filter方法会生成一个并行向量,但是我们需要得到的是一个并行串(因为API中的某些接口可能需要一个连续的字符串来作为参数)。 + +为了避免这种情况的发生,我们需要为并行串容器写一个组合子。同时,我们也将继承ParSeqLike trait,以确保filter的返回类型是更具体的类型 - - ParString而不是ParSeq[Char]。ParSeqLike的第三个参数,用于指定并行容器对应的序列的类型(这点和序列化的 *Like trait 不同,它们只有两个类型参数)。 + + class ParString(val str: String) + extends immutable.ParSeq[Char] + with ParSeqLike[Char, ParString, collection.immutable.WrappedString] + +所有的方法仍然和以前一样,只是我们会增加一个额外的protected方法newCombiner,它在内部被filter方法调用。 + + protected[this] override def newCombiner: Combiner[Char, ParString] = new ParStringCombiner + +接下来我们定义ParStringCombiner类。组合子是builders的子类型,它们引进了名叫combine的方法,该方法接收另一个组合子作为参数,并返回一个新的组合子,该新的组合子包含了当前组合子和参数中的组合子中的所有元素。当前组合子和参数中的组合子在调用combine方法之后将会失效。如果参数中的组合子和当前的组合子是同一个对象,那么combine方法仅仅返回当前的组合子。该方法通常情况下是高效的,最坏情况下时间复杂度为元素个数的对数,因为它在一次并行计算中会被多次调用。 + +我们的ParStringCombiner会在内部维护一个字符串生成器的序列。它通过在序列的最后一个字符串builder中增加一个元素的方式,来实现+=方法。并且通过串联当前和参数中的组合子的串builder列表来实现combine方法。result方法,在并行计算结束后被调用,它会通过将所有字符串生成器添加在一起来产生一个并行串。这样一来,元素只在末端被复制一次,避免了每调一次combine方法就被复制一次。理想情况下,我们想并行化这一进程,并在它们并行时候进行复制(并行数组正在被这样做),但没有办法检测到的字符串的内部表现,这是我们能做的最好的 - 我们不得不忍受这种顺序化的瓶颈。 + + private class ParStringCombiner extends Combiner[Char, ParString] { + var sz = 0 + val chunks = new ArrayBuffer[StringBuilder] += new StringBuilder + var lastc = chunks.last + + def size: Int = sz + + def +=(elem: Char): this.type = { + lastc += elem + sz += 1 + this + } + + def clear = { + chunks.clear + chunks += new StringBuilder + lastc = chunks.last + sz = 0 + } + + def result: ParString = { + val rsb = new StringBuilder + for (sb <- chunks) rsb.append(sb) + new ParString(rsb.toString) + } + + def combine[U <: Char, NewTo >: ParString](other: Combiner[U, NewTo]) = if (other eq this) this else { + val that = other.asInstanceOf[ParStringCombiner] + sz += that.sz + chunks ++= that.chunks + lastc = chunks.last + this + } + } + +### 大体上我如何来实现一个组合子? + +没有现成的秘诀——它的实现依赖于手头上的数据结构,通常在实现上也需要一些创造性。但是,有几种方法经常被采用: + +1. 连接和合并。一些数据结构在这些操作上有高效的实现(经常是对数级的)。如果手头的容器可以由这样的一些数据结构支撑,那么它们的组合子就可以是容器本身。 Finger trees,ropes和各种堆尤其适合使用这种方法。 + +2. 两阶段赋值,是在并行数组和并行哈希表中采用的方法,它假设元素子集可以被高效的划分到连续的排序桶中,这样最终的数据结构就可以并行的构建。第一阶段,不同的处理器独立的占据这些桶,并把这些桶连接在一起。第二阶段,数据结构被分配,不同的处理器使用不相交的桶中的元素并行地占据部分数据结构。必须注意的是,各处理器修改的部分不能有交集,否则,可能会产生微妙的并发错误。正如在前面的篇幅中介绍的,这种方法很容易应用到随机存取序列。 + +3. 一个并发的数据结构。尽管后两种方法实际上不需要数据结构本身有同步原语,它们假定数据结构能够被不修改相同内存的不同处理器,以并发的方式建立。存在大量的并发数据结构,它们可以被多个处理器安全的修改——例如,并发skip list,并发哈希表,split-ordered list,并发 avl树等等。需要注意的是,并发的数据结构应该提供水平扩展的插入方法。对于并发并行容器,组合器可以是容器本身,并且,完成一个并行操作的所有的处理器会共享一个单独的组合器实例。 + +### 使用容器框架整合 + +ParString类还没有完成。虽然我们已经实现了一个自定义的组合子,它将会被类似filter,partition,takeWhile,或者span等方式使用,但是大部分transformer方法都需要一个隐式的CanBuildFrom出现(Scala collections guide有详细的解释)。为了让ParString可能,并且完全的整合到容器框架中,我们需要为其掺入额外的一个叫做GenericParTemplate的trait,并且定义ParString的伴生对象。 + + class ParString(val str: String) + extends immutable.ParSeq[Char] + with GenericParTemplate[Char, ParString] + with ParSeqLike[Char, ParString, collection.immutable.WrappedString] { + + def companion = ParString +在这个伴生对象内部,我们隐式定义了CanBuildFrom。 + + object ParString { + implicit def canBuildFrom: CanCombineFrom[ParString, Char, ParString] = + new CanCombinerFrom[ParString, Char, ParString] { + def apply(from: ParString) = newCombiner + def apply() = newCombiner + } + + def newBuilder: Combiner[Char, ParString] = newCombiner + + def newCombiner: Combiner[Char, ParString] = new ParStringCombiner + + def apply(elems: Char*): ParString = { + val cb = newCombiner + cb ++= elems + cb.result + } + } + +### 进一步定制——并发和其他容器 + +实现一个并发容器(与并行容器不同,并发容器是像collection.concurrent.TrieMap一样可以被并发修改的)并不总是简单明了的。尤其是组合器,经常需要仔细想想。到目前为止,在大多数描述的并行容器中,组合器都使用两步评估。第一步元素被不同的处理器加入到组合器中,组合器被合并在一起。第二步,在所有元素完成处理后,结果容器就被创建。 + +组合器的另一种方式是把结果容器作为元素来构建。前提是:容器是线程安全的——组合器必须允许并发元素插入。这样的话,一个组合器就可以被所有处理器共享。 + +为了使一个并发容器并行化,它的组合器必须重写canBeShared方法以返回真。这会保证当一个并行操作被调用,只有一个组合器被创建。然后,+=方法必须是线程安全的。最后,如果当前的组合器和参数组合器是相同的,combine方法仍然返回当前的组合器,要不然会自动抛出异常。 + +为了获得更好的负载均衡,Splitter被分割成更小的splitter。默认情况下,remaining方法返回的信息被用来决定何时停止分割splitter。对于一些容器而言,调用remaining方法是有花销的,一些其他的方法应该被使用来决定何时分割splitter。在这种情况下,需要重写splitter的shouldSplitFurther方法。 + +如果剩余元素的数量比容器大小除以8倍并行级别更大,默认的实现将拆分splitter。 + + def shouldSplitFurther[S](coll: ParIterable[S], parallelismLevel: Int) = + remaining > thresholdFromSize(coll.size, parallelismLevel) + +同样的,一个splitter可以持有一个计数器,来计算splitter被分割的次数。并且,如果split次数超过3+log(并行级别),shouldSplitFurther将直接返回true。这避免了必须去调用remaining方法。 + +此外,对于一个指定的容器如果调用remaining方法开销不低(比如,他需要评估容器中元素的数量),那么在splitter中的方法isRemainingCheap就应该被重写并返回false。 + +最后,若果在splitter中的remaining方法实现起来极其麻烦,你可以重写容器中的isStrictSplitterCollection方法,并返回false。虽然这些容器将不能够执行一些严格依赖splitter的方法,比如,在remaining方法中返回一个正确的值。重点是,这并不影响 for-comprehension 中使用的方法。 diff --git a/_zh-cn/overviews/parallel-collections/overview.md b/_zh-cn/overviews/parallel-collections/overview.md new file mode 100644 index 0000000000..160432d0c3 --- /dev/null +++ b/_zh-cn/overviews/parallel-collections/overview.md @@ -0,0 +1,176 @@ +--- +layout: multipage-overview +title: 概述 + +discourse: false + +partof: parallel-collections +overview-name: Parallel Collections + +num: 1 +language: zh-cn +--- + +**Aleksandar Prokopec, Heather Miller** + +## 动机 + +近年来,处理器厂家在单核向多核架构迁移的过程中,学术界和工业界都认为当红的并行编程仍是一个艰巨的挑战。 + +在Scala标准库中包含的并行容器通过免去并行化的底层细节,以方便用户并行编程,同时为他们提供一个熟悉而简单的高层抽象。希望隐藏在抽象容器之后的隐式并行性将带来可靠的并行执行,并进一步靠近主流开发者的工作流程。 + +原理其实很简单-容器是抽象编程中被广泛熟识和经常使用的类,并且考虑到他们的规则性,容器能够使程序高效且透明的并行化。通过使用户能够在并行操作有序容器的同时改变容器序列,Scala的并行容器在使代码能够更容易的并行化方面做了很大改进。 + +下面是个序列的例子,这里我们在某个大的容器上执行一个一元运算: + + val list = (1 to 10000).toList + list.map(_ + 42) +为了并行的执行同样的操作,我们只需要简单的调用序列容器(列表)的par方法。这样,我们就可以像通常使用序列容器的方式那样来使用并行容器。上面的例子可以通过执行下面的来并行化: + + list.par.map(_ + 42) +Scala的并行容器库设计创意般的同Scala的(序列)容器库(从2.8引入)密切的整合在一起。在Scala(序列)容器库中,它提供了大量重要的数据结构对应的东西,包括: + +- ParArray +- ParVector +- mutable.ParHashMap +- mutable.ParHashSet +- immutable.ParHashMap +- immutable.ParHashSet +- ParRange +- ParTrieMap (collection.concurrent.TrieMaps are new in 2.10) + +在通常的架构之外,Scala的并行容器库也同序列容器库(sequential collections)一样具有可扩展性。这就是说,像通常的序列容器那样,用户可以整合他们自己的容器类型,并且自动继承所有的可用在别的并行容器(在标准库里的)的预定义(并行)操作。 + +### 下边是一些例子 + +为了说明并行容器的通用性和实用性,我们提供几个简单的示例用法,所有这些都被透明的并行执行。 + +提示:随后某些例子操作小的容器,实际中不推荐这样做。他们提供的示例仅为演示之用。一般来说,当容器的尺寸比较巨大(通常为成千上万个元素时)时,加速才会比较明显。(关于并行容器的尺寸和性能更多的信息,请参见) + +#### map + +使用parallel map来把一个字符串容器变为全大写字母: + + scala> val lastNames = List("Smith","Jones","Frankenstein","Bach","Jackson","Rodin").par + astNames: scala.collection.parallel.immutable.ParSeq[String] = ParVector(Smith, Jones, Frankenstein, Bach, Jackson, Rodin) + + scala> lastNames.map(_.toUpperCase) + res0: scala.collection.parallel.immutable.ParSeq[String] = ParVector(SMITH, JONES, FRANKENSTEIN, BACH, JACKSON, RODIN) + +#### fold + +通过fold计算一个ParArray中所有数的累加值: + + scala> val parArray = (1 to 10000).toArray.par + parArray: scala.collection.parallel.mutable.ParArray[Int] = ParArray(1, 2, 3, ... + + scala> parArray.fold(0)(_ + _) + res0: Int = 50005000 + +#### filter + +使用并行过滤器来选择按字母顺序排在“K”之后的姓名。(译者注:这个例子有点问题,应该是排在“J”之后的) + + scala> val lastNames = List("Smith","Jones","Frankenstein","Bach","Jackson","Rodin").par + astNames: scala.collection.parallel.immutable.ParSeq[String] = ParVector(Smith, Jones, Frankenstein, Bach, Jackson, Rodin) + + scala> lastNames.filter(_.head >= 'J') + res0: scala.collection.parallel.immutable.ParSeq[String] = ParVector(Smith, Jones, Jackson, Rodin) + +## 创建一个并行容器 + +并行容器(parallel collections)同顺序容器(sequential collections)完全一样的被使用,唯一的不同是要怎样去获得一个并行容器。 + +通常,我们有两种方法来创建一个并行容器: + +第一种,通过使用new关键字和一个适当的import语句: + + import scala.collection.parallel.immutable.ParVector + val pv = new ParVector[Int] + +第二种,通过从一个顺序容器转换得来: + + val pv = Vector(1,2,3,4,5,6,7,8,9).par + +这里需要着重强调的是这些转换方法:通过调用顺序容器(sequential collections)的par方法,顺序容器(sequential collections)可以被转换为并行容器;通过调用并行容器的seq方法,并行容器可以被转换为顺序容器。 + +注意:那些天生就有序的容器(意思是元素必须一个接一个的访问),像lists,queues和streams,通过拷贝元素到类似的并行容器中被转换为它们的并行对应物。例如List--被转换为一个标准的不可变的并行序列中,就是ParVector。当然,其他容器类型不需要这些拷贝的开销,比如:Array,Vector,HashMap等等。 + +关于并行容器的转换的更多信息请参见 [conversions](http://docs.scala-lang.org/overviews/parallel-collections/conversions.html) 和 [concrete parallel collections classes](http://docs.scala-lang.org/overviews/parallel-collections/concrete-parallel-collections.html)章节 + +## 语义(semantic) + +尽管并行容器的抽象概念很像通常的顺序容器,重要的是要注意它的语义的不同,特别是关于副作用(side-effects)和无关操作(non-associative operations)。 + +为了看看这是怎样的情况,首先,我们设想操作是如何被并行的执行。从概念上讲,Scala的并行容器框架在并行容器上通过递归的“分解"给定的容器来并行化一个操作,在并行中,容器的每个部分应用一个操作,然后“重组”所有这些并行执行的结果。 + +这些并发和并行容器的“乱序”语义导致以下两个影响: + +1. 副作用操作可能导致结果的不确定性 +2. 非关联(non-associative)操作导致不确定性 + +### 副作用操作 + +为了保持确定性,考虑到并行容器框架的并发执行的语义,一般应该避免执行那些在容器上引起副作用的操作。一个简单的例子就是使用访问器方法,像在 foreach 之外来增加一个 var 定义然后传递给foreach。 + + scala> var sum = 0 + sum: Int = 0 + + scala> val list = (1 to 1000).toList.par + list: scala.collection.parallel.immutable.ParSeq[Int] = ParVector(1, 2, 3,… + + scala> list.foreach(sum += _); sum + res01: Int = 467766 + + scala> var sum = 0 + sum: Int = 0 + + scala> list.foreach(sum += _); sum + res02: Int = 457073 + + scala> var sum = 0 + sum: Int = 0 + + scala> list.foreach(sum += _); sum + res03: Int = 468520 + +从上述例子我们可以看到虽然每次 sum 都被初始化为0,在list的foreach每次调用之后,sum都得到不同的值。这个不确定的源头就是数据竞争 -- 同时读/写同一个可变变量(mutable variable)。 + +在上面这个例子中,可能同时有两个线程在读取同一个sum的值,某些操作花了些时间后,它们又试图写一个新的值到sum中,可能的结果就是某个有用的值被覆盖了(因此丢失了),如下表所示: + + 线程A: 读取sum的值, sum = 0 sum的值: 0 + 线程B: 读取sum的值, sum = 0 sum的值: 0 + 线程A: sum 加上760, 写 sum = 760 sum的值: 760 + 线程B: sum 加上12, 写 sum = 12 sum的值: 12 + +上面的示例演示了一个场景:两个线程读相同的值:0。在这种情况下,线程A读0并且累计它的元素:0+760,线程B,累计0和它的元素:0+12。在各自计算了和之后,它们各自把计算结果写入到sum中。从线程A到线程B,线程A写入后,马上被线程B写入的值覆盖了,值760就完全被覆盖了(因此丢失了)。 + +### 非关联(non-associative)操作 + +对于”乱序“语义,为了避免不确定性,也必须注意只执行相关的操作。这就是说,给定一个并行容器:pcoll,我们应该确保什么时候调用一个pcoll的高阶函数,例如:pcoll.reduce(func),被应用到pcoll元素的函数顺序是任意的。一个简单但明显不可结合(non-associative)例子是减法运算: + + scala> val list = (1 to 1000).toList.par + list: scala.collection.parallel.immutable.ParSeq[Int] = ParVector(1, 2, 3,… + + scala> list.reduce(_-_) + res01: Int = -228888 + + scala> list.reduce(_-_) + res02: Int = -61000 + + scala> list.reduce(_-_) + res03: Int = -331818 + +在上面这个例子中,我们对 ParVector[Int]调用 reduce 函数,并给他 _-_ 参数(简单的两个非命名元素),从第二个减去第一个。因为并行容器(parallel collections)框架创建线程来在容器的不同部分执行reduce(-),而由于执行顺序的不确定性,两次应用reduce(-)在并行容器上很可能会得到不同的结果。 + +注意:通常人们认为,像不可结合(non-associative)作,不可交换(non-commutative)操作传递给并行容器的高阶函数同样导致非确定的行为。但和不可结合是不一样的,一个简单的例子是字符串联合(concatenation),就是一个可结合但不可交换的操作: + + scala> val strings = List("abc","def","ghi","jk","lmnop","qrs","tuv","wx","yz").par + strings: scala.collection.parallel.immutable.ParSeq[java.lang.String] = ParVector(abc, def, ghi, jk, lmnop, qrs, tuv, wx, yz) + + scala> val alphabet = strings.reduce(_++_) + alphabet: java.lang.String = abcdefghijklmnopqrstuvwxyz + +并行容器的“乱序”语义仅仅意味着操作被执行是没有顺序的(从时间意义上说,就是非顺序的),并不意味着结果的重“组合”也是乱序的(从空间意义上)。恰恰相反,结果一般总是按序组合的 -- 一个并行容器被分成A,B,C三部分,按照这个顺序,将重新再次按照A,B,C的顺序组合。而不是某种其他随意的顺序如B,C,A。 + +关于并行容器在不同的并行容器类型上怎样进行分解和组合操作的更多信息,请参见。 diff --git a/_zh-cn/overviews/parallel-collections/performance.md b/_zh-cn/overviews/parallel-collections/performance.md new file mode 100644 index 0000000000..265842facb --- /dev/null +++ b/_zh-cn/overviews/parallel-collections/performance.md @@ -0,0 +1,182 @@ +--- +layout: multipage-overview +title: 测量性能 + +discourse: false + +partof: parallel-collections +overview-name: Parallel Collections + +num: 8 +language: zh-cn +--- + +### 在JVM上的性能 + +对JVM性能模型的评论常常令人费解,其结论也往往不易理解。由于种种原因,代码也可能不像预期的那样高性能、可扩展。在这里,我们提供了一些示例。 + +其中一个原因是JVM应用程序的编译过程不同于静态编译语言(见[[2](http://www.ibm.com/developerworks/library/j-jtp12214/)])。Java和Scala的编译器将源代码转换为JVM的字节码,做了非常少的优化。大多数现代JVM,运行时,会把字节码转化成相应机器架构的机器代码。这个过程被称为即时编译。由于追求运行速度,所以实时编译的代码优化程度较低。为了避免重新编译,所谓的HotSpot编译器只优化了部分经常被运行的代码。这对于基准程序作者来说,这意味着程序每次运行时的性能都可能不同。在同一个JVM实例中多次执行一段相同的代码(比如一个方法)可能会得到非常不同的性能结果,这取决于这段代码在运行过程中是否被优化。另外,在测量某些代码的执行时间时其中可能包含JIT编译器对代码进行优化的时间,因此可能得到不一致的结果。 + +另一个在JVM上隐藏执行的是内存自动管理。每隔一段时间,程序的运行就被阻塞并且启动垃圾收集器。如果被进行基准测试的程序分配了任何堆内存(大部分JVM程序都会分配),垃圾收集器将会工作,因此可能会影响测量结果。为了缓冲垃圾收集的影响,被测量的程序应该运行多次以便触发多次垃圾回收。 + +性能恶化的常见原因之一是将原始类型作为参数传递给泛型方法时发生的隐式装箱和拆箱。在运行时,原始类型被转换为封装对象,这样它们就可以作为参数传给有泛型类型参数的方法。这会导致额外的空间分配并且运行速度会更慢,也会在堆中产生额外的垃圾。 + +就并行性能而言,一个常见的问题是存储冲突,因为程序员针对对象的内存分配没有做明确的控制。事实上,由于GC的影响,冲突可以发生在应用程序生命期的最后,在对象被移出内存后。在编写基准测试时这种影响需要被考虑到。 + +### 微基准测试的例子 + +有几种方法可以在测试中避免上述影响。首先,目标微基准测试必须被执行足够多次来确保实时编译器将程序编译为机器码并被优化过。这就是所谓的预热阶段。 + +微基准测试本身需要被运行在单独的JVM实例中,以便减少在程序不同部分或不相关的实时编译过程中针对对象分配的垃圾收集所带来的干扰。 + +微基准测试应该跑在会做更多积极优化的服务器版本的HotSpot JVM上。 + +最后,为了减少在基准测试中间发生垃圾回收的可能性,理想的垃圾回收周期应该发生在基准测试之前,并尽可能的推迟下一个垃圾回收周期。 + +scala.testing.Benchmark trait 是在Scala标准库中被预先定义的,并按前面提到的方式设计。下面是一个用于测试并行算法中映射操作的例子: + + import collection.parallel.mutable.ParTrieMap + import collection.parallel.ForkJoinTaskSupport + + object Map extends testing.Benchmark { + val length = sys.props("length").toInt + val par = sys.props("par").toInt + val partrie = ParTrieMap((0 until length) zip (0 until length): _*) + + partrie.tasksupport = new ForkJoinTaskSupport(new scala.concurrent.forkjoin.ForkJoinPool(par)) + + def run = { + partrie map { + kv => kv + } + } + } + +run方法包含了基准测试代码,重复运行时测量执行时间。上面的Map对象扩展了scala.testing.Benchmark trait,同时,参数par为系统的并行度,length为trie中元素数量的长度。 + +在编译上面的程序之后,可以这样运行: + + java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=1 -Dlength=300000 Map 10 + +server参数指定需要使用server类型的虚拟机。cp参数指定了类文件的路径,包含当前文件夹的类文件以及以及scala类库的jar包。参数-Dpar和-Dlength分别对应并行度和元素数量。最后,10意味着基准测试需要在同一个JVM中运行的次数。 + +在i7四核超线程处理器上将par的值设置为1、2、4、8并获得对应的执行时间。 + + Map$ 126 57 56 57 54 54 54 53 53 53 + Map$ 90 99 28 28 26 26 26 26 26 26 + Map$ 201 17 17 16 15 15 16 14 18 15 + Map$ 182 12 13 17 16 14 14 12 12 12 + +我们从上面的结果可以看到运行时间在最初的几次运行中是较高的,但是在代码被优化后时间就缩短了。另外,我们可以看到在这个例子中超线程带来的好处并不明显,从4线程到8线程的结果说明性能只有小幅提升。 + +### 多大的容器才应该使用并发? + +这是一个经常被问到的问题。答案是有些复杂的。 + +collection的大小所对应的实际并发消耗取决于很多因素。部分原因如下: + +- 硬件架构。不同的CPU类型具有不同的性能和可扩展能力。取决于硬件是否为多核或者是否有多个通过主板通信的处理器。 +- JVM的供应商及版本。在运行时不同的虚拟机应用不同的代码优化。它们的内存管理实现不同,同步技术也不同。有些不支持ForkJoinPool,转而使用ThreadPoolExecutor,这会导致更多的开销。 +- 元素负载。用于并行操作的函数或断言决定了每个元素的负载有多大。负载越小,并发运行时用来提高运行速度的元素数量就越高。 +- 特定的容器。例如:ParArray和ParTrieMap的分离器在遍历容器时有不同的速度,这意味着在遍历过程中要有更多的per-element work。 +- 特定的操作。例如:ParVector在转换方法中(比如filter)要比在存取方法中(比如foreach)慢得多。 +- 副作用。当同时修改内存区域或者在foreach、map等语句中使用同步时,就会发生竞争。 +- 内存管理。当分配大量对象时垃圾回收机制就会被触发。GC循环会消耗多长时间取决于新对象的引用如何进行传递。 + +即使单独的来看,对上面的问题进行推断并给出关于容器应有大小的明确答案也是不容易的。为了粗略的说明容器的应有大小,我们给出了一个无副作用的在i7四核处理器(没有使用超线程)和JDK7上运行的并行矢量减(在这个例子中进行的是求和)处理性能的例子: + + import collection.parallel.immutable.ParVector + + object Reduce extends testing.Benchmark { + val length = sys.props("length").toInt + val par = sys.props("par").toInt + val parvector = ParVector((0 until length): _*) + + parvector.tasksupport = new collection.parallel.ForkJoinTaskSupport(new scala.concurrent.forkjoin.ForkJoinPool(par)) + + def run = { + parvector reduce { + (a, b) => a + b + } + } + } + + object ReduceSeq extends testing.Benchmark { + val length = sys.props("length").toInt + val vector = collection.immutable.Vector((0 until length): _*) + + def run = { + vector reduce { + (a, b) => a + b + } + } + + } +首先我们设定在元素数量为250000的情况下运行基准测试,在线程数设置为1、2、4的情况下得到了如下结果: + + java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=1 -Dlength=250000 Reduce 10 10 + Reduce$ 54 24 18 18 18 19 19 18 19 19 + java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=2 -Dlength=250000 Reduce 10 10 + Reduce$ 60 19 17 13 13 13 13 14 12 13 + java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=4 -Dlength=250000 Reduce 10 10 + Reduce$ 62 17 15 14 13 11 11 11 11 9 +然后我们将元素数量降低到120000,使用4个线程来比较序列矢量减运行的时间: + + java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=4 -Dlength=120000 Reduce 10 10 + Reduce$ 54 10 8 8 8 7 8 7 6 5 + java -server -cp .:../../build/pack/lib/scala-library.jar -Dlength=120000 ReduceSeq 10 10 + ReduceSeq$ 31 7 8 8 7 7 7 8 7 8 +在这个例子中,元素数量为120000时看起来正处于阈值附近。 + +在另一个例子中,我们使用mutable.ParHashMap和map方法(一个转换方法),并在同样的环境中运行下面的测试程序: + + import collection.parallel.mutable.ParHashMap + + object Map extends testing.Benchmark { + val length = sys.props("length").toInt + val par = sys.props("par").toInt + val phm = ParHashMap((0 until length) zip (0 until length): _*) + + phm.tasksupport = new collection.parallel.ForkJoinTaskSupport(new scala.concurrent.forkjoin.ForkJoinPool(par)) + + def run = { + phm map { + kv => kv + } + } + } + + object MapSeq extends testing.Benchmark { + val length = sys.props("length").toInt + val hm = collection.mutable.HashMap((0 until length) zip (0 until length): _*) + + def run = { + hm map { + kv => kv + } + } + } +在元素数量为120000、线程数量从1增加至4的时候,我们得到了如下结果: + + java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=1 -Dlength=120000 Map 10 10 + Map$ 187 108 97 96 96 95 95 95 96 95 + java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=2 -Dlength=120000 Map 10 10 + Map$ 138 68 57 56 57 56 56 55 54 55 + java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=4 -Dlength=120000 Map 10 10 + Map$ 124 54 42 40 38 41 40 40 39 39 + +现在,如果我们将元素数量降低到15000来跟序列化哈希映射做比较: + + java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=1 -Dlength=15000 Map 10 10 + Map$ 41 13 10 10 10 9 9 9 10 9 + java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=2 -Dlength=15000 Map 10 10 + Map$ 48 15 9 8 7 7 6 7 8 6 + java -server -cp .:../../build/pack/lib/scala-library.jar -Dlength=15000 MapSeq 10 10 + MapSeq$ 39 9 9 9 8 9 9 9 9 9 + +对这个容器和操作来说,当元素数量大于15000的时候采用并发是有意义的(通常情况下,对于数组和向量来说使用更少的元素来并行处理hashmap和hashset是可行的但不是必须的)。 + +**引用** + +1. [Anatomy of a flawed microbenchmark,Brian Goetz](http://www.ibm.com/developerworks/java/library/j-jtp02225/index.html) +2. [Dynamic compilation and performance measurement, Brian Goetz](http://www.ibm.com/developerworks/library/j-jtp12214/) diff --git a/zh-cn/overviews/thanks.md b/_zh-cn/thanks.md similarity index 98% rename from zh-cn/overviews/thanks.md rename to _zh-cn/thanks.md index a214af96dc..16c526c2ad 100644 --- a/zh-cn/overviews/thanks.md +++ b/_zh-cn/thanks.md @@ -1,5 +1,5 @@ --- -layout: guides-thanks +layout: inner-page-no-masthead language: zh-cn title: 致谢名单 --- diff --git a/_zh-tw/tutorials/scala-for-java-programmers.md b/_zh-tw/tutorials/scala-for-java-programmers.md new file mode 100755 index 0000000000..15d95b0b27 --- /dev/null +++ b/_zh-tw/tutorials/scala-for-java-programmers.md @@ -0,0 +1,387 @@ +--- +layout: singlepage-overview +title: 給 Java 程式設計師的 Scala 入門教學 + +partof: scala-for-java-programmers + +discourse: false +language: zh-tw +--- + +Michel Schinz 與 Philipp Haller 著 +Chikei Lee 譯 + +## 介紹 + +此教學將對 Scala 語言以及編譯器做一個簡易介紹。設定的讀者為具有程設經驗且想要看 Scala 功能概要的人。內文假設讀者有著基本、特別是 Java 上的物件導向程設知識。 + +## 第一個例子 + +這邊用標準的 *Hello world* 程式作為第一個例子。雖然它很無趣,可是這讓我們在僅用少量語言特性下演示 Scala 工具。程式如下: + + object HelloWorld { + def main(args: Array[String]) { + println("Hello, world!") + } + } + +Java 程式員應該對這個程式結構感到熟悉:有著一個 `main` 函式,該函式接受一個字串陣列引數,也就是命令列引數;函式內容為呼叫已定義好的函式 `println` 並用 Hello world 字串當引數。 `main` 函式沒有回傳值 (它是程序函式)。因此並不需要宣告回傳型別。 + +Java 程式員不太熟悉的是包著 `main` 函式的 `object` 宣告。這種宣告引入我們一般稱之 *Singleton* 的東西,也就是只有一個實體的類別。所以上面的宣告同時宣告了一個 `HelloWorld` 類別跟一個這類別的實體,也叫做 `HelloWorld`。該實體會在第一次被使用到的時候即時產生。 + +眼尖的讀者可能已經注意到這邊 `main` 函式的宣告沒有帶著 `static`。這是因為 Scala 沒有靜態成員 (函式或資料欄)。Scala 程式員將這成員宣告在單實例物件中,而不是定義靜態成員。 + +### 編譯這例子 + +我們用 Scala 編譯器 `scalac`來編譯這個例子。`scalac` 就像大多數編譯器一樣,它接受原碼檔當引數,並接受額外的選項,然後產生一個或多個物件檔。它產出的物件檔為標準 Java class 檔案。 + +如果我們將上面的程式存成 `HelloWorld.scala` 檔,編譯指令為( `>` 是提示字元,不用打): + + > scalac HelloWorld.scala + +這會在當前目錄產生一些 class 檔案。其中一個會叫做 `HelloWorld.class`,裡面包含著可被 `scala` 直接執行的類別。 + +### 執行範例 + +一旦編譯過後,Scala 程式可以用 `scala` 指令執行。其使用方式非常像執行 Java 程式的 `java` 指令,並且接受同樣選項。上面的範例可以用以下指令來執行並得到我們預期的輸出: + + > scala -classpath . HelloWorld + + Hello, world! + +## 與 Java 互動 + +Scala 的優點之一是它非常容易跟 Java 程式碼溝通。預設匯入所有 `java.lang` 底下之類別,其他類別則需要明確匯入。 + +讓我們看個展示這點的範例。取得當下日期並根據某個特定國家調整成該國格式,如法國。 + +Java 的標準函式庫定義了一些有用的工具類別,如 `Date` 跟 `DateFormat`。因為 Scala 可以無縫的跟 Jav a互動,這邊不需要以 Scala 實作同樣類別-我們只需要匯入對應的Java套件: + + import java.util.{Date, Locale} + import java.text.DateFormat + import java.text.DateFormat._ + + object FrenchDate { + def main(args: Array[String]) { + val now = new Date + val df = getDateInstance(LONG, Locale.FRANCE) + println(df format now) + } + } + +Scala 的匯入陳述式跟 Java 非常像,但更為強大。如第一行,同一個 package 下的多個類別可以用大括號括起來一起導入。另外一個差別是,當要匯入套件或類別下所有名稱時,用下標 (`_`) 而不是星號 (`*`)。這是因為星號在 Scala 是一個合法的識別符號 (如函式名稱)。 + +所以第三行的陳述式導入所有 `DateFormat` 類別的成員。這讓靜態函式 `getDateInstance` 跟靜態資料欄 `LONG` 可直接被使用。 + +在 `main` 函式中我們先創造一個 Java 的 `Date` 類別實體,該實體預設擁有現在的日期。接下來用 `getDateInstance` 函式定義日期格式。最後根據地區化的 `DateFormat` 實體對現在日期設定格式並印出。最後一行展現了一個 Scala 有趣特點。只需要一個引數的函式可以用中綴語法呼叫。就是說,這個表示式 + + df format now + +是比較不詳細版本的這個表示式 + + df.format(now) + +這點也許看起來只是語法上的小細節,但是它有著重要的後果,其中一個將會在下一節做介紹。 + +最後值得一提的是,Scala 可以直接繼承 Java 類別跟實作 Java 介面。 + +## 萬物皆物件 + +Scala 是一個純粹的物件導向語言,這句話的意思是說,*所有東西*都是物件,包括數字、函式。因為 Java 將基本型別 (如 `boolean` 與 `int` ) 跟參照型別分開,而且沒有辦法像操作變數一樣操作函式,從這角度來看 Scala 跟 Java 是不同的。 + +### 數字是物件 + +因為數字是物件,它們也有函式。事實上,一個像底下的算數表示式: + + 1 + 2 * 3 / x + +只有使用函式呼叫,因為像前一節一樣,該式等價於 + + (1).+(((2).*(3))./(x)) + +這也表示著 `+`、`*` 之類的在 Scala 裡是合法的識別符號。 + +因為Scala的詞法分析器對於符號採用最長匹配,在第二版的表示式當中,那些括號是必要的。也就是說分析器會把這個表示式: + + 1.+(2) + +拆成 `1.`、`+`、`2` 這三個符號。會這樣拆分是因為 `1.` 既是合法匹配同時又比 `1` 長。 `1.` 會被解釋成字面常數 `1.0`,使得它被視為 `Double` 而不是 `Int`。把表示式寫成: + + (1).+(2) + +可以避免 `1` 被解釋成 `Double`。 + +### 函式是物件 + +可能令 Java 程式員更為驚訝的會是,Scala 中函式也是物件。因此,將函式當做引數傳遞、把它們存入變數、從其他函式返回函式都是可能的。能夠像操作變數一樣的操作函式這點是*函數編程*這一非常有趣的程設典範的基石之一。 + +為何把函式當做變數一樣的操作會很有用呢,讓我們考慮一個定時函式,功能是每秒執行一些動作。我們要怎麼將這動作傳給它?最直接的便是將這動作視為函式傳入。應該有不少程式員對這種簡單傳遞函式的行為很熟悉:通常在使用者介面相關的程式上,用以註冊一些當事件發生時被呼叫的回呼函式。 + +在接下來的程式中,定時函式叫做 `oncePerSecond` ,它接受一個回呼函式做參數。該函式的型別被寫作 `() => Unit` ,這個型別便是所有無引數且無返回值函式的型別( `Unit` 這個型別就像是 C/C++ 的 `void` )。此程式的主函式只是呼叫定時函式並帶入回呼函式,回呼函式輸出一句話到終端上。也就是說這個程式會不斷的每秒輸出一次 "time flies like an arrow"。 + + object Timer { + def oncePerSecond(callback: () => Unit) { + while (true) { callback(); Thread sleep 1000 } + } + def timeFlies() { + println("time flies like an arrow...") + } + def main(args: Array[String]) { + oncePerSecond(timeFlies) + } + } + +值得注意的是,這邊輸出時我們使用 Scala 的函式 `println`,而不是 `System.out` 裡的函式。 + +#### 匿名函式 + +這程式還有改進空間。第一點,函式 `timeFlies` 只是為了能夠被傳遞進 `oncePerSecond` 而定義的。賦予一個只被使用一次的函式名字似乎是沒有必要的,最好能夠在傳入 `oncePerSecond` 時構造出這個函式。Scala 可以藉由*匿名函式*來達到這點。利用匿名函式的改進版本程式如下: + + object TimerAnonymous { + def oncePerSecond(callback: () => Unit) { + while (true) { callback(); Thread sleep 1000 } + } + def main(args: Array[String]) { + oncePerSecond(() => + println("time flies like an arrow...")) + } + } + +這例子中的右箭頭 `=>` 告訴我們有一個匿名函式,右箭頭將函式引數跟函式內容分開。這個例子中,在箭頭左邊那組空的括號告訴我們引數列是空的。函式內容則是跟先前的 `timeFlies` 裡一樣。 + +## 類別 + +之前已講過,Scala 是一個物件導向語言,因此它有著類別的概念 (更精確的說,的確有一些物件導向語言沒有類別的概念,但是 Scala 不是這類)。Scala 宣告類別的語法跟 Java 很接近。一個重要的差別是,Scala 的類別可以有參數。這邊用底下複數的定義來展示: + + class Complex(real: Double, imaginary: Double) { + def re() = real + def im() = imaginary + } + +這個複數類別接受兩個參數,分別為實跟虛部。在創造 `Complex` 的實體時,必須傳入這些參數: `new Complex(1.5, 2.3)`。這個類別有兩個函式分別叫做 `re` 跟 `im` 讓我們取得這兩個部分。 + +值得注意的是,這兩個函式的回傳值並沒有被明確給定。編譯器將會自動的推斷,它會查看這些函式的右側並推導出這兩個函式都會回傳型別為 `Double` 的值。 + +編譯器並不一定每次都能夠推斷出型別,而且很不幸的是我們並沒有簡單規則以分辨哪種情況能推斷,哪種情況不能。因為當編譯器無法推斷未明確給定的型別時它會回報錯誤,實務上這通常不是問題。Scala 初學者在遇到那些看起來很簡單就能推導出型別的情況時,應該嘗試著忽略型別宣告並看看編譯器是不是也覺得可以推斷。多嘗試幾次之後程式員應該能夠體會到何時忽略型別、何時該明確指定。 + +### 無引數函式 + +函式 `re`、`im` 有個小問題,為了呼叫函式,我們必須在函式名稱後面加上一對空括號,如這個例子: + + object ComplexNumbers { + def main(args: Array[String]) { + val c = new Complex(1.2, 3.4) + println("imaginary part: " + c.im()) + } + } + +最好能夠在不需要加括號的情況下取得實虛部,這樣便像是在取資料欄。Scala完全可以做到這件事,需要的只是在定義函式的時候*不要定義引數*。這種函式跟零引數函式是不一樣的,不論是定義或是呼叫,它們都沒有括號跟在名字後面。我們的 `Complex` 可以改寫成: + + class Complex(real: Double, imaginary: Double) { + def re = real + def im = imaginary + } + +### 繼承與覆寫 + +Scala 中所有的類別都繼承自一個母類別。像前一節的 `Complex` 這種沒有指定的例子,Scala 會暗中使用 `scala.AnyRef`。 + +Scala 中可以覆寫繼承自母類別的函式。但是為了避免意外覆寫,必須加上 `override` 修飾字來明確表示要覆寫函式。我們以覆寫 `Complex` 類別中來自 `Object` 的 `toString` 作為範例。 + + class Complex(real: Double, imaginary: Double) { + def re = real + def im = imaginary + override def toString() = + "" + re + (if (im < 0) "" else "+") + im + "i" + } + + +## Case Class 跟模式匹配(pattern matching) + +樹是常見的資料結構。如:解譯器跟編譯器內部常見的表示程式方式便是樹;XML文件是樹;還有一些容器是根基於樹,如紅黑樹。 + +接下來我們會藉由一個小型計算機程式來看看 Scala 是如何呈現並操作樹。這個程式的功能將會是足以操作簡單、僅含有整數常數、整數變數跟加法的算術式。`1+2` 跟 `(x+x)+(7+y)` 為兩個例子。 + +我們得先決定這種表示式的表示法。最自然表示法便是樹,其中節點是操作、葉節點是值。 + +Java 中我們會將這個樹用一個抽象母類別表示,然後每種節點跟葉節點分別有各自的實際類別。在函數編程裡會用代數資料類型。Scala 則是提供了介於兩者之間的 *case class*。將它運用在這邊會是如下: + + abstract class Tree + case class Sum(l: Tree, r: Tree) extends Tree + case class Var(n: String) extends Tree + case class Const(v: Int) extends Tree + +`Sum`、`Var`、`Const` 類別定義成 case class 代表著它們跟一般類別有所差別: + +- 在創建類別實體時不需要用 `new` (也就是說我們可以寫 `Const(5)`,而不是 `new Const(5)`)。 +- 對應所有建構式參數,Scala 會自動定義對應的取值函式 (即,對於 `Const` 類別的實體,我們可以直接用 `c.v` 來取得建構式中的 `v` 參數)。 +- `equals` 跟 `hashCode` 會有預設定義。該定義會根據實體的*結構*而不是個別實體的識別來運作。 +- `toString` 會有預設定義。會印出"原始型態" (即,`x+1` 的樹會被印成`Sum(Var(x),Const(1))`)。 +- 這些類別的實體可以藉由*模式匹配*來拆解。 + +現在我們有了算術表示式的資料型別,可以開始定義各種運算。我們將從一個可以在*環境*內對運算式求值的函式起頭。環境的用處是賦值給變數。舉例來說,運算式 `x+1` 在一個將 `x` 賦與 `5` 的環境 (寫作 `{ x -> 5 }` ) 下求值會得到 `6`。 + +因此我們需要一個表示環境的方法。當然我們可以用一些像是雜湊表的關連性資料結構,但是我們也可以直接用函式!環境就只是一個將值對應到 (變數) 名稱的函式。之前提到的環境 `{ x -> 5 }` 在 Scala 中可以簡單的寫作: + + { case "x" => 5 } + +這串符號定義了一個當輸入是字串 `"x"` 時回傳整數 `5`,其他輸入則是用例外表示失敗的函式。 + +開始實作之前,讓我們先給環境型別一個名字。當然,我們可以直接用 `String => Int`,但是給這型別名字可以讓我們簡化程式,而且在未來要改動時較為簡便。在 Scala 我們是這樣表示這件事: + + type Environment = String => Int + +於是型別 `Environment` 便可以當做輸入 `String` 回傳 `Int` 函式的型別之代名。 + +現在我們可以給出求值函式實作。概念上非常簡單:兩個表示式和的值是兩個表示式值的和;變數的值直接從環境取值;常數的值就是常數本身。表示這些在 Scala 裡並不困難: + + def eval(t: Tree, env: Environment): Int = t match { + case Sum(l, r) => eval(l, env) + eval(r, env) + case Var(n) => env(n) + case Const(v) => v + } + +這個求值函式藉由對樹 `t` 做*模式匹配*來求值。上述實作的意思應該從直觀上便很明確: + +1. 首先檢查樹 `t` 是否為 `Sum`,如果是的話將左跟右側子樹綁定到新變數 `l`跟 `r`,然後再對箭頭後方的表示式求值;這一個表示式可以使用(而且這邊也用到)根據箭頭左側模式所綁定的變數,也就是 `l` 跟 `r`, +2. 如果第一個檢查失敗,也就是說樹不是 `Sum`,接下來檢查 `t` 是否為 `Var`,如果是的話將 `Var` 所帶的名稱綁定到變數 `n` 並求值右側表示式, +3. 如果第二個檢查也失敗,表示樹不是 `Sum` 也不是 `Var`,那便檢查是不是 `Const`,如果是的話將 `Const` 所帶的名稱綁定到變數 `v` 並求值右側表示式, +4. 最後,如果全部檢查都失敗,會丟出例外表示匹配失敗;這只會在有更多 `Tree` 的子類別時發生。 + +如上,模式匹配基本上就是嘗試將一個值對一系列模式做匹配,並在一個模式成功匹配時抽取並命名該值的各部分,最後對一些程式碼求值,而這些程式碼通常會利用被命名到的部位。 + +一個經驗豐富的物件導向程式員也許會疑惑為何我們不將 `eval` 定義成 `Tree` 類別跟子類的*函式*。由於 Scala 允許在 case class 中跟一般類別一樣定義函式,事實上我們可以這樣做。要用模式匹配或是函式只是品味的問題,但是這會對擴充性有重要影響。 + +- 當使用函式時,只要定義新的 `Tree` 子類便新增新節點,相當容易。另一方面,增加新操作需要修改所有子類,很麻煩。 +- 當使用模式匹配時情況則反過來:增加新節點需要修改所有對樹做模式匹配的函式將新節點納入考慮;增加新操作則很簡單,定義新函式就好。 + +讓我們定義新操作以更進一步的探討模式匹配:對符號求導數。讀者們可能還記得這個操作的規則: + +1. 和的導數是導數的和 +2. 如果是對變數 `v` 取導數,變數 `v` 的導數是1,不然就是0 +3. 常數的導數是0 + +這些規則幾乎可以從字面上直接翻成 Scala 程式碼: + + def derive(t: Tree, v: String): Tree = t match { + case Sum(l, r) => Sum(derive(l, v), derive(r, v)) + case Var(n) if (v == n) => Const(1) + case _ => Const(0) + } + +這個函式引入兩個關於模式匹配的新觀念。首先,變數的 `case` 運算式有一個*看守*,也就是 `if` 關鍵字之後的表示式。除非表示式求值為真,不然這個看守會讓匹配直接失敗。在這邊是用來確定我們只在取導數變數跟被取導數變數名稱相同時才回傳常數 `1`。第二個新特徵是可以匹配任何值的*萬用字元* `_`。 + +我們還沒有探討完模式匹配的全部功能,不過為了讓這份文件保持簡短,先就此打住。我們還是希望能看到這兩個函式在真正的範例如何作用。因此讓我們寫一個簡單的 `main` 函數,對表示式 `(x+x)+(7+y)` 做一些操作:先在環境 `{ x -> 5, y -> 7 }` 下計算結果,然後在對 `x` 接著對 `y` 取導數。 + + def main(args: Array[String]) { + val exp: Tree = Sum(Sum(Var("x"),Var("x")),Sum(Const(7),Var("y"))) + val env: Environment = { case "x" => 5 case "y" => 7 } + println("Expression: " + exp) + println("Evaluation with x=5, y=7: " + eval(exp, env)) + println("Derivative relative to x:\n " + derive(exp, "x")) + println("Derivative relative to y:\n " + derive(exp, "y")) + } + +執行這程式,得到預期的輸出: + + Expression: Sum(Sum(Var(x),Var(x)),Sum(Const(7),Var(y))) + Evaluation with x=5, y=7: 24 + Derivative relative to x: + Sum(Sum(Const(1),Const(1)),Sum(Const(0),Const(0))) + Derivative relative to y: + Sum(Sum(Const(0),Const(0)),Sum(Const(0),Const(1))) + +研究這輸出我們可以發現,取導數的結果應該在輸出前更進一步化簡。用模式匹配實作一個基本化簡函數是一個很有趣 (但是意外的棘手) 的問題,在這邊留給讀者當練習。 + +## 特質 (Traits) + +除了由母類別繼承行為以外,Scala 類別還可以從一或多個*特質*導入行為。 + +對一個 Java 程式員最簡單去理解特質的方式應該是視它們為帶有實作的介面。在 Scala 裡,當一個類別繼承特質時,它實作了該特質的介面並繼承所有特質帶有的功能。 + +為了理解特質的用處,讓我們看一個經典範例:有序物件。大部分情況下,一個類別所產生出來的物件之間可以互相比較大小是很有用的,如排序它們。在Java裡可比較大小的物件實作 `Comparable` 介面。在Scala中藉由定義等價於 `Comparable` 的特質 `Ord`,我們可以做的比Java稍微好一點。 + +當在比較物件的大小時,有六個有用且不同的謂詞 (predicate):小於、小於等於、等於、不等於、大於等於、大於。但是把六個全部都實作很煩,尤其是當其中有四個可以用剩下兩個表示的時候。也就是說,(舉例來說) 只要有等於跟小於謂詞,我們就可以表示其他四個。在 Scala 中這些觀察可以很漂亮的用下面的特質宣告呈現: + + trait Ord { + def < (that: Any): Boolean + def <=(that: Any): Boolean = (this < that) || (this == that) + def > (that: Any): Boolean = !(this <= that) + def >=(that: Any): Boolean = !(this < that) + } + +這份定義同時創造了一個叫做 `Ord` 的新型別,跟 Java 的 `Comparable` 介面有著同樣定位,且給了一份以第一個抽象謂詞表示剩下三個謂詞的預設實作。因為所有物件預設都有一份等於跟不等於的謂詞,這邊便沒有定義。 + +上面使用了一個 `Any` 型別,在 Scala 中這個型別是所有其他型別的母型別。因為它同時也是基本型別如 `Int`、`Float` 的母型別,可以將其視為更為一般化的 Java `Object` 型別。 + +因此只要定義測試相等性跟小於的謂詞,並且加入 `Ord`,就可以讓一個類別的物件們互相比較大小。讓我們實作一個表示陽曆日期的 `Date` 類別來做為例子。這種日期是由日、月、年組成,我們將用整數來表示這三個資料。因此我們可以定義 `Date` 類別為: + + class Date(y: Int, m: Int, d: Int) extends Ord { + def year = y + def month = m + def day = d + override def toString(): String = year + "-" + month + "-" + day + +這邊要注意的是宣告在類別名稱跟參數之後的 `extends Ord`。這個語法宣告了 `Date` 繼承 `Ord` 特質。 + +然後我們重新定義來自 `Object` 的 `equals` 函式好讓這個類別可以正確的根據每個資料欄來比較日期。因為在 Java 中 `equals` 預設實作是直接比較實際物件本身,並不能在這邊用。於是我們有下面的實作: + + override def equals(that: Any): Boolean = + that.isInstanceOf[Date] && { + val o = that.asInstanceOf[Date] + o.day == day && o.month == month && o.year == year + } + +這個函式使用了預定義函式 `isInstanceOf` 跟 `asInstanceOf`。`isInstanceOf` 對應到 Java 的 `instanceof` 運算子,只在當使用它的物件之型別跟給定型別一樣時傳回真。 `asInstanceOf` 對應到 Java 的轉型運算子,如果物件是給定型別的實體,該物件就會被視為給定型別,不然就會丟出 `ClassCastException` 。 + +最後我們需要定義測試小於的謂詞如下。 + + def <(that: Any): Boolean = { + if (!that.isInstanceOf[Date]) + error("cannot compare " + that + " and a Date") + + val o = that.asInstanceOf[Date] + (year < o.year) || + (year == o.year && (month < o.month || + (month == o.month && day < o.day))) + } + +這邊使用了另外一個預定義函式 `error`,它會丟出帶著給定錯誤訊息的例外。這便完成了 `Date` 類別。這個類別的實體可被視為日期或是可比較物件。而且它們通通都定義了之前所提到的六個比較謂詞: `equals` 跟 `<` 直接出現在類別定義當中,其他則是繼承自 `Ord` 特質。 + +特質在其他場合也有用,不過詳細探討它們的用途並不在本文件目標內。 + +## 泛型 + +在這份教學裡,我們最後要探討的 Scala 特性是泛型。Java 程式員應該相當清楚在 Java 1.5 之前缺乏泛型所導致的問題。 + +泛型指的是能夠將型別也作為程式參數。舉例來說,當程式員在為鏈結串列寫函式庫時,它必須決定串列的元素型別為何。由於這串列是要在許多不同場合使用,不可能決定串列的元素型別為如 `Int` 一類。這樣限制太多。 + +Java 程式員採用所有物件的母類別 `Object`。這個解決辦法並不理想,一方面這並不能用在基礎型別 (`int`、`long`、`float` 之類),再來這表示必須靠程式員手動加入大量的動態轉型。 + +Scala 藉由可定義泛型類別 (跟函式) 來解決這問題。讓我們藉由最簡單的類別容器來檢視這點:參照,它可以是空的或者指向某型別的物件。 + + class Reference[T] { + private var contents: T = _ + def set(value: T) { contents = value } + def get: T = contents + } + +類別 `Reference` 帶有一個型別參數 `T`,這個參數會是容器內元素的型別。此型別被用做 `contents` 變數的型別、 `set` 函式的引數型別、 `get` 函式的回傳型別。 + +上面程式碼使用的 Scala 變數語法應該不需要過多的解釋。值得注意的是賦與該變數的初始值是 `_`,該語法表示預設值。數值型別預設值是0,`Boolean` 型別是 `false`, `Unit` 型別是 `()` ,所有的物件型別是 `null`。 + +為了使用 `Reference` 類型,我們必須指定 `T`,也就是這容器所包容的元素型別。舉例來說,創造並使用該容器來容納整數,我們可以這樣寫: + + object IntegerReference { + def main(args: Array[String]) { + val cell = new Reference[Int] + cell.set(13) + println("Reference contains the half of " + (cell.get * 2)) + } + } + +如例子中所展現,並不需要先將 `get` 函式所回傳的值轉型便能當做整數使用。同時因為被宣告為儲存整數,也不可能存除了整數以外的東西到這一個容器中。 + +## 結語 + +本文件對Scala語言做了快速的概覽並呈現一些基本的例子。對 Scala 有更多興趣的讀者可以閱讀有更多進階範例的 *Scala By Example*,並在需要的時候參閱 *Scala Language Specification* 。 diff --git a/api/all.md b/api/all.md new file mode 100644 index 0000000000..2ec60d8e21 --- /dev/null +++ b/api/all.md @@ -0,0 +1,150 @@ +--- +layout: singlepage-overview +title: Scala API Docs +includeTOC: true +--- + +## Latest releases +* Scala 2.12.3 + * [Library API](http://www.scala-lang.org/api/2.12.3/) + * [Compiler API](http://www.scala-lang.org/api/2.12.3/scala-compiler/) + * [Reflection API](http://www.scala-lang.org/api/2.12.3/scala-reflect/#scala.reflect.package) + * Scala Modules + * [XML API](http://www.scala-lang.org/api/2.12.3/scala-xml/#scala.xml.package) + * [Parser Combinators API](http://www.scala-lang.org/api/2.12.3/scala-parser-combinators/) + * [Swing API](http://www.scala-lang.org/api/2.12.3/scala-swing/#scala.swing.package) +* Scala 2.11.8 + * [Library API](http://www.scala-lang.org/api/2.11.8/) + * [Compiler API](http://www.scala-lang.org/api/2.11.8/scala-compiler/) + * [Reflection API](http://www.scala-lang.org/api/2.11.8/scala-reflect/#scala.reflect.package) + * Scala Modules + * [XML API](http://www.scala-lang.org/api/2.11.8/scala-xml/#scala.xml.package) + * [Parser Combinators API](http://www.scala-lang.org/api/2.11.8/scala-parser-combinators/) + * [Actors API](http://www.scala-lang.org/api/2.11.8/scala-actors/#scala.actors.package) (deprecated) + * [Swing API](http://www.scala-lang.org/api/2.11.8/scala-swing/#scala.swing.package) + * [Continuations API](http://www.scala-lang.org/files/archive/api/2.11.8/scala-continuations-library/#scala.util.continuations.package) +* [Scala 2.10.6](http://www.scala-lang.org/api/2.10.6/) + +## Nightly builds + +* Scala 2.11.x + * [Library API](http://www.scala-lang.org/files/archive/nightly/2.11.x/api/2.11.x/) + * [Compiler API](http://www.scala-lang.org/files/archive/nightly/2.11.x/api/2.11.x/scala-compiler/) +* Scala 2.12.x + * [Library API](http://www.scala-lang.org/files/archive/nightly/2.12.x/api/2.12.x/) + * [Compiler API](http://www.scala-lang.org/files/archive/nightly/2.12.x/api/2.12.x/scala-compiler/) + +## Previous releases + +* Scala 2.12.2 + * [Library API](http://www.scala-lang.org/api/2.12.2/) + * [Compiler API](http://www.scala-lang.org/api/2.12.2/scala-compiler/) + * [Reflection API](http://www.scala-lang.org/api/2.12.2/scala-reflect/#scala.reflect.package) + * Scala Modules + * [XML API](http://www.scala-lang.org/api/2.12.2/scala-xml/#scala.xml.package) + * [Parser Combinators API](http://www.scala-lang.org/api/2.12.2/scala-parser-combinators/) + * [Swing API](http://www.scala-lang.org/api/2.12.2/scala-swing/#scala.swing.package) +* Scala 2.12.1 + * [Library API](http://www.scala-lang.org/api/2.12.1/) + * [Compiler API](http://www.scala-lang.org/api/2.12.1/scala-compiler/) + * [Reflection API](http://www.scala-lang.org/api/2.12.1/scala-reflect/#scala.reflect.package) + * Scala Modules + * [XML API](http://www.scala-lang.org/api/2.12.1/scala-xml/#scala.xml.package) + * [Parser Combinators API](http://www.scala-lang.org/api/2.12.1/scala-parser-combinators/) + * [Swing API](http://www.scala-lang.org/api/2.12.1/scala-swing/#scala.swing.package) +* Scala 2.11.7 + * [Library API](http://www.scala-lang.org/api/2.11.7/) + * [Compiler API](http://www.scala-lang.org/api/2.11.7/scala-compiler/) + * [Reflection API](http://www.scala-lang.org/api/2.11.7/scala-reflect/#scala.reflect.package) + * Scala Modules + * [XML API](http://www.scala-lang.org/api/2.11.7/scala-xml/#scala.xml.package) + * [Parser Combinators API](http://www.scala-lang.org/api/2.11.7/scala-parser-combinators/) + * [Actors API](http://www.scala-lang.org/api/2.11.7/scala-actors/#scala.actors.package) (deprecated) + * [Swing API](http://www.scala-lang.org/api/2.11.7/scala-swing/#scala.swing.package) + * [Continuations API](http://www.scala-lang.org/files/archive/api/2.11.7/scala-continuations-library/#scala.util.continuations.package) +* Scala 2.11.6 + * [Library API](http://www.scala-lang.org/api/2.11.6/) + * [Compiler API](http://www.scala-lang.org/api/2.11.6/scala-compiler/) + * [Reflection API](http://www.scala-lang.org/api/2.11.6/scala-reflect/#scala.reflect.package) + * Scala Modules + * [XML API](http://www.scala-lang.org/api/2.11.6/scala-xml/#scala.xml.package) + * [Parser Combinators API](http://www.scala-lang.org/api/2.11.6/scala-parser-combinators/) + * [Actors API](http://www.scala-lang.org/api/2.11.6/scala-actors/#scala.actors.package) (deprecated) + * [Swing API](http://www.scala-lang.org/api/2.11.6/scala-swing/#scala.swing.package) + * [Continuations API](http://www.scala-lang.org/files/archive/api/2.11.6/scala-continuations-library/#scala.util.continuations.package) +* Scala 2.11.5 + * [Library API](http://www.scala-lang.org/api/2.11.5/) + * [Compiler API](http://www.scala-lang.org/api/2.11.5/scala-compiler/) + * [Reflection API](http://www.scala-lang.org/api/2.11.5/scala-reflect/#scala.reflect.package) + * Scala Modules + * [XML API](http://www.scala-lang.org/api/2.11.5/scala-xml/#scala.xml.package) + * [Parser Combinators API](http://www.scala-lang.org/api/2.11.5/scala-parser-combinators/) + * [Actors API](http://www.scala-lang.org/api/2.11.5/scala-actors/#scala.actors.package) (deprecated) + * [Swing API](http://www.scala-lang.org/api/2.11.5/scala-swing/#scala.swing.package) + * [Continuations API](http://www.scala-lang.org/files/archive/api/2.11.5/scala-continuations-library/#scala.util.continuations.package) +* Scala 2.11.4 + * [Library API](http://www.scala-lang.org/api/2.11.4/) + * [Compiler API](http://www.scala-lang.org/api/2.11.4/scala-compiler/) + * [Reflection API](http://www.scala-lang.org/api/2.11.4/scala-reflect/#scala.reflect.package) + * Scala Modules + * [XML API](http://www.scala-lang.org/api/2.11.4/scala-xml/#scala.xml.package) + * [Parser Combinators API](http://www.scala-lang.org/api/2.11.4/scala-parser-combinators/) + * [Actors API](http://www.scala-lang.org/api/2.11.4/scala-actors/#scala.actors.package) (deprecated) + * [Swing API](http://www.scala-lang.org/api/2.11.4/scala-swing/#scala.swing.package) + * [Continuations API](http://www.scala-lang.org/files/archive/api/2.11.4/scala-continuations-library/#scala.util.continuations.package) +* Scala 2.11.2 + * [Library API](http://www.scala-lang.org/api/2.11.2/) + * [Compiler API](http://www.scala-lang.org/api/2.11.2/scala-compiler/) + * [Reflection API](http://www.scala-lang.org/api/2.11.2/scala-reflect/#scala.reflect.package) + * Scala Modules + * [XML API](http://www.scala-lang.org/api/2.11.2/scala-xml/#scala.xml.package) + * [Parser Combinators API](http://www.scala-lang.org/api/2.11.2/scala-parser-combinators/) + * [Actors API](http://www.scala-lang.org/api/2.11.2/scala-actors/#scala.actors.package) (deprecated) + * [Swing API](http://www.scala-lang.org/api/2.11.2/scala-swing/#scala.swing.package) + * [Continuations API](http://www.scala-lang.org/files/archive/api/2.11.2/scala-continuations-library/#scala.util.continuations.package) +* Scala 2.11.1 + * [Library API](http://www.scala-lang.org/api/2.11.1/) + * [Compiler API](http://www.scala-lang.org/api/2.11.1/scala-compiler/) + * [Reflection API](http://www.scala-lang.org/api/2.11.1/scala-reflect/#scala.reflect.package) + * Scala Modules + * [XML API](http://www.scala-lang.org/api/2.11.1/scala-xml/#scala.xml.package) + * [Parser Combinators API](http://www.scala-lang.org/api/2.11.1/scala-parser-combinators/) + * [Actors API](http://www.scala-lang.org/api/2.11.1/scala-actors/#scala.actors.package) (deprecated) + * [Swing API](http://www.scala-lang.org/api/2.11.1/scala-swing/#scala.swing.package) + * [Continuations API](http://www.scala-lang.org/files/archive/api/2.11.1/scala-continuations-library/#scala.util.continuations.package) +* Scala 2.11.0 + * [Library API](http://www.scala-lang.org/api/2.11.0/) + * [Compiler API](http://www.scala-lang.org/api/2.11.0/scala-compiler/) + * [Reflection API](http://www.scala-lang.org/api/2.11.0/scala-reflect/#scala.reflect.package) + * Scala Modules + * [XML API](http://www.scala-lang.org/api/2.11.0/scala-xml/#scala.xml.package) + * [Parser Combinators API](http://www.scala-lang.org/api/2.11.0/scala-parser-combinators/) + * [Actors API](http://www.scala-lang.org/api/2.11.0/scala-actors/#scala.actors.package) (deprecated) + * [Swing API](http://www.scala-lang.org/api/2.11.0/scala-swing/#scala.swing.package) + * [Continuations API](http://www.scala-lang.org/files/archive/api/2.11.0/scala-continuations-library/#scala.util.continuations.package) +* [Scala 2.10.5](http://www.scala-lang.org/api/2.10.5/) +* [Scala 2.10.4](http://www.scala-lang.org/api/2.10.4/) +* [Scala 2.10.3](http://www.scala-lang.org/api/2.10.3/) +* [Scala 2.10.2](http://www.scala-lang.org/api/2.10.2/) +* [Scala 2.10.1](http://www.scala-lang.org/api/2.10.1/) +* [Scala 2.9.3](http://www.scala-lang.org/api/2.9.3/) +* [Scala 2.9.2](http://www.scala-lang.org/api/2.9.2/) +* [Scala 2.9.1-1](http://www.scala-lang.org/api/2.9.1-1/) +* [Scala 2.9.1.final](http://www.scala-lang.org/api/2.9.1/) +* [Scala 2.9.0.1](http://www.scala-lang.org/api/2.9.0.1/) +* [Scala 2.9.0.final](http://www.scala-lang.org/api/2.9.0/) +* [Scala 2.8.2.final](http://www.scala-lang.org/api/2.8.2/) +* [Scala 2.8.1.final](http://www.scala-lang.org/api/2.8.1/) +* [Scala 2.8.0.final](http://www.scala-lang.org/api/2.8.0/) +* [Scala 2.7.7.final](http://www.scala-lang.org/api/2.7.7/) +* [Scala 2.7.6.final](http://www.scala-lang.org/api/2.7.6/) +* [Scala 2.7.5.final](http://www.scala-lang.org/api/2.7.5/) +* [Scala 2.7.4.final](http://www.scala-lang.org/api/2.7.4/) +* [Scala 2.7.3.final](http://www.scala-lang.org/api/2.7.3/) +* [Scala 2.7.2.final](http://www.scala-lang.org/api/2.7.2/) +* [Scala 2.7.1.final](http://www.scala-lang.org/api/2.7.1/) +* [Scala 2.7.0.final](http://www.scala-lang.org/api/2.7.0/) +* [Scala 2.6.1.final](http://www.scala-lang.org/api/2.6.1/) +* [Scala 2.6.0.final](http://www.scala-lang.org/api/2.6.0/) +* [Scala 2.5.1.final](http://www.scala-lang.org/api/2.5.1/) +* [Scala 2.5.0.final](http://www.scala-lang.org/api/2.5.0/) diff --git a/ba/cheatsheets/index.md b/ba/cheatsheets/index.md deleted file mode 100644 index e336e31a23..0000000000 --- a/ba/cheatsheets/index.md +++ /dev/null @@ -1,86 +0,0 @@ ---- -layout: cheatsheet -istranslation: true -title: Scalacheat -by: Brendan O'Connor -about: Zahvaljujući Brendan O'Connoru ovaj cheatsheet teži da bude kratki pregled sintakse Scale. Licenca pripada Brendan O'Connor-u, pod CC-BY-SA 3.0 licencom. -language: ba ---- - -###### Doprinio {{ page.by }} - -| | | -| ------ | ------ | -| varijable | | -| `var x = 5` | varijabla. | -| Dobro `val x = 5`
        Loše `x=6` | konstanta. | -| `var x: Double = 5` | eksplicitni tip. | -| funkcije | | -| Dobro `def f(x: Int) = { x*x }`
        Loše `def f(x: Int) { x*x }` | definicija funkcije.
        skrivena greška: bez `=` ovo je procedura koja vraća `Unit`; uzrokuje zabunu. | -| Dobro `def f(x: Any) = println(x)`
        Loše `def f(x) = println(x)` | definicija funkcije.
        sintaksna greška: potrebni su tipovi za svaki argument. | -| `type R = Double` | pseudonim za tip. | -| `def f(x: R)` ili
        `def f(x: => R)` | poziv-po-vrijednosti.
        poziv-po-imenu (lijeni parameteri). | -| `(x:R) => x*x` | anonimna funkcija. | -| `(1 to 5).map(_*2)` ili
        `(1 to 5).reduceLeft( _+_ )` | anonimna funkcija: donja crta odgovara argumentu po poziciji. | -| `(1 to 5).map( x => x*x )` | anonimna funkcija: da bi koristili argument više od jednom, morate mu dati ime. | -| Dobro `(1 to 5).map(2*)`
        Loše `(1 to 5).map(*2)` | anonimna funkcija: vezana infiksna metoda. Koristite `2*_` zbog jasnoće. | -| `(1 to 5).map { x => val y=x*2; println(y); y }` | anonimna funkcija: blokovski stil vraća vrijednost zadnjeg izraza. | -| `(1 to 5) filter {_%2 == 0} map {_*2}` | anonimne funkcije: pipeline stil (može i sa oblim zagradama). | -| `def compose(g:R=>R, h:R=>R) = (x:R) => g(h(x))`
        `val f = compose({_*2}, {_-1})` | anonimne funkcije: da bi proslijedili više blokova, potrebne su dodatne zagrade. | -| `val zscore = (mean:R, sd:R) => (x:R) => (x-mean)/sd` | curry-jevanje, očita sintaksa. | -| `def zscore(mean:R, sd:R) = (x:R) => (x-mean)/sd` | curry-jevanje, očita sintaksa. | -| `def zscore(mean:R, sd:R)(x:R) = (x-mean)/sd` | curry-jevanje, sintaksni šećer (kratica). Ali onda: | -| `val normer = zscore(7, 0.4) _` | je potrebna prateća donja crta za parcijalnu primjenu, samo kod šećer (skraćene) verzije. | -| `def mapmake[T](g:T=>T)(seq: List[T]) = seq.map(g)` | generički tip. | -| `5.+(3); 5 + 3`
        `(1 to 5) map (_*2)` | infiksni šećer. | -| `def sum(args: Int*) = args.reduceLeft(_+_)` | varirajući broj argumenata (varargs). | -| paketi | | -| `import scala.collection._` | džoker (wildcard) import. | -| `import scala.collection.Vector`
        `import scala.collection.{Vector, Sequence}` | selektivni import. | -| `import scala.collection.{Vector => Vec28}` | preimenujući import. | -| `import java.util.{Date => _, _}` | import svega iz `java.util` paketa osim `Date`. | -| `package pkg` _na početku fajla_
        `package pkg { ... }` | deklaracija paketa. | -| strukture podataka | | -| `(1,2,3)` | torka (tuple) literal (`Tuple3`). | -| `var (x,y,z) = (1,2,3)` | destrukturirajuće vezivanje: otpakivanje torke podudaranjem uzoraka (pattern matching). | -| Loše`var x,y,z = (1,2,3)` | skrivena greška: svim varijablama dodijeljena cijela torka. | -| `var xs = List(1,2,3)` | lista (nepromjenjiva). | -| `xs(2)` | indeksiranje zagradama ([slajdovi](http://www.slideshare.net/Odersky/fosdem-2009-1013261/27)). | -| `1 :: List(2,3)` | cons. | -| `1 to 5` _isto kao_ `1 until 6`
        `1 to 10 by 2` | šećer za raspon (range). | -| `()` _(prazne zagrade)_ | jedina instanca Unit tipa (slično kao u C/Java void). | -| kontrolne strukture | | -| `if (check) happy else sad` | uslov. | -| `if (check) happy` _isto kao_
        `if (check) happy else ()` | sintaksni šećer za uslov. | -| `while (x < 5) { println(x); x += 1}` | while petlja. | -| `do { println(x); x += 1} while (x < 5)` | do while petlja. | -| `import scala.util.control.Breaks._`
        `breakable {`
        ` for (x <- xs) {`
        ` if (Math.random < 0.1) break`
        ` }`
        `}`| break ([slajdovi](http://www.slideshare.net/Odersky/fosdem-2009-1013261/21)). | -| `for (x <- xs if x%2 == 0) yield x*10` _isto kao_
        `xs.filter(_%2 == 0).map(_*10)` | for komprehensija: filter/map. | -| `for ((x,y) <- xs zip ys) yield x*y` _isto kao_
        `(xs zip ys) map { case (x,y) => x*y }` | for komprehensija: destrukturirajuće vezivanje. | -| `for (x <- xs; y <- ys) yield x*y` _isto kao_
        `xs flatMap {x => ys map {y => x*y}}` | for komprehensija: međuproizvod (vektorski proizvod). | -| `for (x <- xs; y <- ys) {`
        `println("%d/%d = %.1f".format(x, y, x/y.toFloat))`
        `}` | for komprehensija: imperativ-asto.
        [sprintf-stil.](http://java.sun.com/javase/6/docs/api/java/util/Formatter.html#syntax) | -| `for (i <- 1 to 5) {`
        `println(i)`
        `}` | for komprehensija: iteracija uključujući gornju granicu. | -| `for (i <- 1 until 5) {`
        `println(i)`
        `}` | for komprehensija: iteracija ne uključujući gornju granicu. | -| podudaranje uzoraka (pattern matching) | | -| Dobro `(xs zip ys) map { case (x,y) => x*y }`
        Loše `(xs zip ys) map( (x,y) => x*y )` | slučaj korištenja u argumentima funkcije. | -| Loše
        `val v42 = 42`
        `Some(3) match {`
        ` case Some(v42) => println("42")`
        ` case _ => println("Not 42")`
        `}` | "v42" interpretira se kao ime koje odgovara bilo kojoj vrijednosti Int, i "42" se prikazuje. | -| Dobro
        `val v42 = 42`
        `Some(3) match {`
        `` case Some(`v42`) => println("42")``
        `case _ => println("Not 42")`
        `}` | "\`v42\`" s kosim apostrofima interpretira se kao postojeća val `v42`, i "Not 42" se prikazuje. | -| Dobro
        `val UppercaseVal = 42`
        `Some(3) match {`
        ` case Some(UppercaseVal) => println("42")`
        ` case _ => println("Not 42")`
        `}` | `UppercaseVal` tretira se kao postojeća val, a ne kao nova vrijednost uzorka, zato što počinje velikim slovom. Stoga, vrijednost u `UppercaseVal` se poredi sa `3`, i "Not 42" se prikazuje. | -| objektna orijentisanost | | -| `class C(x: R)` _isto kao_
        `class C(private val x: R)`
        `var c = new C(4)` | parameteri konstruktora - privatni. | -| `class C(val x: R)`
        `var c = new C(4)`
        `c.x` | parameteri konstruktora - javni. | -| `class C(var x: R) {`
        `assert(x > 0, "positive please")`
        `var y = x`
        `val readonly = 5`
        `private var secret = 1`
        `def this = this(42)`
        `}`|
        konstruktor je tijelo klase.
        deklaracija javnog člana.
        deklaracija dostupnog ali nepromjenjivog člana
        deklaracija privatnog člana.
        alternativni konstruktor.| -| `new{ ... }` | anonimna klasa. | -| `abstract class D { ... }` | definicija apstraktne klase (ne može se kreirati). | -| `class C extends D { ... }` | definicija nasljedne klase. | -| `class D(var x: R)`
        `class C(x: R) extends D(x)` | nasljeđivanje i parameteri konstruktora (lista želja: automatsko prosljeđivanje parametara...). -| `object O extends D { ... }` | definicija singletona (kao modul). | -| `trait T { ... }`
        `class C extends T { ... }`
        `class C extends D with T { ... }` | trejtovi.
        interfejs-s-implementacijom. Bez parametara konstruktora. [Miksabilan]({{ site.baseurl }}/tutorials/tour/mixin-class-composition.html). -| `trait T1; trait T2`
        `class C extends T1 with T2`
        `class C extends D with T1 with T2` | više trejtova. | -| `class C extends D { override def f = ...}` | moraju se deklarisati prebrisane metode. | -| `new java.io.File("f")` | kreiranje objekta. | -| Loše `new List[Int]`
        Dobro `List(1,2,3)` | greška tipa: apstraktni tip.
        umjesto toga, konvencija: fabrika istoimenog tipa. | -| `classOf[String]` | literal za klasu. | -| `x.isInstanceOf[String]` | provjera tipa (runtime). | -| `x.asInstanceOf[String]` | kastovanje tipa (runtime). | -| `x: String` | askripcija (compile time). | diff --git a/ba/tutorials/tour/_posts/2017-02-13-abstract-types.md b/ba/tutorials/tour/_posts/2017-02-13-abstract-types.md deleted file mode 100644 index cc58e31833..0000000000 --- a/ba/tutorials/tour/_posts/2017-02-13-abstract-types.md +++ /dev/null @@ -1,86 +0,0 @@ ---- -layout: tutorial -title: Apstraktni tipovi - -discourse: false - -tutorial: scala-tour -categories: tour -num: 22 -outof: 33 -language: ba - -next-page: compound-types -previous-page: inner-classes ---- - -U Scali, klase su parameterizovane vrijednostima (parameteri konstruktora) i tipovima (ako su [generičke](generic-classes.html)). -Zbog dosljednosti, ne samo da je moguće imati vrijednosti kao članove objekta već i tipove. -Nadalje, obje forme članova mogu biti konkretne ili apstraktne. -Slijedi primjer koji sadrži obje forme: apstraktnu vrijednost i apstraktni tip kao članove [klase](traits.html) `Buffer`. - - trait Buffer { - type T - val element: T - } - -*Apstraktni tipovi* su tipovi čiji identitet nije precizno definisan. -U gornjem primjeru, poznato je samo da svaki objekat klase `Buffer` ima tip-član `T`, -ali definicija klase `Buffer` ne kazuje kojem konkretno tipu odgovara `T`. -Kao i definicije vrijednosti, možemo redefinisati (override) definicije tipova u podklasama. -Ovo nam omogućuje da otkrijemo više informacija o apstraktnom tipu sužavanjem granica tipa (koje opisuju moguće konkretne instance apstraktnog tipa). - -U sljedećem programu izvodimo klasu `SeqBuffer` koja omogućuje čuvanje samo sekvenci u baferu kazivanjem da tip `T` -mora biti podtip `Seq[U]` za neki novi apstraktni tip `U`: - - abstract class SeqBuffer extends Buffer { - type U - type T <: Seq[U] - def length = element.length - } - -Trejtovi (trait) ili [klase](classes.html) s apstraktnim tip-članovima se često koriste u kombinaciji s instanciranjem anonimnih klasa. -Radi ilustracije, pogledaćemo program koji radi s sekvencijalnim baferom koji sadrži listu integera: - - 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) - } - -Povratni tip metode `newIntSeqBuf` odnosi se na specijalizaciju trejta `Buffer` u kom je tip `U` sada jednak `Int`u. -Imamo sličan alijas tip u anonimnoj instanci klase u tijelu metode `newIntSeqBuf`. -Ovdje kreiramo novu instancu `IntSeqBuffer` u kojoj se tip `T` odnosi na `List[Int]`. - -Imajte na umu da je često moguće pretvoriti apstraktni tip-član u tipski parametar klase i obrnuto. -Slijedi verzija gornjeg koda koji koristi tipske parametre: - - 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) - } - -Primijetite da moramo koristiti [anotacije za varijansu](variances.html) ovdje; -inače ne bismo mogli sakriti konkretni tip sekvencijalne implementacije objekta vraćenog iz metode `newIntSeqBuf`. -Nadalje, postoje slučajevi u kojima nije moguće zamijeniti apstraktne tipove tip parametrima. - diff --git a/ba/tutorials/tour/_posts/2017-02-13-annotations.md b/ba/tutorials/tour/_posts/2017-02-13-annotations.md deleted file mode 100644 index 201e3c26eb..0000000000 --- a/ba/tutorials/tour/_posts/2017-02-13-annotations.md +++ /dev/null @@ -1,134 +0,0 @@ ---- -layout: tutorial -title: Anotacije - -discourse: false - -tutorial: scala-tour -categories: tour -num: 31 -outof: 33 -language: ba - -next-page: default-parameter-values -previous-page: case-classes ---- - -Anotacije pridružuju meta-informacije definicijama. - -Jednostavna anotacija ima formu `@C` ili `@C(a1, .., an)`. Ovdje je `C` konstruktor klase `C`, koja mora naslijediti klasu `scala.Annotation`. -Svi argumenti konstruktora `a1, .., an` moraju biti konstante (npr. izrazi ili numeričke primitive, stringovi, primitive klasa, -Java enumeracije i jednodimenzionalni nizovi (`Array`) navedenih). - -Anotacijska klauza (ili više njih) primjenjuje se na prvu definiciju ili deklaraciju koja slijedi nakon nje. -Redoslijed anotacijskih klauza nije bitan. - -Značenje anotacijskih klauza je _implementacijski-nezavisno_. Na Java platformi, sljedeće Scala anotacije imaju standardno značenje. - -| Scala | Java | -| ------ | ------ | -| [`scala.SerialVersionUID`](https://www.scala-lang.org/api/current/scala/SerialVersionUID.html) | [`serialVersionUID`](http://java.sun.com/j2se/1.5.0/docs/api/java/io/Serializable.html#navbar_bottom) (polje) | -| [`scala.deprecated`](https://www.scala-lang.org/api/current/scala/deprecated.html) | [`java.lang.Deprecated`](http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Deprecated.html) | -| [`scala.inline`](https://www.scala-lang.org/api/current/scala/inline.html) (od 2.6.0) | nema ekvivalent | -| [`scala.native`](https://www.scala-lang.org/api/current/scala/native.html) (od 2.6.0) | [`native`](http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_ključna riječs.html) (ključna riječ) | -| [`scala.remote`](https://www.scala-lang.org/api/current/scala/remote.html) | [`java.rmi.Remote`](http://java.sun.com/j2se/1.5.0/docs/api/java/rmi/Remote.html) | -| [`scala.throws`](https://www.scala-lang.org/api/current/scala/throws.html) | [`throws`](http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html) (ključna riječ) | -| [`scala.transient`](https://www.scala-lang.org/api/current/scala/transient.html) | [`transient`](http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html) (ključna riječ) | -| [`scala.unchecked`](https://www.scala-lang.org/api/current/scala/unchecked.html) (od 2.4.0) | nema ekvivalent | -| [`scala.volatile`](https://www.scala-lang.org/api/current/scala/volatile.html) | [`volatile`](http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html) (ključna riječ) | -| [`scala.beans.BeanProperty`](https://www.scala-lang.org/api/current/scala/beans/BeanProperty.html) | [`Dizajn patern`](http://docs.oracle.com/javase/tutorial/javabeans/writing/properties.html) | - -U sljedećem primjeru dodajemo `throws` anotaciju definiciji metode `read` da bi uhvatili izuzetak (exception) u Java main programu. - -> Java kompajler provjerava da li program sadrži rukovatelje (handler) za [provjereni izuzetak (checked exception)](http://docs.oracle.com/javase/specs/jls/se5.0/html/exceptions.html) -analizom koji se izuzeci mogu dobiti izvršenjem neke metode ili konstruktora. -Za svaki mogući provjereni izuzetak **throws** klauza metodi ili konstruktora _mora_ navesti klasu izuzetka ili neku nadklasu izuzetka. -> Pošto Scala nema provjerene izuzetke, Scala metode _moraju_ imati jednu ili više `throws` anotacija kako bi Java kod mogao uhvatiti iste. - - package examples - import java.io._ - class Reader(fname: String) { - private val in = new BufferedReader(new FileReader(fname)) - @throws(classOf[IOException]) - def read() = in.read() - } - -Sljedeći Java program prikazuje sadržaj fajla s imenom koje je proslijeđeno kao prvi argument `main` metode. - - package test; - import examples.Reader; // Scala klasa !! - 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()); - } - } - } - -Kada bi zakomentarisali `throws` anotaciju u klasi `Reader` dobili bi sljedeću grešku s porukom pri kompajliranju Java main programa: - - Main.java:11: exception java.io.IOException is never thrown in body of - corresponding try statement - } catch (java.io.IOException e) { - ^ - 1 error - -### Java anotacije ### - -**Napomena:** Pobrinite se da koristite `-target:jvm-1.5` opciju sa Java anotacijama. - -Java 1.5 je uvela korisnički definisane metapodatke u formi [anotacija](http://java.sun.com/j2se/1.5.0/docs/guide/language/annotations.html). Ključna sposobnost anotacija je da koriste parove ime-vrijednost za inicijalizaciju svojih elemenata. Naprimjer, ako nam treba anotacija da pratimo izvor neke klase mogli bismo je definisati kao - - @interface Source { - public String URL(); - public String mail(); - } - -I upotrijebiti je kao npr.: - - @Source(URL = "http://coders.com/", - mail = "support@coders.com") - public class MyClass extends HisClass ... - -Primjena anotacije u Scali izgleda kao poziv konstruktora, dok se za instanciranje Javinih anotacija moraju koristiti imenovani argumenti: - - @Source(URL = "http://coders.com/", - mail = "support@coders.com") - class MyScalaClass ... - -Ova sintaksa je ponekad naporna, npr. ako anotacija ima samo jedan element (bez podrazumijevane vrijednosti), pa po konvenciji, -ako se koristi naziv `value` onda se u Javi može koristiti i konstruktor-sintaksa: - - @interface SourceURL { - public String value(); - public String mail() default ""; - } - -I upotrijebiti je kao npr.: - - @SourceURL("http://coders.com/") - public class MyClass extends HisClass ... - -U ovom slučaju, Scala omogućuje istu sintaksu: - - @SourceURL("http://coders.com/") - class MyScalaClass ... - -Element `mail` je specificiran s podrazumijevanom vrijednošću tako da ne moramo eksplicitno navoditi vrijednost za njega. -Međutim, ako trebamo, ne možemo miješati dva Javina stila: - - @SourceURL(value = "http://coders.com/", - mail = "support@coders.com") - public class MyClass extends HisClass ... - -Dok u Scali možemo: - - @SourceURL("http://coders.com/", - mail = "support@coders.com") - class MyScalaClass ... diff --git a/ba/tutorials/tour/_posts/2017-02-13-anonymous-function-syntax.md b/ba/tutorials/tour/_posts/2017-02-13-anonymous-function-syntax.md deleted file mode 100644 index 1d1dffa92e..0000000000 --- a/ba/tutorials/tour/_posts/2017-02-13-anonymous-function-syntax.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -layout: tutorial -title: Sintaksa anonimnih funkcija - -discourse: false - -tutorial: scala-tour -categories: tour -num: 6 -outof: 33 -language: ba - -next-page: higher-order-functions -previous-page: mixin-class-composition ---- - -Scala omogućuje relativno lahku sintaksu za definisanje anonimnih funkcija. Sljedeći izraz kreira funkciju za sljedbenike cijelih brojeva: - - (x: Int) => x + 1 - -Ovo je kratica za definiciju sljedeće anonimne klase: - - new Function1[Int, Int] { - def apply(x: Int): Int = x + 1 - } - -Također je moguće definisati funkciju s više parametara: - - (x: Int, y: Int) => "(" + x + ", " + y + ")" - -ili bez parametara: - - () => { System.getProperty("user.dir") } - -Također postoji vrlo lahka sintaksa za pisanje tipa funkcije. Ovo su tipovi tri gore navedene funkcije: - - Int => Int - (Int, Int) => String - () => String - -Ova sintaksa je kratica za sljedeće tipove: - - Function1[Int, Int] - Function2[Int, Int, String] - Function0[String] diff --git a/ba/tutorials/tour/_posts/2017-02-13-automatic-closures.md b/ba/tutorials/tour/_posts/2017-02-13-automatic-closures.md deleted file mode 100644 index f0d551ba99..0000000000 --- a/ba/tutorials/tour/_posts/2017-02-13-automatic-closures.md +++ /dev/null @@ -1,83 +0,0 @@ ---- -layout: tutorial -title: Automatska konstrukcija tipno zavisnih closura (zatvarajućih funkcija) - -discourse: false - -tutorial: scala-tour -categories: tour -num: 30 -outof: 33 -language: ba - -next-page: case-classes -previous-page: operators ---- - -Scala dozvoljava da se argument (ili više njih) metode ne evaluira prije samog poziva metode. -Kada se takva metoda pozove, dati parametar se ne evaluira odmah već se na svakom mjestu u metodi gdje se poziva ubacuje -besparametarska funkcija koja enkapsulira izračunavanje odgovarajućeg parametra (tzv. *poziv-po-imenu* (call-by-name) evaluacija). - -Sljedeći kod demonstrira ovaj mehanizam: - - 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 - } - } - -Funkcija `whileLoop` prima dva parametra, `cond` i `body`. Kada se izvršava tijelo funkcije, stvarni parametri se ne evaluiraju. -Ali svaki put kada se formalni parametri koriste u tijelu `whileLoop`, implicitno kreirana besparametarska funkcija će biti evaluirana. -Stoga, naša metoda `whileLoop` implementira Java-stu while petlju u rekurzivnom stilu. - -Možemo kombinirati upotrebu [infiksnih/postfiksnih operatora](operators.html) s ovim mehanizmom da bi kreirali -komplikovanije naredbe (s lijepom sintaksom). - -Slijedi implementacija loop-unless naredbe: - - 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) - } - -Funkcija `loop` prima samo tijelo i vraća instancu klase `LoopUnlessCond` (koja enkapsulira objekat tijela). -Primijetite da tijelo još uvijek nije evaluirano. -Klasa `LoopUnlessCond` ima metodu `unless` koju možemo koristiti kao *infiksni operator*. -Na ovaj način postižemo prilično prirodnu sintaksu za našu novu petlju: `loop { < stats > } unless ( < cond > )`. - -Ovo je rezultat izvršenja `TargetTest2`: - - i = 10 - i = 9 - i = 8 - i = 7 - i = 6 - i = 5 - i = 4 - i = 3 - i = 2 - i = 1 - diff --git a/ba/tutorials/tour/_posts/2017-02-13-case-classes.md b/ba/tutorials/tour/_posts/2017-02-13-case-classes.md deleted file mode 100644 index 8de2d74751..0000000000 --- a/ba/tutorials/tour/_posts/2017-02-13-case-classes.md +++ /dev/null @@ -1,93 +0,0 @@ ---- -layout: tutorial -title: Case klase - -discourse: false - -tutorial: scala-tour -categories: tour -num: 30 -outof: 33 -language: ba - -next-page: annotations -previous-page: automatic-closures ---- - -Scala podržava tzv. _case klase_. -Case klase su obične klase koje eksponiraju svoje parametre konstruktora i -omogućuju rekurzivnu dekompoziciju pomoću [podudaranja uzorka (pattern matching)](pattern-matching.html). - -Slijedi primjer hijerarhije klasa koja se sastoji od apstraktne nadklase `Term` i tri konkretne case klase `Var`, `Fun`, i `App`. - - abstract class Term - case class Var(name: String) extends Term - case class Fun(arg: String, body: Term) extends Term - case class App(f: Term, v: Term) extends Term - -Ova hijerarhija klasa može biti korištena da predstavi pojmove iz [bestipskog lambda računa](https://en.wikipedia.org/wiki/Lambda_calculus). -Da bi se olakšala konstrukcija instanci case klasa, Scala ne zahtijeva `new` primitivu. Može se jednostavno koristiti ime klase kao funkcija. - -Primjer: - - Fun("x", Fun("y", App(Var("x"), Var("y")))) - -Parametri konstruktora case klasa tretiraju se kao javne (public) vrijednosti i može im se pristupiti direktno. - - val x = Var("x") - println(x.name) - -Za svaku case klasu Scala kompajler izgeneriše `equals` metodu koja implementira strukturalnu jednakost i `toString` metodu. Naprimjer: - - val x1 = Var("x") - val x2 = Var("x") - val y1 = Var("y") - println("" + x1 + " == " + x2 + " => " + (x1 == x2)) - println("" + x1 + " == " + y1 + " => " + (x1 == y1)) - -će prikazati - - Var(x) == Var(x) => true - Var(x) == Var(y) => false - -Ima smisla definisati case klasu samo ako će biti korištena sa podudaranjem uzorka za dekompoziciju. -Sljedeći [objekt](singleton-objects.html) definiše funkciju za lijepo ispisivanje naše reprezentacije lambda računa: - - object TermTest extends scala.App { - - def printTerm(term: Term) { - term match { - case Var(n) => - print(n) - case Fun(x, b) => - print("^" + x + ".") - printTerm(b) - case App(f, v) => - print("(") - printTerm(f) - print(" ") - printTerm(v) - print(")") - } - } - - def isIdentityFun(term: Term): Boolean = term match { - case Fun(x, Var(y)) if x == y => true - case _ => false - } - - val id = Fun("x", Var("x")) - val t = Fun("x", Fun("y", App(Var("x"), Var("y")))) - - printTerm(t) - println - println(isIdentityFun(id)) - println(isIdentityFun(t)) - } - -U našem primjeru, funkcija `printTerm` je izražena kao naredba podudaranja uzorka s `match` ključnom riječi -i sastoji se od niza `case Pattern => Body` klauza. -Gornji program također definiše funkciju `isIdentityFun` koja provjerava da li dati pojam odgovara jednostavnoj funkciji identiteta. -Ovaj primjer koristi duboke uzorke i čuvare (guard). -Nakon što se uzorak podudari s datom vrijednošću, čuvar (definisan nakon ključne riječi `if`) se evaluira. -Ako vrati `true`, podudaranje uspijeva; u suprotnom, ne uspijeva i sljedeći uzorak će biti pokušan. diff --git a/ba/tutorials/tour/_posts/2017-02-13-classes.md b/ba/tutorials/tour/_posts/2017-02-13-classes.md deleted file mode 100644 index 460bd90eb0..0000000000 --- a/ba/tutorials/tour/_posts/2017-02-13-classes.md +++ /dev/null @@ -1,61 +0,0 @@ ---- -layout: tutorial -title: Klase - -discourse: false - -tutorial: scala-tour -categories: tour -num: 3 -outof: 33 -language: ba - -next-page: traits -previous-page: unified-types ---- - -Klase u Scali su statički šabloni koji mogu biti instancirani u više objekata tokom izvršavanja programa (runtime). -Slijedi definicija klase `Point`: - - class Point(xc: Int, yc: Int) { - - var x: Int = xc - var y: Int = yc - - def move(dx: Int, dy: Int) { - x = x + dx - y = y + dy - } - - override def toString(): String = "(" + x + ", " + y + ")"; - } - -Ova klasa definiše dvije varijable: `x` i `y`, i dvije metode: `move` i `toString`. -Metoda `move` prima dva cjelobrojna argumenta ali ne vraća vrijednost (implicitni povratni tip je `Unit`, -koji odgovoara `void`-u u jezicima sličnim Javi). `toString`, za razliku, ne prima nikakve parametre ali vraća `String` vrijednost. -Pošto `toString` prebrisava predefinisanu `toString` metodu, mora biti tagovana s `override`. - -Klase u Scali se parametrizuju parametrima konstruktora. Kod iznad definiše dva parametra konstruktora, `xc` i `yc`; -oba su vidljiva u cijelom tijelu klase. U našem primjeru korišteni su za inicijalizaciju varijabli `x` i `y`. - -Klase se inicijalizaciju pomoću `new` primitive, kao u sljedećem primjeru: - - object Classes { - def main(args: Array[String]) { - val pt = new Point(1, 2) - println(pt) - pt.move(10, 10) - println(pt) - } - } - -Program definiše izvršnu aplikaciju `Classes` u form vrhovnog singlton objekta s `main` metodom. -Metoda `main` kreira novu instancu klase `Point` i sprema je u vrijednost `pt`. -_Imajte u vidu da se vrijednosti definisane primitivom `val` razlikuju -od varijabli definisanih primitivom `var` (vidi klasu `Point` iznad) -u tome da ne dozvoljavaju promjenu vrijednosti; tj. vrijednost je konstanta._ - -Ovo je rezultat programa: - - (1, 2) - (11, 12) diff --git a/ba/tutorials/tour/_posts/2017-02-13-compound-types.md b/ba/tutorials/tour/_posts/2017-02-13-compound-types.md deleted file mode 100644 index 6de3dd1b21..0000000000 --- a/ba/tutorials/tour/_posts/2017-02-13-compound-types.md +++ /dev/null @@ -1,54 +0,0 @@ ---- -layout: tutorial -title: Složeni tipovi - -discourse: false - -tutorial: scala-tour -categories: tour -num: 23 -outof: 33 -language: ba - -next-page: explicitly-typed-self-references -previous-page: abstract-types ---- - -Ponekad je potrebno izraziti da je tip objekta podtip nekoliko drugih tipova. -U Scali ovo može biti izraženo pomoću *složenih tipova*, koji su presjeci tipova objekata. - -Pretpostavimo da imamo dva trejta: `Cloneable` i `Resetable`: - - trait Cloneable extends java.lang.Cloneable { - override def clone(): Cloneable = { - super.clone().asInstanceOf[Cloneable] - } - } - trait Resetable { - def reset: Unit - } - -Pretpostavimo da želimo napisati funkciju `cloneAndReset` koja prima objekt, klonira ga i resetuje originalni objekt: - - def cloneAndReset(obj: ?): Cloneable = { - val cloned = obj.clone() - obj.reset - cloned - } - -Postavlja se pitanje koji bi trebao biti tip parametra `obj`. -Ako je `Cloneable` onda objekt može biti `clone`-iran, ali ne i `reset`-ovan; -ako je `Resetable` onda se može `reset`, ali ne i `clone`. -Da bi izbjegli kastovanje tipa u ovoj situaciji, možemo navesti tip `obj` da bude oboje `Cloneable` i `Resetable`. -Ovaj složeni tip u Scali se piše kao: `Cloneable with Resetable`. - -Ovo je ažurirana funkcija: - - def cloneAndReset(obj: Cloneable with Resetable): Cloneable = { - //... - } - -Složeni tipovi mogu se sastojati od više tipova i mogu imati jednu rafinaciju koja može biti korištena da suzi potpis postojećih članova objekta. -General forma je: `A with B with C ... { refinement }` - -Primjer za upotrebu rafinacije dat je na stranici o [apstraktnim tipovima](abstract-types.html). diff --git a/ba/tutorials/tour/_posts/2017-02-13-currying.md b/ba/tutorials/tour/_posts/2017-02-13-currying.md deleted file mode 100644 index 7c417259de..0000000000 --- a/ba/tutorials/tour/_posts/2017-02-13-currying.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -layout: tutorial -title: Curry-jevanje - -discourse: false - -tutorial: scala-tour -categories: tour -num: 9 -outof: 33 -language: ba - -next-page: pattern-matching -previous-page: nested-functions ---- - -Metode mogu definisati više lista parametara. -Kada je metoda pozvana s manje listi parametara nego što ima, -onda će to vratiti funkciju koja prima preostale liste parametara kao argumente. - -Primjer: - - 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))) - } - -_Napomena: metoda `modN` je parcijalno primijenjena u dva poziva `filter`; tj. samo prvi argument je ustvari primijenjen. -Izraz `modN(2)` vraća funkciju tipa `Int => Boolean` i zato je mogući kandidat za drugi argument funkcije `filter`._ - -Rezultat gornjeg programa: - - List(2,4,6,8) - List(3,6) diff --git a/ba/tutorials/tour/_posts/2017-02-13-default-parameter-values.md b/ba/tutorials/tour/_posts/2017-02-13-default-parameter-values.md deleted file mode 100644 index 9394b36377..0000000000 --- a/ba/tutorials/tour/_posts/2017-02-13-default-parameter-values.md +++ /dev/null @@ -1,76 +0,0 @@ ---- -layout: tutorial -title: Podrazumijevane vrijednosti parametara - -discourse: false - -tutorial: scala-tour -categories: tour -num: 32 -outof: 33 -language: ba - -next-page: named-parameters -previous-page: annotations ---- - -Scala omogućuje davanje podrazumijevanih vrijednosti parametrima koje dozvoljavaju korisniku metode da izostavi te parametre. - -U Javi, postoje mnoge preopterećene (overloaded) metode koje samo služe da bi obezbijedile podrazumijevane vrijednosti za neke parametre velike metode. -Ovo je posebno tačno kod konstruktora: - - public class HashMap { - public HashMap(Map m); - /** Kreiraj novu HashMap s podrazumijevanim kapacitetom (16) - * i loadFactor-om (0.75) - */ - public HashMap(); - - /** Kreiraj novu HashMap s podrazumijevanim loadFactor-om (0.75) */ - public HashMap(int initialCapacity); - public HashMap(int initialCapacity, float loadFactor); - } - -Ovdje postoje samo dva konstruktora ustvari; jedan koji prima drugu mapu, i jedan koji prima kapacitet i faktor opterećenja. -Treći i četvrti konstruktor su tu samo da dozvole korisnicima HashMap-e da kreiraju instance s vjerovatno-dobrim podrazumijevanim parametrima -za kapacitet i faktor opterećenja. - -Problematičnije je to da su podrazumijevane vrijednosti i u Javadoc-u *i* u kodu. -Održavanje ovih vrijednosti sinhronizovanim se lahko zaboravi. -Tipičan način zaobilaženja ovog problema je dodavanje javnih konstanti čije vrijednosti se pojavljuju u Javadoc: - - public class HashMap { - public static final int DEFAULT_CAPACITY = 16; - public static final float DEFAULT_LOAD_FACTOR = 0.75; - - public HashMap(Map m); - /** Kreiraj novu HashMap s podrazumijevanim kapacitetom (16) - * i loadFactor-om (0.75) - */ - public HashMap(); - /** Kreiraj novu HashMap s podrazumijevanim loadFactor-om (0.75) */ - public HashMap(int initialCapacity); - public HashMap(int initialCapacity, float loadFactor); - } - -Ovaj pristup umanjuje ponavljanje koda, ali je manje ekspresivan. - -Scala ima direktnu podršku za ovaj problem: - - class HashMap[K,V](initialCapacity:Int = 16, loadFactor:Float = 0.75f) { - } - - // Koristi podrazumijevane vrijednosti - val m1 = new HashMap[String,Int] - - // initialCapacity 20, podrazumijevani loadFactor - val m2 = new HashMap[String,Int](20) - - // prosljeđivanje obje vrijednosti - val m3 = new HashMap[String,Int](20, 0.8f) - - // prosljeđivanje samo loadFactor preko - // imenovanih parametara - val m4 = new HashMap[String,Int](loadFactor = 0.8f) - -Ovako možemo iskoristiti prednost *bilo koje* podrazumijevane vrijednosti korištenjem [imenovanih parametara]({{ site.baseurl }}/tutorials/tour/named-parameters.html). diff --git a/ba/tutorials/tour/_posts/2017-02-13-explicitly-typed-self-references.md b/ba/tutorials/tour/_posts/2017-02-13-explicitly-typed-self-references.md deleted file mode 100644 index 7ca378319e..0000000000 --- a/ba/tutorials/tour/_posts/2017-02-13-explicitly-typed-self-references.md +++ /dev/null @@ -1,127 +0,0 @@ ---- -layout: tutorial -title: Eksplicitno tipizirane samo-reference - -discourse: false - -tutorial: scala-tour -categories: tour -num: 24 -outof: 33 -language: ba - -next-page: implicit-parameters -previous-page: compound-types ---- - -Kada se razvija proširiv softver ponekad je zgodno deklarisati tip vrijednosti `this` eksplicitno. -Za motivaciju, izvešćemo malu proširivu reprezentaciju strukture grafa u Scali. - -Slijedi definicija koja opisuje grafove: - - 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 - } - -Grafovi se sastoje od liste čvorova i ivica gdje su oba tipa ostavljena apstraktnim. -Upotreba [apstraktnih tipova](abstract-types.html) dozvoljava da implementacije trejta `Graph` obezbijede svoje konkretne klase za čvorove i ivice. -Nadalje, tu je metoda `addNode` za dodavanje novih čvorova u graf. Čvorovi se povezuju metodom `connectWith`. - -Moguća implementacija klase `Graph` data je u sljedećoj klasi: - - 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` je specijalizacija klase `Graph`, predstavlja parcijalnu implementaciju. -Implementacija je parcijalna jer želimo da proširimo klasu `DirectedGraph` dalje. -Zato ova klasa ostavlja implementacijske detalje otvorenim pa su tipovi čvora i ivice ostavljeni apstraktnim. -Klasa `DirectedGraph` otkriva neke dodatne detalje o implementaciji tipa čvora ograničavanjem ga klasom `EdgeImpl`. -Nadalje, imamo neke preliminarne implementacije ivica i čvorova u klasama `EdgeImpl` i `NodeImpl`. -Pošto je potrebno kreirati nove čvorove i ivice u našoj parcijalnoj implementaciji grafa, -moramo dodati i tvorničke (factory) metode `newNode` i `newEdge`. -Metode `addNode` i `connectWith` su definisane pomoću ovih tvorničkih metoda. -Pažljiviji pogled na implementaciju metode `connectWith` otkriva da za kreiranje ivice, -moramo proslijediti samo-referencu (self-reference) `this` tvorničkoj metodi `newEdge`. -Ali, `this` ima tip `NodeImpl`, tako da nije kompatibilan s tipom `Node` kojeg zahtijeva odgovarajuća tvornička metoda. -Kao posljedica, gornji program nije dobro formiran i Scala kompajler će javiti grešku. - -U Scali je moguće vezati klasu za neki drugi tip (koji će biti implementiran kasnije) -davanjem drugog tipa samo-referenci `this`. -Ovaj mehanizam možemo iskoristiti u gornjem kodu. -Eksplicitna samo-referenca je specificirana u tijelu klase `DirectedGraph`. - -Ovo je popravljeni program: - - abstract class DirectedGraph extends Graph { - ... - class NodeImpl extends NodeIntf { - self: Node => - def connectWith(node: Node): Edge = { - val edge = newEdge(this, node) // now legal - edges = edge :: edges - edge - } - } - ... - } - -U novoj definiciji klase `NodeImpl`, `this` (self) ima tip `Node`. Pošto je tip `Node` apstraktan i još uvijek ne znamo da li je `NodeImpl` -podtip od `Node`, sistem tipova Scale nam neće dozvoliti instanciranje ove klase. -Kako god, eksplicitnom anotacijom iskazali smo da podklasa `NodeImpl` -mora navesti podtip tipa `Node` da bi se mogla instancirati. - -Ovo je konkretna specijalizacija klase `DirectedGraph` u kojoj su svi apstraktni članovi klase sada konkretni: - - 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) - } - -Primijetite da u ovoj klasi možemo instancirati `NodeImpl` jer znamo da je `NodeImpl` -podtip tipa `Node` (koja je samo pseudonim za `NodeImpl`). - -Primjer korištenja klase `ConcreteDirectedGraph`: - - 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/ba/tutorials/tour/_posts/2017-02-13-extractor-objects.md b/ba/tutorials/tour/_posts/2017-02-13-extractor-objects.md deleted file mode 100644 index fafdb160a6..0000000000 --- a/ba/tutorials/tour/_posts/2017-02-13-extractor-objects.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -layout: tutorial -title: Ekstraktor objekti - -discourse: false - -tutorial: scala-tour -categories: tour -num: 15 -outof: 33 -language: ba - -next-page: sequence-comprehensions -previous-page: regular-expression-patterns ---- - -U Scali, uzorci (patterns) mogu biti definisani nezavisno od case klasa. -Za ovo se koristi metoda `unapply` koja vraća tzv. ekstraktor. -Ekstraktor se može smatrati kao specijalna metoda koja ima obrnuti efekt od primjene (apply) nekog objekta na neke ulazne parametre. -Svrha mu je da 'ekstraktuje' ulazne parametre koji su bili prisutni prije 'apply' operacije. -Naprimjer, sljedeći kod definiše ekstraktor [objekt](singleton-objects.html) Twice. - - 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) } // prints 21 - } - -Navedene su dvije sintaksne konvencije: - -Uzorak `case Twice(n)` će pozvati `Twice.unapply`, koja se koristi da ekstraktuje bilo koji paran broj; -povratna vrijednost od `unapply` signalizira da li se argument podudario s uzorkom ili ne, -i bilo koje pod-vrijednosti mogu biti korištene za daljnje uzorkovanje (matching). -Ovdje je pod-vrijednost `z/2`. - -Metoda `apply` nije obavezna za uzorkovanje. Ona se jedino koristi da imitira konstructor. -`val x = Twice(21)` se proširuje na `val x = Twice.apply(21)`. - -Povratni tip od `unapply` se bira na sljedeći način: - -* Ako je samo test, vraća `Boolean`. Naprimjer `case even()` -* Ako vraća jednu pod-vrijednost tipa, vraća `Option[T]` -* Ako vraća više pod-vrijednosti `T1,...,Tn`, groupiše ih u opcionu torku `Option[(T1,...,Tn)]`. - -Ponekad, broj pod-vrijednosti nije fiksan i želimo da vratimo listu. -Iz ovog razloga, također možete definisati uzorke pomoću `unapplySeq`. -Zadnja pod-vrijednost tipa `Tn` mora biti `Seq[S]`. -Ovaj mehanizam se koristi naprimjer za uzorak `case List(x1, ..., xn)`. - -Ekstraktori čine kod lakšim za održavanje. -Za više detalja, pročitajte dokument -["Matching Objects with Patterns"](https://infoscience.epfl.ch/record/98468/files/MatchingObjectsWithPatterns-TR.pdf) -(u sekciji 4) od Emir, Odersky i Williams (Januar 2007). diff --git a/ba/tutorials/tour/_posts/2017-02-13-generic-classes.md b/ba/tutorials/tour/_posts/2017-02-13-generic-classes.md deleted file mode 100644 index fd97e10d79..0000000000 --- a/ba/tutorials/tour/_posts/2017-02-13-generic-classes.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -layout: tutorial -title: Generičke klase - -discourse: false - -tutorial: scala-tour -categories: tour -num: 17 -outof: 33 -language: ba - -next-page: variances -previous-page: sequence-comprehensions ---- - -Kao u Javi 5 ([JDK 1.5](http://java.sun.com/j2se/1.5/)), Scala ima ugrađenu podršku za klase parametrizovane tipovima. -Takve klase su vrlo korisne za implementiranje kolekcija. -Ovo je primjer koji to demonstrira: - - 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 imperativne (promjenjive) stekove elemenata proizvoljnog tipa `T`. -Parametri tipova obezbjeđuju sigurnost da se samo legalni elementi (samo tipa `T`) guraju na stek. -Slično, s tipskim parametrima možemo izraziti da metoda `top` vraća samo elemente zadanog tipa. - -Ovo su neki primjeri korištenja: - - object GenericsTest extends App { - val stack = new Stack[Int] - stack.push(1) - stack.push('a') - println(stack.top) - stack.pop() - println(stack.top) - } - -Izlaz ovog programa je: - - 97 - 1 - -_Napomena: nasljeđivanje generičkih tipova je *invarijantno*. -Ovo znači da ako imamo stek karaktera, koji ima tip `Stack[Char]` onda on ne može biti korišten kao stek cijelih brojeva tipa `Stack[Int]`. -Ovo bi bilo netačno (unsound) jer bi onda mogli stavljati i integere na stek karaktera. -Zaključimo, `Stack[T]` je podtip `Stack[S]` ako i samo ako je `S = T`. -Pošto ovo može biti prilično ograničavajuće, Scala ima i [anotacije tipskih parametara](variances.html) za kontrolisanje ponašanja podtipova generičkih tipova._ diff --git a/ba/tutorials/tour/_posts/2017-02-13-higher-order-functions.md b/ba/tutorials/tour/_posts/2017-02-13-higher-order-functions.md deleted file mode 100644 index 37ea367301..0000000000 --- a/ba/tutorials/tour/_posts/2017-02-13-higher-order-functions.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -layout: tutorial -title: Funkcije višeg reda - -discourse: false - -tutorial: scala-tour -categories: tour -num: 7 -outof: 33 -language: ba - -next-page: nested-functions -previous-page: anonymous-function-syntax ---- - -Scala dozvoljava definisanje funkcija višeg reda. -To su funkcije koje _primaju druge funkcije kao parametre_, ili čiji je _rezultat funkcija_. -Ovo je funkcija `apply` koja uzima drugu funkciju `f` i vrijednost `v` i primjenjuje funkciju `f` na `v`: - - def apply(f: Int => String, v: Int) = f(v) - -_Napomena: metode se automatski pretvoraju u funkcije ako kontekst zahtijeva korištenje this._ - -Ovo je još jedan primjer: - - 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)) - } - -Izvršenjem se dobije izlaz: - - [7] - -U ovom primjeru, metoda `decorator.layout` je automatski pretvorena u vrijednost tipa `Int => String` koju zahtijeva metoda `apply`. -Primijetite da je metoda `decorator.layout` _polimorfna metoda_ (tj. apstrahuje neke tipove u svom potpisu) -i Scala kompajler mora prvo instancirati tipove metode. diff --git a/ba/tutorials/tour/_posts/2017-02-13-implicit-conversions.md b/ba/tutorials/tour/_posts/2017-02-13-implicit-conversions.md deleted file mode 100644 index 14b311b48d..0000000000 --- a/ba/tutorials/tour/_posts/2017-02-13-implicit-conversions.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -layout: tutorial -title: Implicitne konverzije - -discourse: false - -tutorial: scala-tour -categories: tour -num: 26 -outof: 33 -language: ba - -next-page: polymorphic-methods -previous-page: implicit-parameters ---- - -Implicitna konverzija iz tipa `S` u tip `T` je definisana kao implicitna vrijednost koja ima tip `S => T` (funkcija), -ili kao implicitna metoda koja može pretvoriti u očekivani tip `T`. - -Implicitne konverzije se primjenjuju u dvije situacije: - -* Ako je izraz `e` tipa `S`, i `S` ne odgovara očekivanom tipu `T`. -* U selekciji `e.m` gdje je `e` tipa `T`, ako selektor `m` nije član tipa `T`. - -U prvom slučaju, traži se konverzija `c` koja je primjenjiva na `e` i čiji rezultat odgovara `T`. -U drugom slučaju, traži se konverzija `c` koja je primjenjiva na `e` i čiji rezultat sadrži član pod imenom `m`. - -Sljedeća operacija nad dvije liste xs i ys tipa `List[Int]` je legalna: - - xs <= ys - -pod pretpostavkom da su implicitne metode `list2ordered` i `int2ordered` definisane i dostupne (in scope): - - 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] { /* .. */ } - -Implicitno importovani objekt `scala.Predef` deklariše nekoliko predefinisanih tipova (npr. `Pair`) i metoda (npr. `assert`) ali i nekoliko implicitnih konverzija. - -Naprimjer, kada se pozivaju Javine metode koje očekuju `java.lang.Integer`, možete proslijediti `scala.Int`. -Možete, zato što `Predef` uključuje slj. implicitnu konverziju: - - implicit def int2Integer(x: Int) = - java.lang.Integer.valueOf(x) - -Da bi definisali vlastite implicitne konverzije, morate importovati `scala.language.implicitConversions` -(ili uključiti kompajler s flegom `-language:implicitConversions`). -Ova osobina mora biti korištena eksplicitno jer ima potencijalne zamke ako se koristi neselektivno. diff --git a/ba/tutorials/tour/_posts/2017-02-13-implicit-parameters.md b/ba/tutorials/tour/_posts/2017-02-13-implicit-parameters.md deleted file mode 100644 index fa635ec16e..0000000000 --- a/ba/tutorials/tour/_posts/2017-02-13-implicit-parameters.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -layout: tutorial -title: Implicitni parametri - -discourse: false - -tutorial: scala-tour -categories: tour -num: 25 -outof: 33 -language: ba - -next-page: implicit-conversions -previous-page: explicitly-typed-self-references ---- - -Metoda s _implicitnim parametrima_ može biti primijenjena na argumente kao i normalna metoda. -U ovom slučaju, implicitna labela nema nikakav efekt. -Međutim, ako takvoj metodi nedostaju argumenti za implicitne parametre, ti argumenti će biti proslijeđeni automatski. - -Argumenti koji se mogu proslijediti kao implicitni parametri spadaju u dvije kategorije: - -* Prva, kvalifikovani su svi identifikatori x koji su dostupni pri pozivu metode bez prefiksa i predstavljaju implicitnu definiciju ili implicitni parameter. -* Druga, kvalifikovani su također svi članovi prijateljskih objekata (modula) tipova implicitnih parametara. - -U sljedećem primjeru definisaćemo metodu `sum` koja izračunava sumu liste elemenata koristeći `add` i `unit` operacije monoida. -Molimo primijetite da implicitne vrijednosti ne mogu biti top-level, već moraju biti članovi templejta. - - abstract class SemiGroup[A] { - def add(x: A, y: A): A - } - abstract class Monoid[A] extends SemiGroup[A] { - def unit: A - } - object ImplicitTest extends App { - 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 - } - def sum[A](xs: List[A])(implicit m: Monoid[A]): A = - if (xs.isEmpty) m.unit - else m.add(xs.head, sum(xs.tail)) - - println(sum(List(1, 2, 3))) - println(sum(List("a", "b", "c"))) - } - -Ovo je izlaz navedenog Scala programa: - - 6 - abc diff --git a/ba/tutorials/tour/_posts/2017-02-13-inner-classes.md b/ba/tutorials/tour/_posts/2017-02-13-inner-classes.md deleted file mode 100644 index 58c80dc369..0000000000 --- a/ba/tutorials/tour/_posts/2017-02-13-inner-classes.md +++ /dev/null @@ -1,102 +0,0 @@ ---- -layout: tutorial -title: Unutarnje klase - -discourse: false - -tutorial: scala-tour -categories: tour -num: 21 -outof: 33 -language: ba - -next-page: abstract-types -previous-page: lower-type-bounds ---- - -U Scali je moguće da klase imaju druge klase kao članove. -Nasuprot jezicima sličnim Javi, gdje su unutarnje klase članovi vanjske klase, -u Scali takve unutarnje klase su vezane za vanjski objekt. -Radi ilustracije razlike, prikazaćemo implementaciju klase grafa: - - 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 - } - } - -U našem programu, grafovi su predstavljeni listom čvorova. -Čvorovi su objekti unutarnje klase `Node`. -Svaki čvor ima listu susjeda, koji se smještaju u listu `connectedNodes`. -Sada kad možemo kreirati graf s nekim čvorovima i povezati čvorove inkrementalno: - - 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) - } - -Sada obogaćujemo gornji primjer s tipovima s eksplicitno napisanim tipovima: - - 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) - } - -Ovaj kod jasno pokazuje da tip čvora ima prefiks instance vanjskog objekta (`g` u našem primjeru). -Ako sada imamo dva grafa, Scalin sistem tipova neće dozvoliti miješanje čvorova definisanih u različitim grafovima, -jer čvorovi različitih grafova imaju različit tip. -Ovo je primjer netačnog programa: - - object IllegalGraphTest extends App { - val g: Graph = new Graph - val n1: g.Node = g.newNode - val n2: g.Node = g.newNode - n1.connectTo(n2) // može - val h: Graph = new Graph - val n3: h.Node = h.newNode - n1.connectTo(n3) // ne može! - } - -Primijetite da bi u Javi zadnja linija prethodnog primjera bila tačna. -Za čvorove oba grafa, Java bi dodijelila isti tip `Graph.Node`; npr. `Node` bi imala prefiks klase `Graph`. -U Scali takav tip je također moguće izraziti, piše se kao `Graph#Node`. -Ako želimo povezati čvorove različitih grafova, moramo promijeniti definiciju naše inicijalne implementacije grafa: - - 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 - } - } - -> Primijetite da ovaj program ne dozvoljava da dodamo čvor u dva različita grafa. -Ako bi htjeli ukloniti i ovo ograničenje, moramo promijeniti tipski parametar `nodes` u `Graph#Node`. diff --git a/ba/tutorials/tour/_posts/2017-02-13-local-type-inference.md b/ba/tutorials/tour/_posts/2017-02-13-local-type-inference.md deleted file mode 100644 index 3199b6fb7e..0000000000 --- a/ba/tutorials/tour/_posts/2017-02-13-local-type-inference.md +++ /dev/null @@ -1,62 +0,0 @@ ---- -layout: tutorial -title: Lokalno zaključivanje tipova (type inference) - -discourse: false - -tutorial: scala-tour -categories: tour -num: 28 -outof: 33 -language: ba - -next-page: operators -previous-page: polymorphic-methods ---- -Scala ima ugrađen mehanizam zaključivanja tipova koji dozvoljava programeru da izostavi određene anotacije tipova. -Često nije potrebno specificirati tip varijable u Scali, -jer kompajler može sam zaključiti tip iz inicijalizacijskog izraza varijable. -Povratni tipovi metoda također mogu biti izostavljeni jer oni odgovaraju tipu tijela (zadnji izraz u tijelu), koje kompajler sam zaključi. - -Slijedi jedan primjer: - - object InferenceTest1 extends App { - val x = 1 + 2 * 3 // tip x-a je Int - val y = x.toString() // tip y-a je String - def succ(x: Int) = x + 1 // metoda succ vraća Int - } - -Za rekurzivne metode, kompajler nije u mogućnosti da zaključi tip rezultata. -Ovo je program koji se ne može kompajlirati iz ovog razloga: - - object InferenceTest2 { - def fac(n: Int) = if (n == 0) 1 else n * fac(n - 1) - } - -Također nije obavezno specificirati tipske parametre kada se pozivaju [polimorfne metode](polymorphic-methods.html) -ili kada se [generičke klase](generic-classes.html) instanciraju. -Scala kompajler će zaključiti nedostajuće tipske parametre iz konteksta i iz tipova stvarnih parametara metoda/konstruktora. - -Ovo je primjer koji to ilustrira: - - case class MyPair[A, B](x: A, y: B); - object InferenceTest3 extends App { - def id[T](x: T) = x - val p = MyPair(1, "scala") // tip: MyPair[Int, String] - val q = id(1) // tip: Int - } - -Zadnje dvije linije ovog programa su ekvivalentne sljedećem kodu gdje su svi zaključeni tipovi eksplicitno napisani: - - val x: MyPair[Int, String] = MyPair[Int, String](1, "scala") - val y: Int = id[Int](1) - -U nekim situacijama može biti vrlo opasno osloniti se na Scalin mehanizam zaključivanja tipova: - - object InferenceTest4 { - var obj = null - obj = new Object() - } - -Ovaj program se ne može kompajlirati jer je zaključeni tip varijable `obj` tip `Null`. -Pošto je jedina vrijednost tog tipa `null`, nemoguće je dodijeliti ovoj varijabli neku drugu vrijednost. diff --git a/ba/tutorials/tour/_posts/2017-02-13-lower-type-bounds.md b/ba/tutorials/tour/_posts/2017-02-13-lower-type-bounds.md deleted file mode 100644 index 64cfd51e4f..0000000000 --- a/ba/tutorials/tour/_posts/2017-02-13-lower-type-bounds.md +++ /dev/null @@ -1,62 +0,0 @@ ---- -layout: tutorial -title: Donja granica tipa - -discourse: false - -tutorial: scala-tour -categories: tour -num: 20 -outof: 33 -language: ba - -next-page: inner-classes -previous-page: upper-type-bounds ---- - -Dok [gornja granica tipa](upper-type-bounds.html) limitira tip na podtip nekog drugog tipa, -*donja granica tipa* limitira tip da bude nadtip nekog drugog tipa. -Izraz `T >: A` izražava tipski parametar `T` ili apstraktni tip `T` koji je nadtip tipa `A`. - -Kroz slj. primjer vidjećemo zašto je ovo korisno: - - 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) - } - -Gornji program implementira povezanu listu s operacijom nadovezivanja (na početak liste). -Nažalost, ovaj tip je invarijantan u tipskom parametru `T`, klase `ListNode`; -tj. `ListNode[String]` nije podtip `ListNode[Any]`. -Pomoću [anotacija varijansi](variances.html) možemo izraziti navedeno: - - case class ListNode[+T](h: T, t: ListNode[T]) { ... } - -Nažalost, ovaj program se ne može kompajlirati, jer anotacija za kovarijansu je jedino moguća ako se varijabla tipa koristi na kovarijantnoj poziciji (u ovom slučaju kao povratni tip). -Pošto je varijabla tipa `T` tipski parametar metode `prepend`, ovo pravilo je prekršeno. -Pomoću *donje granice tipa*, možemo implementirati operaciju nadovezivanja gdje se `T` pojavljuje samo u kovarijantnoj poziciji. - -Ovo je odgovarajući kod: - - 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) - } - -_Napomena:_ nova `prepend` metoda ima manje restriktivan tip. -Ona dozvoljava, naprimjer, da se nadoveže objekt nadtipa na postojeću listu. -Rezultujuća lista biće lista ovog nadtipa. - -Ovo je kod koji to ilustrira: - - 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/ba/tutorials/tour/_posts/2017-02-13-mixin-class-composition.md b/ba/tutorials/tour/_posts/2017-02-13-mixin-class-composition.md deleted file mode 100644 index 1ea808fe5d..0000000000 --- a/ba/tutorials/tour/_posts/2017-02-13-mixin-class-composition.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -layout: tutorial -title: Kompozicija mixin klasa - -discourse: false - -tutorial: scala-tour -categories: tour -num: 5 -outof: 33 -language: ba - -next-page: anonymous-function-syntax -previous-page: traits ---- - -Nasuprot jezicima koji podržavaju samo _jednostruko nasljeđivanje_, Scala ima generalniji pojam ponovne upotrebe klasa. -Scala omogućuje ponovno korištenje _novih definicija članova klase_ (tj. razlika u odnosu na nadklasu) u definiciji nove klase. -Ovo se izražava _kompozicijom mixin-klasa_. -Razmotrimo apstrakciju za iteratore. - - abstract class AbsIterator { - type T - def hasNext: Boolean - def next: T - } - -Dalje, razmotrimo mixin klasu koja nasljeđuje `AbsIterator` s metodom `foreach` koja primjenjuje datu funkciju na svaki element iteratora. -Da bi definisali klasu koja može biti korištena kao mixin koristimo ključnu riječ `trait` (en. osobina, svojstvo). - - trait RichIterator extends AbsIterator { - def foreach(f: T => Unit) { while (hasNext) f(next) } - } - -Ovo je konkretna klasa iteratora, koja vraća sukcesivne karaktere datog stringa: - - 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 } - } - -Željeli bismo iskombinirati funkcionalnosti `StringIterator`-a i `RichIterator`-a u jednoj klasi. -S jednostrukim nasljeđivanjem i interfejsima ovo je nemoguće, jer obje klase sadrže implementacije članova. -Scala nam pomaže s _kompozicijom mixin-klasa_. -Ona dozvoljava programerima da ponovo iskoriste razliku definicija klasa, tj., sve nove definicije koje nisu naslijeđene. -Ovaj mehanizam omogućuje kombiniranje `StringIterator`-a s `RichIterator`-om, kao u sljedećem test programu koji ispisuje kolonu svih karaktera datog stringa. - - object StringIteratorTest { - def main(args: Array[String]) { - class Iter extends StringIterator(args(0)) with RichIterator - val iter = new Iter - iter foreach println - } - } - -Klasa `Iter` u funkciji `main` je konstruisana mixin kompozicijom roditelja `StringIterator` i `RichIterator` ključnom riječju `with`. -Prvi roditelj se zove _nadklasa_ `Iter`-a, a drugi (i svaki sljedeći, ako postoji) roditelj se zove _mixin_. diff --git a/ba/tutorials/tour/_posts/2017-02-13-named-parameters.md b/ba/tutorials/tour/_posts/2017-02-13-named-parameters.md deleted file mode 100644 index 40f1263be3..0000000000 --- a/ba/tutorials/tour/_posts/2017-02-13-named-parameters.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -layout: tutorial -title: Imenovani parametri - -discourse: false - -tutorial: scala-tour -categories: tour -num: 33 -outof: 33 -language: ba - -previous-page: default-parameter-values ---- - -Kada se pozivaju metode i funkcije, možete koristiti imena varijabli eksplicitno pri pozivu: - - def printName(first: String, last: String) = { - println(first + " " + last) - } - - printName("John", "Smith") - // ispisuje "John Smith" - printName(first = "John", last = "Smith") - // ispisuje "John Smith" - printName(last = "Smith", first = "John") - // ispisuje "John Smith" - -Primijetite da kada koristite imenovane parametre pri pozivu, redoslijed nije bitan, dok god su svi parametri imenovani. -Ova sposobnost Scale radi vrlo dobro u paru sa [podrazumijevanim parametrima]({{ site.baseurl }}/tutorials/tour/default-parameter-values.html): - - def printName(first: String = "John", last: String = "Smith") = { - println(first + " " + last) - } - - printName(last = "Jones") - // ispisuje "John Jones" - -Pošto parametre možete navesti u bilo kom redoslijedu, možete koristiti podrazumijevane vrijednosti za parametre koji su zadnji u listi parametara. diff --git a/ba/tutorials/tour/_posts/2017-02-13-nested-functions.md b/ba/tutorials/tour/_posts/2017-02-13-nested-functions.md deleted file mode 100644 index 881931b0ef..0000000000 --- a/ba/tutorials/tour/_posts/2017-02-13-nested-functions.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -layout: tutorial -title: Ugniježdene funkcije - -discourse: false - -tutorial: scala-tour -categories: tour -num: 8 -outof: 33 -language: ba - -next-page: currying -previous-page: higher-order-functions ---- - -U Scali je moguće ugnježdavati definicije funkcija. -Sljedeći objekt sadrži funkciju `filter` za dobijanje vrijednosti iz liste cijelih brojeva koji su manji od vrijednosti praga (threshold): - - 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)) - } - -_Napomena: ugniježdena funkcija `process` koristi i varijablu `threshold` definisanu u vanjskom području (scope) kao parametar funkcije `filter`._ - -Izlaz ovog programa je: - - List(1,2,3,4) diff --git a/ba/tutorials/tour/_posts/2017-02-13-operators.md b/ba/tutorials/tour/_posts/2017-02-13-operators.md deleted file mode 100644 index 29cd3a8de0..0000000000 --- a/ba/tutorials/tour/_posts/2017-02-13-operators.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -layout: tutorial -title: Operatori - -discourse: false - -tutorial: scala-tour -categories: tour -num: 29 -outof: 33 -language: ba - -next-page: automatic-closures -previous-page: local-type-inference ---- - -Bilo koja metoda koja prima samo jedan parametar može biti korištena kao *infiksni operator* u Scali. -Slijedi definicija klase `MyBool` koja definiše tri metode `and`, `or`, i `negate`. - - 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 = new MyBool(!x) - } - -Sada je moguće koristiti `and` i `or` kao infiksne operatore: - - def not(x: MyBool) = x negate; // tačka-zarez je obavezna ovdje - def xor(x: MyBool, y: MyBool) = (x or y) and not(x and y) - -Prva linija navedenog koda pokazuje da je također moguće koristiti besparametarske metode kao postfiksne operatore. -Druga linija definiše funkciju `xor` koristeći `and` i `or` metode kao i novu `not` funkciju. -U ovom primjeru korištenje _infiksnih operatora_ pomaže da definiciju `xor`-a učinimo čitljivijom. - -Ovo je primjer sintakse tradicionalnih objektno orijenitsanih programskih jezika: - - def not(x: MyBool) = x.negate; // tačka-zarez je obavezna ovdje - def xor(x: MyBool, y: MyBool) = x.or(y).and(x.and(y).negate) diff --git a/ba/tutorials/tour/_posts/2017-02-13-pattern-matching.md b/ba/tutorials/tour/_posts/2017-02-13-pattern-matching.md deleted file mode 100644 index 474fad2a7e..0000000000 --- a/ba/tutorials/tour/_posts/2017-02-13-pattern-matching.md +++ /dev/null @@ -1,50 +0,0 @@ ---- -layout: tutorial -title: Podudaranje uzoraka (pattern matching) - -discourse: false - -tutorial: scala-tour -categories: tour -num: 11 - -outof: 33 -language: ba - -next-page: singleton-objects -previous-page: currying ---- - -Scala ima ugrađen mehanizam generalnog podudaranja uzoraka. -On omogućuje da se podudaraju uzorci bilo koje vrste podataka politikom "prvo podudaranje". -Slijedi mali primjer koji pokazuje kako podudarati vrijednost cijelog broja: - - object MatchTest1 extends App { - def matchTest(x: Int): String = x match { - case 1 => "one" - case 2 => "two" - case _ => "many" - } - println(matchTest(3)) - } - -Blok s `case` izrazima definiše funkciju koja mapira cijele brojeve u stringove. -Ključna riječ `match` omogućuje pogodan način za primjenu funkcije (kao pattern matching funkcija iznad) na objekt. - -Ovo je drugi primjer koja podudara vrijednost s uzorcima različitih tipova: - - 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")) - } - -Prvi `case` se podudara ako je `x` cijeli broj `1`. -Drugi `case` se podudara ako je `x` jednak stringu `"two"`. -Treći slučaj se sastoji od tipskog uzorka; podudara se sa bilo kojim integerom i povezuje vrijednost selektora `x` s varijablom `y` tipa integer. - -Scalin mehanizam podudaranja uzoraka je najkorisniji za algebarske tipove koji su izraženi kroz [case klase](case-classes.html). -Scala također dozvoljava definisanje uzoraka nezavisno od case klasa, koristeći `unapply` metode u [ekstraktor objektima](extractor-objects.html). diff --git a/ba/tutorials/tour/_posts/2017-02-13-polymorphic-methods.md b/ba/tutorials/tour/_posts/2017-02-13-polymorphic-methods.md deleted file mode 100644 index 8d22fb9bdc..0000000000 --- a/ba/tutorials/tour/_posts/2017-02-13-polymorphic-methods.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -layout: tutorial -title: Polimorfne metode - -discourse: false - -tutorial: scala-tour -categories: tour -num: 27 - -outof: 33 -language: ba - -next-page: local-type-inference -previous-page: implicit-conversions ---- - -Metode u Scali mogu biti parametrizovane i s vrijednostima i s tipovima. -Kao i na nivou klase, parameteri vrijednosti su ograđeni parom zagrada, dok su tipski parameteri deklarisani u paru uglatih zagrada. - -Slijedi primjer: - - 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` je parametrizovana tipom `T` i vrijednostima parametara `x: T` i `n: Int`. -Pri prvom pozivu `dup`, programer navodi sve zahtijevane parametre, ali kako vidimo u sljedećoj liniji, -programer ne mora eksplicitno navesti tipske parametre. -Scalin sistem tipova može zaključiti takve tipove sam. -Scalin kompajler ovo postiže gledanjem tipova vrijednosti datih parametara i konteksta u kojem je metoda pozvana. diff --git a/ba/tutorials/tour/_posts/2017-02-13-regular-expression-patterns.md b/ba/tutorials/tour/_posts/2017-02-13-regular-expression-patterns.md deleted file mode 100644 index 52c6237c84..0000000000 --- a/ba/tutorials/tour/_posts/2017-02-13-regular-expression-patterns.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -layout: tutorial -title: Regularni izrazi - -discourse: false - -tutorial: scala-tour -categories: tour -num: 14 - -outof: 33 -language: ba - -next-page: extractor-objects -previous-page: singleton-objects ---- - -## Desno-ignorišući uzorci sekvenci ## - -Desno-ignorišući uzorci su korisna opcija za dekompoziciju bilo kojeg podatka koji je ili podtip `Seq[A]` -ili case klasa s ponavljajućim formalnim parametrima (`Node*` u primjeru), naprimjer: - - Elem(prefix: String, label: String, attrs: MetaData, scp: NamespaceBinding, children: Node*) - -U takvim slučajevima, Scala dozvoljava uzorke koji imaju zvjezdicu `_*` na najdesnijoj poziciji, predstavljajući sekvencu bilo koje dužine. -Sljedeći primjer demonstrira uzorak koji se podudara s prefiksom sekvence i povezuje ostatak s varijablom `rest`. - - 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 - } - } - } - -Za razliku od prijašnjih verzija Scale, više nije dozvoljeno imati bilo kakve regularne izraze, iz razloga navedenih ispod. - -### Generalni `RegExp` uzorci privremeno povučeni iz Scale ### - -Pošto smo otkrili problem tačnosti, ova opcija je privremeno povučena iz Scala jezika. -Ako korisnici budu zahtijevali, moguće je da ćemo je ponovo aktivirati u poboljšanoj formi. - -Naše mišljenje je da uzorci za regularne izraze nisu bili toliko korisni za procesiranje XML-a kako smo mislili. -U stvarnim aplikacijama za procesiranje XML-a, XPath se čini kao bolja opcija. -Kada smo otkrili da naš prevod uzoraka regularnih izraza ima greške kod ezoteričnih uzoraka koji su neobični ali nezamjenjivi, -odlučili smo da je vrijeme da pojednostavimo jezik. diff --git a/ba/tutorials/tour/_posts/2017-02-13-sequence-comprehensions.md b/ba/tutorials/tour/_posts/2017-02-13-sequence-comprehensions.md deleted file mode 100644 index 65c7b87d1e..0000000000 --- a/ba/tutorials/tour/_posts/2017-02-13-sequence-comprehensions.md +++ /dev/null @@ -1,73 +0,0 @@ ---- -layout: tutorial -title: Komprehensije sekvenci - -discourse: false - -tutorial: scala-tour -categories: tour -num: 16 -outof: 33 -language: ba - -next-page: generic-classes -previous-page: extractor-objects ---- - -Scala ima skraćenu notaciju za pisanje *komprehensija sekvenci*. -Komprehensije imaju oblik -`for (enumeratori) yield e`, gdje su `enumeratori` lista enumeratora razdvojenih tačka-zarezima. -*Enumerator* je ili generator koji uvodi nove varijable, ili je filter. -Komprehensija evaluira tijelo `e` za svako vezivanje varijable generisano od strane enumeratora i vraća sekvencu ovih vrijednosti. - -Slijedi primjer: - - 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)) - } - -For-izraz u funkciji uvodi novu varijablu `i` tipa `Int` koja se u svakoj iteraciji vezuje za vrijednost iz liste `List(from, from + 1, ..., to - 1)`. -Čuvar (guard) `if i % 2 == 0` izbacuje sve neparne brojeve tako da se tijelo (koje se sastoji samo od izraza `i`) evaluira samo za parne brojeve. -Stoga, cijeli for-izraz vraća listu parnih brojeva. - -Program ispisuje sljedeće: - - List(0, 2, 4, 6, 8, 10, 12, 14, 16, 18) - -Slijedi komplikovaniji primjer koji izračunava sve parove brojeva između `0` i `n-1` čija je suma jednaka zadanoj vrijednosti `v`: - - object ComprehensionTest2 extends App { - def foo(n: Int, v: Int) = - for (i <- 0 until n; - j <- i until n if i + j == v) yield - Pair(i, j); - foo(20, 32) foreach { - case (i, j) => - println("(" + i + ", " + j + ")") - } - } - -Ovaj primjer pokazuje da komprehensije nisu ograničene samo na liste. -Prethodni program koristi iteratore (a ne liste). -Svaki tip podatka koji podržava operacije `withFilter`, `map`, i `flatMap` (s odgovarajućim tipovima) može biti korišten u komprehensijama. - -Ovo je izlaz programa: - - (13, 19) - (14, 18) - (15, 17) - (16, 16) - -Također postoji poseban oblik za komprehensije sekvenci koje vraćaju `Unit`. -Ovdje se vrijednosti koje se uzimaju iz liste generatora i filtera koriste za popratne pojave (side-effects). -Programer mora izostaviti ključnu riječ `yield` da bi koristio takve komprehensije. -Slijedi program koji je ekvivalentan prethodnom ali koristi specijalnu for komprehensiju koja vraća `Unit`: - - object ComprehensionTest3 extends App { - for (i <- Iterator.range(0, 20); - j <- Iterator.range(i, 20) if i + j == 32) - println("(" + i + ", " + j + ")") - } - diff --git a/ba/tutorials/tour/_posts/2017-02-13-singleton-objects.md b/ba/tutorials/tour/_posts/2017-02-13-singleton-objects.md deleted file mode 100644 index 6192120ebb..0000000000 --- a/ba/tutorials/tour/_posts/2017-02-13-singleton-objects.md +++ /dev/null @@ -1,90 +0,0 @@ ---- -layout: tutorial -title: Singlton objekti - -discourse: false - -tutorial: scala-tour -categories: tour -num: 12 - -outof: 33 -language: ba - -next-page: regular-expression-patterns -previous-page: pattern-matching ---- - -Metode i vrijednosti koje ne pripadaju individualnim instancama [klase](classes.html) pripadaju *singlton objektima*, -označenim ključnom riječju `object` umjesto `class`. - - package test - - object Blah { - def sum(l: List[Int]): Int = l.sum - } - -Metoda `sum` je dostupna globalno, i može se pozvati, ili importovati, kao `test.Blah.sum`. - -Singlton objekt je ustvari kratica za definisanje jednokratne klase, koja ne može biti direktno instancirana, -i ima `val` član `object`, s istim imenom. -Kao i `val`, singlton objekti mogu biti definisani kao članovi [trejta](traits.html) ili klase, iako je ovo netipično. - -Singlton objekt može naslijediti klase i trejtove. -Ustvari, [case klasa](case-classes.html) bez [tipskih parametara](generic-classes.html) -će podrazumijevano kreirati singlton objekt s istim imenom, -i implementiranim [`Function*`](http://www.scala-lang.org/api/current/scala/Function1.html) trejtom. - -## Kompanjoni (prijatelji) ## - -Većina singlton objekata nisu samostalni, već su povezani s istoimenom klasom. -“Singlton objekt istog imena” case klase, pomenut ranije, je jedan primjer ovoga. -U ovom slučaju, singlton objekt se zove *kompanjon objekt* klase, a klasa se zove *kompanjon klasa* objekta. - -[Scaladoc](https://wiki.scala-lang.org/display/SW/Introduction) ima posebnu podršku za prebacivanje između klase i njenog kompanjona: -ako krug s velikim “C” ili “O” ima savijenu ivicu (kao papir), možete kliknuti na krug da pređete na kompanjon. - -Klasa i njen kompanjon objekt, ako ga ima, moraju biti definisani u istom izvornom fajlu: - - 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)) - } - -Često vidimo typeclass (jedan od dizajn paterna) instance kao [implicitne vrijednosti](implicit-parameters.html), kao navedeni `ipord`, -definisane u kompanjonu. -Ovo je pogodno jer se i članovi kompanjona uključuju u implicitnu pretragu za potrebnim vrijednostima. - -## Napomene Java programerima ## - -`static` nije ključna riječ u Scali. -Umjesto nje, svi članovi koji bi u Javi bili statički, uključujući i klase, trebaju ići u neki singlton objekt. -Pristupa im se istom sintaksom, importovanim posebno ili grupno. - -Java programeri nekada definišu statičke članove privatnim kao pomoćne vrijednosti/funkcije. -Iz ovoga proizilazi čest šablon kojim se importuju svi članovi kompanjon objekta u klasu: - - class X { - import X._ - - def blah = foo - } - - object X { - private def foo = 42 - } - -U kontekstu ključne riječi `private`, klasa i njen kompanjon su prijatelji. -`object X` može pristupiti privatnim članovima od `class X`, i obrnuto. -Da bi član bio *zaista* privatan, koristite `private[this]`. - -Za pogodno korištenje u Javi, metode, uključujući `var` i `val` vrijednosti, definisane direktno u singlton objektu -također imaju statičke metode u kompanjon klasi, zvane *statički prosljeđivači*. -Drugi članovi su dostupni kroz `X$.MODULE$` statička polja za `object X`. - -Ako prebacite sve u kompanjon objekt i klasa ostane prazna koju ne želite instancirati, samo obrišite klasu. -Statički prosljeđivači će biti kreirani svakako. diff --git a/ba/tutorials/tour/_posts/2017-02-13-tour-of-scala.md b/ba/tutorials/tour/_posts/2017-02-13-tour-of-scala.md deleted file mode 100644 index 8e3fe7f319..0000000000 --- a/ba/tutorials/tour/_posts/2017-02-13-tour-of-scala.md +++ /dev/null @@ -1,79 +0,0 @@ ---- -layout: tutorial -title: Uvod - -discourse: false - -tutorial: scala-tour -categories: tour -num: 1 -outof: 33 -language: ba - -next-page: unified-types ---- - -Scala je moderan programski jezik koji spaja više paradigmi, -dizajniran da izrazi česte programske šablone kroz precizan, elegantan i tipski bezbjedan način. -Scala elegantno objedinjuje mogućnosti objektno orijentisanih i funkcionalnih jezika. - -## Scala je objektno orijentisana ## -Scala je čisto objektno orijentisan jezik u smislu da je [svaka vrijednost objekt](unified-types.html). -Tipovi i ponašanja objekata se opisuju kroz [klase](classes.html) i [trejtove](traits.html). -Klase se proširuju nasljeđivanjem i fleksibilnim mehanizmom [kompozicije mixina](mixin-class-composition.html) -kao čistom zamjenom za višestruko nasljeđivanje. - -## Scala je funkcionalna ## -Scala je također funkcionalni jezik u smislu da je [svaka funkcija vrijednost](unified-types.html). -Scala ima [lahku sintaksu](anonymous-function-syntax.html) za definisanje anonimnih funkcija, -i podržava [funkcije višeg reda](higher-order-functions.html), omogućuje [ugnježdavanje funkcija](nested-functions.html), -i podržava [curry-jevanje](currying.html). -Scaline [case klase](case-classes.html) i njen mehanizam [podudaranja uzoraka](pattern-matching.html) modeluju algebarske tipove -koji se koriste u dosta funkcionalnih programskih jezika. -[Singlton objekti](singleton-objects.html) omogućuju pogodan način za grupisanje funkcija koje nisu članovi klase. - -Nadalje, Scalin mehanizam podudaranja uzoraka (pattern-matching) prirodno podržava [procesiranje XML podataka](xml-processing.html) -pomoću [desno-ignorišućih uzoraka sekvenci](regular-expression-patterns.html), -i generalnim proširivanjem s [ekstraktor objektima](extractor-objects.html). -U ovom kontekstu, [komprehensije sekvenci](sequence-comprehensions.html) su korisne za izražavanje upita (query). -Ove mogućnosti čine Scalu idealnom za razvijanje aplikacija kao što su web servisi. - -## Scala je statički tipizirana (statically typed) ## -Scala je opremljena ekspresivnim sistemom tipova koji primorava da se apstrakcije koriste na bezbjedan i smislen način. -Konkretno, sistem tipova podržava sljedeće: - -* [generičke klase](generic-classes.html) -* [anotacije varijanse](variances.html) -* [gornje](upper-type-bounds.html) i [donje](lower-type-bounds.html) granice tipa, -* [unutarnje klase](inner-classes.html) i [apstraktne tipove](abstract-types.html) kao članove objekta -* [složene tipove](compound-types.html) -* [eksplicitno tipizirane samo-reference](explicitly-typed-self-references.html) -* implicitne [parametre](implicit-parameters.html) i [konverzije](implicit-conversions.html) -* [polimorfne metode](polymorphic-methods.html) - -Mehanizam za [lokalno zaključivanje tipova](local-type-inference.html) se brine da korisnik ne mora pisati tipove varijabli -više nego što je potrebno. -U kombinaciji, ove mogućnosti su jaka podloga za bezbjedno ponovno iskorištenje programskih apstrakcija -i za tipski bezbjedno proširenje softvera. - -## Scala je proširiva ## - -U praksi, razvijanje domenski specifičnih aplikacija često zahtijeva i domenski specifične ekstenzije jezika. -Scala omogućuje jedinstvenu kombinaciju mehanizama jezika koji olakšavaju elegantno dodavanje novih -jezičkih konstrukcija u formi biblioteka: - -* bilo koja metoda se može koristiti kao [infiksni ili postfiksni operator](operators.html) -* [closure se prave automatski zavisno od očekivanog tipa](automatic-closures.html) (ciljno tipiziranje). - -Zajedničkom upotrebom obje mogućnosti olakšava definisanje novih izraza bez proširenja sintakse samog Scala jezika i bez -korištenja olakšica u vidu macro-a ili meta-programiranja. - -Scala je dizajnirana za interoperabilnost s popularnim Java 2 Runtime Environment (JRE). -Konkretno, interakcija s popularnim objektno orijentisanim Java programskim jezikom je prirodna. -Novije mogućnosti Jave kao [anotacije](annotations.html) i Javini generički tipovi imaju direktnu analogiju u Scali. -Scaline mogućnosti bez analogija u Javi, kao što su [podrazumijevani](default-parameter-values.html) i [imenovani parametri](named-parameters.html), -se kompajliraju što približnije Javi. -Scala ima isti kompilacijski model (posebno kompajliranje, dinamičko učitavanje klasa) -kao Java i time omogućuje pristupanje hiljadama postojećih visoko kvalitetnih biblioteka. - -Molimo nastavite sa sljedećom stranicom za više informacija. diff --git a/ba/tutorials/tour/_posts/2017-02-13-traits.md b/ba/tutorials/tour/_posts/2017-02-13-traits.md deleted file mode 100644 index 1fbfd1820a..0000000000 --- a/ba/tutorials/tour/_posts/2017-02-13-traits.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -layout: tutorial -title: Trejtovi - -discourse: false - -tutorial: scala-tour -categories: tour -num: 4 -outof: 33 -language: ba - -next-page: mixin-class-composition -previous-page: classes ---- - -Slično Javinim interfejsima, trejtovi se koriste za definisanje tipova objekata navođenjem potpisa podržanih metoda. -Kao u Javi 8, Scala dozvoljava trejtovima da budu parcijalno implementirani; -tj. moguće je definisati podrazumijevane implementacije nekih metoda. -Nasuprot klasama, trejtovi ne mogu imati parametre konstruktora. -Slijedi primjer: - - trait Similarity { - def isSimilar(x: Any): Boolean - def isNotSimilar(x: Any): Boolean = !isSimilar(x) - } - -Ovaj trejt se sastoji od dvije metode, `isSimilar` i `isNotSimilar`. -Dok metoda `isSimilar` nema konkretnu implementaciju (u Java terminologiji, apstraktna je), -metoda `isNotSimilar` definiše konkretnu implementaciju. -Klase koje integrišu ovaj trejt moraju obezbijediti samo implementaciju za `isSimilar`. -Ponašanje metode `isNotSimilar` se direktno nasljeđuje iz trejta. -Trejtovi se obično integrišu u [klase](classes.html) (ili druge trejtove) [kompozicijom mixin klasa](mixin-class-composition.html): - - 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) - println(p1.isNotSimilar(p2)) - println(p1.isNotSimilar(p3)) - println(p1.isNotSimilar(2)) - } - -Ovo je izlaz programa: - - false - true - true diff --git a/ba/tutorials/tour/_posts/2017-02-13-unified-types.md b/ba/tutorials/tour/_posts/2017-02-13-unified-types.md deleted file mode 100644 index 19f8cc74ed..0000000000 --- a/ba/tutorials/tour/_posts/2017-02-13-unified-types.md +++ /dev/null @@ -1,61 +0,0 @@ ---- -layout: tutorial -title: Sjedinjeni tipovi - -discourse: false - -tutorial: scala-tour -categories: tour -num: 2 -outof: 33 -language: ba - -next-page: classes -previous-page: tour-of-scala ---- - -Nasuprot Javi, sve vrijednosti u Scali su objekti (uključujući brojeve i funkcije). -Pošto je Scala bazirana na klasama, sve vrijednosti su instance neke klase. -Dijagram ispod prikazuje hijerarhiju Scala klasa. - -![Scala Type Hierarchy]({{ site.baseurl }}/resources/images/classhierarchy.img_assist_custom.png) - -## Hijerarhija klasa u Scali ## - -Nadklasa svih klasa, `scala.Any`, ima dvije direktne podklase, `scala.AnyVal` i `scala.AnyRef`, koje predstavljaju dva različita svijeta klasa: -klase za vrijednosti i klase za reference. -Sve klase za vrijednosti su predefinisane; one odgovaraju primitivnim tipovima u jezicima kao Java. -Sve ostale klase definišu referencne tipove. -Korisnički definisane klase definišu referencne tipove po defaultu; tj. uvijek (indirektno) nasljeđuju `scala.AnyRef`. -Svaka korisnični definisana klasa u Scali implicitno nasljeđuje trejt `scala.ScalaObject`. -Klase iz infrastrukture na kojoj se izvršava Scala (tj. JRE) ne nasljeđuju `scala.ScalaObject`. -Ako se Scala koristi u kontekstu JRE, onda `scala.AnyRef` odgovara klasi `java.lang.Object`. -Primijetite da gornji dijagram također prikazuje i implicitne konverzije (isprekidana crta) između vrijednosnih (value) klasa. -Slijedi primjer koji pokazuje da su i brojevi, i karakteri, boolean vrijednosti, i funkcije samo objekti kao i svaki drugi: - - object UnifiedTypes extends App { - val set = new scala.collection.mutable.LinkedHashSet[Any] - set += "Ovo je string" // dodaj string - set += 732 // dodaj number - set += 'c' // dodaj character - set += true // dodaj boolean - set += main _ // dodaj main funkciju - val iter: Iterator[Any] = set.iterator - while (iter.hasNext) { - println(iter.next.toString()) - } - } - -Ovaj program deklariše aplikaciju `UnifiedTypes` kao top-level [singlton objekt](singleton-objects.html) koji nasljeđuje `App`. -Aplikacija definiše lokalnu varijablu `set` koja se odnosi na instancu klase `LinkedHashSet[Any]`. -Program dodaje različite elemente u ovaj skup. -Elementi moraju odgovarati deklarisanom tipu elementa skupa, `Any`. -Na kraju, ispisana je string reprezentacija svih elemenata. - -Ovo je izlaz programa: - - Ovo je string - 732 - c - true - diff --git a/ba/tutorials/tour/_posts/2017-02-13-upper-type-bounds.md b/ba/tutorials/tour/_posts/2017-02-13-upper-type-bounds.md deleted file mode 100644 index 0d50e9b690..0000000000 --- a/ba/tutorials/tour/_posts/2017-02-13-upper-type-bounds.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -layout: tutorial -title: Gornja granica tipa - -discourse: false - -tutorial: scala-tour -categories: tour -num: 19 -outof: 33 -language: ba - -next-page: lower-type-bounds -previous-page: variances ---- - -U Scali, [tipski parametri](generic-classes.html) i [apstraktni tipovi](abstract-types.html) mogu biti ograničeni granicom tipa. -Takve granice tipa ograničavaju konkretne vrijednosti tipskih varijabli i ponekad otkrivaju još informacija o članovima takvih tipova. - _Gornja granica tipa_ `T <: A` deklariše da se tipska varijabla `T` odnosi na podtip tipa `A`. -Slijedi primjer koji se oslanja na gornju granicu tipa za implementaciju polimorfne metode `findSimilar`: - - trait Similar { - def isSimilar(x: Any): Boolean - } - case class MyInt(x: Int) extends Similar { - def isSimilar(m: Any): Boolean = - m.isInstanceOf[MyInt] && - m.asInstanceOf[MyInt].x == x - } - object UpperBoundTest extends App { - def findSimilar[T <: Similar](e: T, xs: List[T]): Boolean = - if (xs.isEmpty) false - else if (e.isSimilar(xs.head)) true - else findSimilar[T](e, xs.tail) - val list: List[MyInt] = List(MyInt(1), MyInt(2), MyInt(3)) - println(findSimilar[MyInt](MyInt(4), list)) - println(findSimilar[MyInt](MyInt(2), list)) - } - -Bez gornje granice ne bi bilo moguće pozvati metodu `isSimilar` iz metode `findSimilar`. -Korištenje donje granice tipa razmotreno je [ovdje](lower-type-bounds.html). diff --git a/ba/tutorials/tour/_posts/2017-02-13-variances.md b/ba/tutorials/tour/_posts/2017-02-13-variances.md deleted file mode 100644 index 5769ed6809..0000000000 --- a/ba/tutorials/tour/_posts/2017-02-13-variances.md +++ /dev/null @@ -1,63 +0,0 @@ ---- -layout: tutorial -title: Varijanse - -discourse: false - -tutorial: scala-tour -categories: tour -num: 18 -outof: 33 -language: ba - -next-page: upper-type-bounds -previous-page: generic-classes ---- - -Scala podržava anotacije varijanse tipskih parametara [generičkih klasa](generic-classes.html). -Nasuprot Javi 5 ([JDK 1.5](http://java.sun.com/j2se/1.5/)), anotacije varijanse se dodaju pri definiciji same klase, -dok u Javi 5, anotacije varijanse se dodaju na korisničkoj strani, tj. kada se klasa koristi. - -Na stranici o [generičkim klasama](generic-classes.html) dat je primjer promjenjivog steka. -Objasnili smo da je tip definisan klasom `Stack[T]` subjekt invarijantnog nasljeđivanja u odnosu na tipski parametar. -Ovo može ograničiti ponovnu upotrebu apstrakcije klase. -Sada ćemo izvesti funkcionalnu (tj. nepromjenjivu) implementaciju steka koja nema ovo ograničenje. -Molimo primijetite da je ovo komplikovaniji primjer koji kombinuje upotrebu [polimorfnih metoda](polymorphic-methods.html), -[donjih granica tipa](lower-type-bounds.html), i kovarijantnu anotaciju tipskog parametra na netrivijalan način. -Nadalje, koristimo [unutarnje klase](inner-classes.html) da povežemo elemente steka bez eksplicitnih veza. - -```tut -class Stack[+T] { - def push[S >: T](elem: S): Stack[S] = new Stack[S] { - override def top: S = elem - override def pop: Stack[S] = Stack.this - override def toString: String = - elem.toString + " " + Stack.this.toString - } - def top: T = sys.error("no element on stack") - def pop: Stack[T] = sys.error("no element on stack") - override def toString: String = "" -} - -object VariancesTest extends App { - var s: Stack[Any] = new Stack().push("hello") - s = s.push(new Object()) - s = s.push(7) - println(s) -} -``` - -Anotacija `+T` deklariše tip `T` da bude korišten samo na kovarijantnim pozicijama. -Slično, `-T` bi deklarisalo `T` da bude korišten samo na kontravarijantnim pozicijama. -Za kovarijantne tipske parametre dobijamo kovarijantnu podtip relaciju u odnosu na ovaj parametar. -Za naš primjer to znači da je `Stack[T]` podtip od `Stack[S]` ako je `T` podtip `S`. -Suprotno važi za tipske parametre koji su obilježeni s `-`. - -Za primjer sa stekom morali bi koristiti kovarijantni tipski parametar `T` na kontravarijantnoj poziciji pri definiciji metode `push`. -Pošto želimo kovarijantno nasljeđivanje za stekove, koristimo trik kojim apstrahujemo nad tipskim parametrom metode `push`. -Dobijamo polimorfnu metodu u kojoj koristimo element tip `T` kao donju granicu tipske varijable metode `push`. -Ovo ima efekt sinhronizovanja varijanse `T` s njegovom deklaracijom kao kovarijantni tipski parametar. -Sada su stekovi kovarijantni, ali naše rješenje dozvoljava npr. da dodamo string na stek integera. -Rezultat će biti stek tipa `Stack[Any]`; -tako da samo ako je rezultat korišten u kontekstu gdje se zahtijeva stek integera, možemo otkriti grešku. -U suprotnom dobijamo stek s generalnijim tipom elemenata. diff --git a/books.md b/books.md new file mode 100644 index 0000000000..0bdfff3358 --- /dev/null +++ b/books.md @@ -0,0 +1,13 @@ +--- +title: Books +layout: inner-page +redirect_from: + - /documentation/books.html +--- + +More and more books being published about Scala every year. Here, you can find +just a small selection of the many available titles. + +
        + +{% include books.html %} diff --git a/cheatsheets/index.md b/cheatsheets/index.md deleted file mode 100644 index 6a79afa732..0000000000 --- a/cheatsheets/index.md +++ /dev/null @@ -1,85 +0,0 @@ ---- -layout: cheatsheet -title: Scalacheat -by: Brendan O'Connor -about: Thanks to Brendan O'Connor, this cheatsheet aims to be a quick reference of Scala syntactic constructions. Licensed by Brendan O'Connor under a CC-BY-SA 3.0 license. -languages: [ba, fr, ja, pl, pt-br] ---- - -###### Contributed by {{ page.by }} - -| | | -| ------ | ------ | -| variables | | -| `var x = 5` | variable | -| Good `val x = 5`
        Bad `x=6` | constant | -| `var x: Double = 5` | explicit type | -| functions | | -| Good `def f(x: Int) = { x*x }`
        Bad `def f(x: Int) { x*x }` | define function
        hidden error: without = it's a Unit-returning procedure; causes havoc | -| Good `def f(x: Any) = println(x)`
        Bad `def f(x) = println(x)` | define function
        syntax error: need types for every arg. | -| `type R = Double` | type alias | -| `def f(x: R)` vs.
        `def f(x: => R)` | call-by-value
        call-by-name (lazy parameters) | -| `(x:R) => x*x` | anonymous function | -| `(1 to 5).map(_*2)` vs.
        `(1 to 5).reduceLeft( _+_ )` | anonymous function: underscore is positionally matched arg. | -| `(1 to 5).map( x => x*x )` | anonymous function: to use an arg twice, have to name it. | -| Good `(1 to 5).map(2*)`
        Bad `(1 to 5).map(*2)` | anonymous function: bound infix method. Use `2*_` for sanity's sake instead. | -| `(1 to 5).map { x => val y=x*2; println(y); y }` | anonymous function: block style returns last expression. | -| `(1 to 5) filter {_%2 == 0} map {_*2}` | anonymous functions: pipeline style. (or parens too). | -| `def compose(g:R=>R, h:R=>R) = (x:R) => g(h(x))`
        `val f = compose({_*2}, {_-1})` | anonymous functions: to pass in multiple blocks, need outer parens. | -| `val zscore = (mean:R, sd:R) => (x:R) => (x-mean)/sd` | currying, obvious syntax. | -| `def zscore(mean:R, sd:R) = (x:R) => (x-mean)/sd` | currying, obvious syntax | -| `def zscore(mean:R, sd:R)(x:R) = (x-mean)/sd` | currying, sugar syntax. but then: | -| `val normer = zscore(7, 0.4) _` | need trailing underscore to get the partial, only for the sugar version. | -| `def mapmake[T](g:T=>T)(seq: List[T]) = seq.map(g)` | generic type. | -| `5.+(3); 5 + 3`
        `(1 to 5) map (_*2)` | infix sugar. | -| `def sum(args: Int*) = args.reduceLeft(_+_)` | varargs. | -| packages | | -| `import scala.collection._` | wildcard import. | -| `import scala.collection.Vector`
        `import scala.collection.{Vector, Sequence}` | selective import. | -| `import scala.collection.{Vector => Vec28}` | renaming import. | -| `import java.util.{Date => _, _}` | import all from java.util except Date. | -| `package pkg` _at start of file_
        `package pkg { ... }` | declare a package. | -| data structures | | -| `(1,2,3)` | tuple literal. (`Tuple3`) | -| `var (x,y,z) = (1,2,3)` | destructuring bind: tuple unpacking via pattern matching. | -| Bad`var x,y,z = (1,2,3)` | hidden error: each assigned to the entire tuple. | -| `var xs = List(1,2,3)` | list (immutable). | -| `xs(2)` | paren indexing. ([slides](http://www.slideshare.net/Odersky/fosdem-2009-1013261/27)) | -| `1 :: List(2,3)` | cons. | -| `1 to 5` _same as_ `1 until 6`
        `1 to 10 by 2` | range sugar. | -| `()` _(empty parens)_ | sole member of the Unit type (like C/Java void). | -| control constructs | | -| `if (check) happy else sad` | conditional. | -| `if (check) happy` _same as_
        `if (check) happy else ()` | conditional sugar. | -| `while (x < 5) { println(x); x += 1}` | while loop. | -| `do { println(x); x += 1} while (x < 5)` | do while loop. | -| `import scala.util.control.Breaks._`
        `breakable {`
        ` for (x <- xs) {`
        ` if (Math.random < 0.1) break`
        ` }`
        `}`| break. ([slides](http://www.slideshare.net/Odersky/fosdem-2009-1013261/21)) | -| `for (x <- xs if x%2 == 0) yield x*10` _same as_
        `xs.filter(_%2 == 0).map(_*10)` | for comprehension: filter/map | -| `for ((x,y) <- xs zip ys) yield x*y` _same as_
        `(xs zip ys) map { case (x,y) => x*y }` | for comprehension: destructuring bind | -| `for (x <- xs; y <- ys) yield x*y` _same as_
        `xs flatMap {x => ys map {y => x*y}}` | for comprehension: cross product | -| `for (x <- xs; y <- ys) {`
        `println("%d/%d = %.1f".format(x, y, x/y.toFloat))`
        `}` | for comprehension: imperative-ish
        [sprintf-style](http://java.sun.com/javase/6/docs/api/java/util/Formatter.html#syntax) | -| `for (i <- 1 to 5) {`
        `println(i)`
        `}` | for comprehension: iterate including the upper bound | -| `for (i <- 1 until 5) {`
        `println(i)`
        `}` | for comprehension: iterate omitting the upper bound | -| pattern matching | | -| Good `(xs zip ys) map { case (x,y) => x*y }`
        Bad `(xs zip ys) map( (x,y) => x*y )` | use case in function args for pattern matching. | -| Bad
        `val v42 = 42`
        `Some(3) match {`
        ` case Some(v42) => println("42")`
        ` case _ => println("Not 42")`
        `}` | "v42" is interpreted as a name matching any Int value, and "42" is printed. | -| Good
        `val v42 = 42`
        `Some(3) match {`
        `` case Some(`v42`) => println("42")``
        `case _ => println("Not 42")`
        `}` | "\`v42\`" with backticks is interpreted as the existing val `v42`, and "Not 42" is printed. | -| Good
        `val UppercaseVal = 42`
        `Some(3) match {`
        ` case Some(UppercaseVal) => println("42")`
        ` case _ => println("Not 42")`
        `}` | `UppercaseVal` is treated as an existing val, rather than a new pattern variable, because it starts with an uppercase letter. Thus, the value contained within `UppercaseVal` is checked against `3`, and "Not 42" is printed. | -| object orientation | | -| `class C(x: R)` | constructor params - `x` is only available in class body | -| `class C(val x: R)`
        `var c = new C(4)`
        `c.x` | constructor params - automatic public member defined | -| `class C(var x: R) {`
        `assert(x > 0, "positive please")`
        `var y = x`
        `val readonly = 5`
        `private var secret = 1`
        `def this = this(42)`
        `}`|
        constructor is class body
        declare a public member
        declare a gettable but not settable member
        declare a private member
        alternative constructor| -| `new{ ... }` | anonymous class | -| `abstract class D { ... }` | define an abstract class. (non-createable) | -| `class C extends D { ... }` | define an inherited class. | -| `class D(var x: R)`
        `class C(x: R) extends D(x)` | inheritance and constructor params. (wishlist: automatically pass-up params by default) -| `object O extends D { ... }` | define a singleton. (module-like) | -| `trait T { ... }`
        `class C extends T { ... }`
        `class C extends D with T { ... }` | traits.
        interfaces-with-implementation. no constructor params. [mixin-able]({{ site.baseurl }}/tutorials/tour/mixin-class-composition.html). -| `trait T1; trait T2`
        `class C extends T1 with T2`
        `class C extends D with T1 with T2` | multiple traits. | -| `class C extends D { override def f = ...}` | must declare method overrides. | -| `new java.io.File("f")` | create object. | -| Bad `new List[Int]`
        Good `List(1,2,3)` | type error: abstract type
        instead, convention: callable factory shadowing the type | -| `classOf[String]` | class literal. | -| `x.isInstanceOf[String]` | type check (runtime) | -| `x.asInstanceOf[String]` | type cast (runtime) | -| `x: String` | ascription (compile time) | diff --git a/contribute.md b/contribute.md index 9855a82316..1062cca37d 100644 --- a/contribute.md +++ b/contribute.md @@ -93,7 +93,7 @@ At the moment, `RELEVANT-CATEGORY` corresponds to only a single category, "core, If your document consists of **multiple** pages, like the [Collections]({{ site.baseurl }}/overviews/collections/introduction.html) overview, an ordering must be specified, by numbering documents in their logical order with `num`, and a name must be assigned to the collection of pages using `partof`. For example, the following header might be used for a document in the collections overview: --- - layout: overview-large + layout: overview title: YOUR TITLE partof: collections @@ -103,7 +103,7 @@ If your document consists of **multiple** pages, like the [Collections]({{ site. A **single** document in the collection must contain a tag in the header, `outof`, that indicates the total number of documents in the large overview. Putting it on the last page in the overview is often best: --- - layout: overview-large + layout: overview title: YOUR TITLE partof: collections @@ -127,7 +127,7 @@ At the moment, a tutorial that can be logically placed on **one** page must be p If you have a **multiple-page** tutorial, like in the case of multiple-page overviews, you must both specify an ordering for your document, and a name must be assigned to the collection of tutorial pages. For example, the following header is used for the [Tour of Scala]({{ site.baseurl }}/tutorials) series of tutorial articles: --- - layout: tutorial + layout: inner-page-no-masthead title: YOUR TITLE tutorial: scala-tour diff --git a/de/tutorials/scala-for-java-programmers.md b/de/tutorials/scala-for-java-programmers.md deleted file mode 100644 index 9336cd9e83..0000000000 --- a/de/tutorials/scala-for-java-programmers.md +++ /dev/null @@ -1,634 +0,0 @@ ---- -layout: overview -title: Ein Scala Tutorial für Java Programmierer -overview: scala-for-java-programmers - -discourse: false -multilingual-overview: true -language: de ---- - -Von Michel Schinz und Philipp Haller. -Deutsche Übersetzung von Christian Krause. - -## Einleitung - -Dieses Tutorial dient einer kurzen Vorstellung der Programmiersprache Scala und deren Compiler. Sie -ist für fortgeschrittene Programmierer gedacht, die sich einen Überblick darüber verschaffen wollen, -wie man mit Scala arbeitet. Grundkenntnisse in Objekt-orientierter Programmierung, insbesondere -Java, werden vorausgesetzt. - -## Das erste Beispiel - -Als erstes folgt eine Implementierung des wohlbekannten *Hallo, Welt!*-Programmes. Obwohl es sehr -einfach ist, eignet es sich sehr gut, Scalas Funktionsweise zu demonstrieren, ohne dass man viel -über die Sprache wissen muss. - - object HalloWelt { - def main(args: Array[String]) { - println("Hallo, Welt!") - } - } - -Die Struktur des Programmes sollte Java Anwendern bekannt vorkommen: es besteht aus einer Methode -namens `main`, welche die Kommandozeilenparameter als Feld (Array) von Zeichenketten (String) -übergeben bekommt. Der Körper dieser Methode besteht aus einem einzelnen Aufruf der vordefinierten -Methode `println`, die die freundliche Begrüßung als Parameter übergeben bekommt. Weiterhin hat die -`main`-Methode keinen Rückgabewert - sie ist also eine Prozedur. Daher ist es auch nicht notwendig, -einen Rückgabetyp zu spezifizieren. - -Was Java-Programmierern allerdings weniger bekannt sein sollte, ist die Deklaration `object -HalloWelt`, welche die Methode `main` enthält. Eine solche Deklaration stellt dar, was gemeinhin als -*Singleton Objekt* bekannt ist: eine Klasse mit nur einer Instanz. Im Beispiel oben werden also mit -dem Schlüsselwort `object` sowohl eine Klasse namens `HalloWelt` als auch die dazugehörige, -gleichnamige Instanz definiert. Diese Instanz wird erst bei ihrer erstmaligen Verwendung erstellt. - -Dem aufmerksamen Leser ist vielleicht aufgefallen, dass die `main`-Methode nicht als `static` -deklariert wurde. Der Grund dafür ist, dass statische Mitglieder (Attribute oder Methoden) in Scala -nicht existieren. Die Mitglieder von Singleton Objekten stellen in Scala dar, was Java und andere -Sprachen mit statischen Mitgliedern erreichen. - -### Das Beispiel kompilieren - -Um das obige Beispiel zu kompilieren, wird `scalac`, der Scala-Compiler verwendet. `scalac` arbeitet -wie die meisten anderen Compiler auch: er akzeptiert Quellcode-Dateien als Parameter, einige weitere -Optionen, und übersetzt den Quellcode in Java-Bytecode. Dieser Bytecode wird in ein oder mehrere -Java-konforme Klassen-Dateien, Dateien mit der Endung `.class`, geschrieben. - -Schreibt man den obigen Quellcode in eine Datei namens `HalloWelt.scala`, kann man diese mit dem -folgenden Befehl kompilieren (das größer-als-Zeichen `>` repräsentiert die Eingabeaufforderung und -sollte nicht mit geschrieben werden): - - > scalac HalloWelt.scala - -Damit werden einige Klassen-Dateien in das aktuelle Verzeichnis geschrieben. Eine davon heißt -`HalloWelt.class` und enthält die Klasse, die direkt mit dem Befehl `scala` ausgeführt werden kann, -was im folgenden Abschnitt erklärt wird. - -### Das Beispiel ausführen - -Sobald kompiliert, kann ein Scala-Programm mit dem Befehl `scala` ausgeführt werden. Die Anwendung -ist dem Befehl `java`, mit dem man Java-Programme ausführt, nachempfunden und akzeptiert dieselben -Optionen. Das obige Beispiel kann demnach mit folgendem Befehl ausgeführt werden, was das erwartete -Resultat ausgibt: - - > scala -classpath . HalloWelt - Hallo, Welt! - -## Interaktion mit Java - -Eine Stärke der Sprache Scala ist, dass man mit ihr sehr leicht mit Java interagieren kann. Alle -Klassen des Paketes `java.lang` stehen beispielsweise automatisch zur Verfügung, während andere -explizit importiert werden müssen. - -Als nächstes folgt ein Beispiel, was diese Interoperabilität demonstriert. Ziel ist es, das aktuelle -Datum zu erhalten und gemäß den Konventionen eines gewissen Landes zu formatieren, zum Beispiel -Frankreich. - -Javas Klassen-Bibliothek enthält viele nützliche Klassen, beispielsweise `Date` und `DateFormat`. -Dank Scala Fähigkeit, nahtlos mit Java zu interoperieren, besteht keine Notwendigkeit, äquivalente -Klassen in der Scala Klassen-Bibliothek zu implementieren - man kann einfach die entsprechenden -Klassen der Java-Pakete importieren: - - import java.util.{Date, Locale} - import java.text.DateFormat - import java.text.DateFormat._ - - object FrenchDate { - def main(args: Array[String]) { - val now = new Date - val df = getDateInstance(LONG, Locale.FRANCE) - println(df format now) - } - } - -Scala Import-Anweisung ähnelt sehr der von Java, obwohl sie viel mächtiger ist. Mehrere Klassen des -gleichen Paketes können gleichzeitig importiert werden, indem sie, wie in der ersten Zeile, in -geschweifte Klammern geschrieben werden. Ein weiterer Unterschied ist, dass, wenn man alle -Mitglieder eines Paketes importieren will, einen Unterstrich (`_`) anstelle des Asterisk (`*`) -verwendet. Der Grund dafür ist, dass der Asterisk ein gültiger Bezeichner in Scala ist, -beispielsweise als Name für Methoden, wie später gezeigt wird. Die Import-Anweisung der dritten -Zeile importiert demnach alle Mitglieder der Klasse `DateFormat`, inklusive der statischen Methode -`getDateInstance` und des statischen Feldes `LONG`. - -Innerhalb der `main`-Methode wird zuerst eine Instanz der Java-Klasse `Date` erzeugt, welche -standardmäßig das aktuelle Datum enthält. Als nächstes wird mithilfe der statischen Methode -`getDateInstance` eine Instanz der Klasse `DateFormat` erstellt. Schließlich wird das aktuelle Datum -gemäß der Regeln der lokalisierten `DateFormat`-Instanz formatiert ausgegeben. Außerdem -veranschaulicht die letzte Zeile eine interessante Fähigkeit Scalas Syntax: Methoden, die nur einen -Parameter haben, können in der Infix-Syntax notiert werden. Dies bedeutet, dass der Ausdruck - - df format now - -eine andere, weniger verbose Variante des folgenden Ausdruckes ist: - - df.format(now) - -Dies scheint nur ein nebensächlicher, syntaktischer Zucker zu sein, hat jedoch bedeutende -Konsequenzen, wie im folgenden Abschnitt gezeigt wird. - -Um diesen Abschnitt abzuschließen, soll bemerkt sein, dass es außerdem direkt in Scala möglich ist, -von Java-Klassen zu erben sowie Java-Schnittstellen zu implementieren. - -## Alles ist ein Objekt - -Scala ist eine pur Objekt-orientierte Sprache, in dem Sinne dass *alles* ein Objekt ist, Zahlen und -Funktionen eingeschlossen. Der Unterschied zu Java ist, dass Java zwischen primitiven Typen, wie -`boolean` und `int`, und den Referenz-Typen unterscheidet und es nicht erlaubt ist, Funktionen wie -Werte zu behandeln. - -### Zahlen sind Objekte - -Zahlen sind Objekte und haben daher Methoden. Tatsächlich besteht ein arithmetischer Ausdruck wie -der folgende - - 1 + 2 * 3 / x - -exklusiv aus Methoden-Aufrufen, da es äquivalent zu folgendem Ausdruck ist, wie in vorhergehenden -Abschnitt gezeigt wurde: - - (1).+(((2).*(3))./(x)) - -Dies bedeutet außerdem, dass `+`, `*`, etc. in Scala gültige Bezeichner sind. - -Die Zahlen umschließenden Klammern der zweiten Variante sind notwendig, weil Scalas lexikalischer -Scanner eine Regel zur längsten Übereinstimmung der Token verwendet. Daher würde der folgende -Ausdruck: - - 1.+(2) - -in die Token `1.`, `+`, und `2` zerlegt werden. Der Grund für diese Zerlegung ist, dass `1.` eine -längere, gültige Übereinstimmung ist, als `1`. Daher würde das Token `1.` als das Literal `1.0` -interpretiert, also als Gleitkommazahl anstatt als Ganzzahl. Den Ausdruck als - - (1).+(2) - -zu schreiben, verhindert also, dass `1.` als Gleitkommazahl interpretiert wird. - -### Funktionen sind Objekte - -Vermutlich überraschender für Java-Programmierer ist, dass auch Funktionen in Scala Objekte sind. -Daher ist es auch möglich, Funktionen als Parameter zu übergeben, als Werte zu speichern, und von -anderen Funktionen zurückgeben zu lassen. Diese Fähigkeit, Funktionen wie Werte zu behandeln, ist -einer der Grundsteine eines sehr interessanten Programmier-Paradigmas, der *funktionalen -Programmierung*. - -Ein sehr einfaches Beispiel, warum es nützlich sein kann, Funktionen wie Werte zu behandeln, ist -eine Timer-Funktion, deren Ziel es ist, eine gewisse Aktion pro Sekunde durchzuführen. Wie übergibt -man die durchzuführende Aktion? Offensichtlich als Funktion. Diese einfache Art der Übergabe einer -Funktion sollte den meisten Programmieren bekannt vorkommen: dieses Prinzip wird häufig bei -Schnittstellen für Rückruf-Funktionen (call-back) verwendet, die ausgeführt werden, wenn ein -bestimmtes Ereignis eintritt. - -Im folgenden Programm akzeptiert die Timer-Funktion `oncePerSecond` eine Rückruf-Funktion als -Parameter. Deren Typ wird `() => Unit` geschrieben und ist der Typ aller Funktionen, die keine -Parameter haben und nichts zurück geben (der Typ `Unit` ist das Äquivalent zu `void`). Die -`main`-Methode des Programmes ruft die Timer-Funktion mit der Rückruf-Funktion auf, die einen Satz -ausgibt. In anderen Worten: das Programm gibt endlos den Satz "Die Zeit vergeht wie im Flug." -einmal pro Sekunde aus. - - object Timer { - def oncePerSecond(callback: () => Unit) { - while (true) { - callback() - Thread sleep 1000 - } - } - - def timeFlies() { - println("Die Zeit vergeht wie im Flug.") - } - - def main(args: Array[String]) { - oncePerSecond(timeFlies) - } - } - -Weiterhin ist zu bemerken, dass, um die Zeichenkette auszugeben, die in Scala vordefinierte Methode -`println` statt der äquivalenten Methode in `System.out` verwendet wird. - -#### Anonyme Funktionen - -Während das obige Programm schon leicht zu verstehen ist, kann es noch verbessert werden. Als erstes -sei zu bemerken, dass die Funktion `timeFlies` nur definiert wurde, um der Funktion `oncePerSecond` -als Parameter übergeben zu werden. Dieser nur einmal verwendeten Funktion einen Namen zu geben, -scheint unnötig und es wäre angenehmer, sie direkt mit der Übergabe zu erstellen. Dies ist in Scala -mit *anonymen Funktionen* möglich, die eine Funktion ohne Namen darstellen. Die überarbeitete -Variante des obigen Timer-Programmes verwendet eine anonyme Funktion anstatt der Funktion -`timeFlies`: - - object TimerAnonymous { - def oncePerSecond(callback: () => Unit) { - while (true) { - callback() - Thread sleep 1000 - } - } - - def main(args: Array[String]) { - oncePerSecond(() => println("Die Zeit vergeht wie im Flug.")) - } - } - -Die anonyme Funktion erkennt man an dem Rechtspfeil `=>`, der die Parameter der Funktion von deren -Körper trennt. In diesem Beispiel ist die Liste der Parameter leer, wie man an den leeren Klammern -erkennen kann. Der Körper der Funktion ist derselbe, wie bei der `timeFlies` Funktion des -vorangegangenen Beispiels. - -## Klassen - -Wie weiter oben zu sehen war, ist Scala eine pur Objekt-orientierte Sprache, und als solche enthält -sie das Konzept von Klassen (der Vollständigkeit halber soll bemerkt sein, dass nicht alle -Objekt-orientierte Sprachen das Konzept von Klassen unterstützen, aber Scala ist keine von denen). -Klassen in Scala werden mit einer ähnlichen Syntax wie Java deklariert. Ein wichtiger Unterschied -ist jedoch, dass Scalas Klassen Argumente haben. Dies soll mit der folgenden Definition von -komplexen Zahlen veranschaulicht werden: - - class Complex(real: Double, imaginary: Double) { - def re() = real - def im() = imaginary - } - -Diese Klasse akzeptiert zwei Argumente, den realen und den imaginären Teil der komplexen Zahl. Sie -müssen beim Erzeugen einer Instanz der Klasse übergeben werden: - - val c = new Complex(1.5, 2.3) - -Weiterhin enthält die Klasse zwei Methoden, `re` und `im`, welche als Zugriffsfunktionen (Getter) -dienen. Außerdem soll bemerkt sein, dass der Rückgabe-Typ dieser Methoden nicht explizit deklariert -ist. Der Compiler schlussfolgert ihn automatisch, indem er ihn aus dem rechten Teil der Methoden -ableitet, dass der Rückgabewert vom Typ `Double` ist. - -Der Compiler ist nicht immer fähig, auf den Rückgabe-Typ zu schließen, und es gibt leider keine -einfache Regel, vorauszusagen, ob er dazu fähig ist oder nicht. In der Praxis stellt das -üblicherweise kein Problem dar, da der Compiler sich beschwert, wenn es ihm nicht möglich ist. -Scala-Anfänger sollten versuchen, Typ-Deklarationen, die leicht vom Kontext abzuleiten sind, -wegzulassen, um zu sehen, ob der Compiler zustimmt. Nach einer gewissen Zeit, bekommt man ein Gefühl -dafür, wann man auf diese Deklarationen verzichten kann und wann man sie explizit angeben sollte. - -### Methoden ohne Argumente - -Ein Problem der obigen Methoden `re` und `im` ist, dass man, um sie zu verwenden, ein leeres -Klammerpaar hinter ihren Namen anhängen muss: - - object ComplexNumbers { - def main(args: Array[String]) { - val c = new Complex(1.2, 3.4) - println("imaginary part: " + c.im()) - } - } - -Besser wäre es jedoch, wenn man den realen und imaginären Teil so abrufen könnte, als wären sie -Felder, also ohne das leere Klammerpaar. Mit Scala ist dies möglich, indem Methoden *ohne Argumente* -definiert werden. Solche Methoden haben keine Klammern nach ihrem Namen, weder bei ihrer Definition -noch bei ihrer Verwendung. Die Klasse für komplexe Zahlen kann demnach folgendermaßen umgeschrieben -werden: - - class Complex(real: Double, imaginary: Double) { - def re = real - def im = imaginary - } - -### Vererbung und Überschreibung - -Alle Klassen in Scala erben von einer Oberklasse. Wird keine Oberklasse angegeben, wie bei der -Klasse `Complex` des vorhergehenden Abschnittes, wird implizit `scala.AnyRef` verwendet. - -Außerdem ist es möglich, von einer Oberklasse vererbte Methoden zu überschreiben. Dabei muss jedoch -explizit das Schlüsselwort `override` angegeben werden, um versehentliche Überschreibungen zu -vermeiden. Als Beispiel soll eine Erweiterung der Klasse `Complex` dienen, die die Methode -`toString` neu definiert: - - class Complex(real: Double, imaginary: Double) { - def re = real - def im = imaginary - - override def toString() = - "" + re + (if (im < 0) "" else "+") + im + "i" - } - -## Container-Klassen und Musterabgleiche - -Eine Datenstruktur, die häufig in Programmen vorkommt, ist der Baum. Beispielsweise repräsentieren -Interpreter und Compiler Programme intern häufig als Bäume, XML-Dokumente sind Bäume und einige -Container basieren auf Bäumen, wie Rot-Schwarz-Bäume. - -Als nächstes wird anhand eines kleinen Programmes für Berechnungen gezeigt, wie solche Bäume in -Scala repräsentiert und manipuliert werden können. Das Ziel dieses Programmes ist, einfache -arithmetische Ausdrücke zu manipulieren, die aus Summen, Ganzzahlen und Variablen bestehen. -Beispiele solcher Ausdrücke sind: `1+2` und `(x+x)+(7+y)`. - -Dafür muss zuerst eine Repräsentation für die Ausdrücke gewählt werden. Die natürlichste ist ein -Baum, dessen Knoten Operationen (Additionen) und dessen Blätter Werte (Konstanten und Variablen) -darstellen. - -In Java würde man solche Bäume am ehesten mithilfe einer abstrakten Oberklasse für den Baum und -konkreten Implementierungen für Knoten und Blätter repräsentieren. In einer funktionalen Sprache -würde man algebraische Datentypen mit dem gleichen Ziel verwenden. Scala unterstützt das Konzept -einer Container-Klasse (case class), die einen gewissen Mittelweg dazwischen darstellen. Der -folgenden Quellcode veranschaulicht deren Anwendung: - - abstract class Tree - case class Sum(l: Tree, r: Tree) extends Tree - case class Var(n: String) extends Tree - case class Const(v: Int) extends Tree - -Die Tatsache, dass die Klassen `Sum`, `Var` und `Const` als Container-Klassen deklariert sind, -bedeutet, dass sie sich in einigen Gesichtspunkten von normalen Klassen unterscheiden: - -- das Schlüsselwort `new` ist nicht mehr notwendig, um Instanzen dieser Klassen zu erzeugen (man - kann also `Const(5)` anstelle von `new Const(5)` schreiben) -- Zugriffsfunktionen werden automatisch anhand der Parameter des Konstruktors erstellt (man kann - den Wert `v` einer Instanz `c` der Klasse `Const` erhalten, indem man `c.v` schreibt) -- der Compiler fügt Container-Klassen automatisch Implementierungen der Methoden `equals` und - `hashCode` hinzu, die auf der *Struktur* der Klassen basieren, anstelle deren Identität -- außerdem wird eine `toString`-Methode bereitgestellt, die einen Wert in Form der Quelle - darstellt (der String-Wert des Baum-Ausdruckes `x+1` ist `Sum(Var(x),Const(1))`) -- Instanzen dieser Klassen können mithilfe von Musterabgleichen zerlegt werden, wie weiter unten - zu sehen ist - -Da jetzt bekannt ist, wie die Datenstruktur der arithmetischen Ausdrücke repräsentiert wird, können -jetzt Operationen definiert werden, um diese zu manipulieren. Der Beginn dessen soll eine Funktion -darstellen, die Ausdrücke in einer bestimmten *Umgebung* auswertet. Das Ziel einer Umgebung ist es, -Variablen Werte zuzuweisen. Beispielsweise wird der Ausdruck `x+1` in der Umgebung, die der Variable -`x` den Wert `5` zuweist, geschrieben als `{ x -> 5 }`, mit dem Resultat `6` ausgewertet. - -Demnach muss ein Weg gefunden werden, solche Umgebungen auszudrücken. Dabei könnte man sich für eine -assoziative Datenstruktur entscheiden, wie eine Hash-Tabelle, man könnte jedoch auch direkt eine -Funktion verwenden. Eine Umgebung ist nicht mehr als eine Funktion, die Werte mit Variablen -assoziiert. Die obige Umgebung `{ x -> 5 }` wird in Scala folgendermaßen notiert: - - { case "x" => 5 } - -Diese Schreibweise definiert eine Funktion, welche bei dem String `"x"` als Argument die Ganzzahl -`5` zurückgibt, und in anderen Fällen mit einer Ausnahme fehlschlägt. - -Vor dem Schreiben der Funktionen zum Auswerten ist es sinnvoll, für die Umgebungen einen eigenen Typ -zu definieren. Man könnte zwar immer `String => Int` verwenden, es wäre jedoch besser einen -dedizierten Namen dafür zu verwenden, der das Programmieren damit einfacher macht und die Lesbarkeit -erhöht. Dies wird in Scala mit der folgenden Schreibweise erreicht: - - type Environment = String => Int - -Von hier an wird `Environment` als Alias für den Typ von Funktionen von `String` nach `Int` -verwendet. - -Nun ist alles für die Definition der Funktion zur Auswertung vorbereitet. Konzeptionell ist die -Definition sehr einfach: der Wert der Summe zweier Ausdrücke ist die Summe der Werte der einzelnen -Ausdrücke, der Wert einer Variablen wird direkt der Umgebung entnommen und der Wert einer Konstante -ist die Konstante selbst. Dies in Scala auszudrücken, ist nicht viel schwieriger: - - def eval(t: Tree, env: Environment): Int = t match { - case Sum(l, r) => eval(l, env) + eval(r, env) - case Var(n) => env(n) - case Const(v) => v - } - -Diese Funktion zum Auswerten von arithmetischen Ausdrücken nutzt einen *Musterabgleich* (pattern -matching) am Baumes `t`. Intuitiv sollte die Bedeutung der einzelnen Fälle klar sein: - -1. Als erstes wird überprüft, ob `t` eine Instanz der Klasse `Sum` ist. Falls dem so ist, wird der -linke Teilbaum der Variablen `l` und der rechte Teilbaum der Variablen `r` zugewiesen. Daraufhin -wird der Ausdruck auf der rechten Seite des Pfeiles ausgewertet, der die auf der linken Seite -gebundenen Variablen `l` und `r` verwendet. - -2. Sollte die erste Überprüfung fehlschlagen, also `t` ist keine `Sum`, wird der nächste Fall -abgehandelt und überprüft, ob `t` eine `Var` ist. Ist dies der Fall, wird analog zum ersten Fall der -Wert an `n` gebunden und der Ausdruck rechts vom Pfeil ausgewertet. - -3. Schlägt auch die zweite Überprüfung fehl, also `t` ist weder `Sum` noch `Val`, wird überprüft, -ob es eine Instanz des Typs `Const` ist. Analog wird bei einem Erfolg wie bei den beiden -vorangegangenen Fällen verfahren. - -4. Schließlich, sollten alle Überprüfungen fehlschlagen, wird eine Ausnahme ausgelöst, die -signalisiert, dass der Musterabgleich nicht erfolgreich war. Dies wird unweigerlich geschehen, -sollten neue Baum-Unterklassen erstellt werden. - -Die prinzipielle Idee eines Musterabgleiches ist, einen Wert anhand einer Reihe von Mustern -abzugleichen und, sobald ein Treffer erzielt wird, Werte zu extrahieren, mit denen darauf -weitergearbeitet werden kann. - -Erfahrene Objekt-orientierte Programmierer werden sich fragen, warum `eval` nicht als Methode der -Klasse `Tree` oder dessen Unterklassen definiert wurde. Dies wäre möglich, da Container-Klassen -Methoden definieren können, wie normale Klassen auch. Die Entscheidung, einen Musterabgleich oder -Methoden zu verwenden, ist Geschmackssache, hat jedoch wichtige Auswirkungen auf die -Erweiterbarkeit: - -- einerseits ist es mit Methoden einfach, neue Arten von Knoten als Unterklassen von `Tree` - hinzuzufügen, andererseits ist die Ergänzung einer neuen Operation zur Manipulation des Baumes - mühsam, da sie die Modifikation aller Unterklassen von `Tree` erfordert -- nutzt man einen Musterabgleich kehrt sich die Situation um: eine neue Art von Knoten erfordert - die Modifikation aller Funktionen die einen Musterabgleich am Baum vollführen, wogegen eine neue - Operation leicht hinzuzufügen ist, indem einfach eine unabhängige Funktion dafür definiert wird - -Einen weiteren Einblick in Musterabgleiche verschafft eine weitere Operation mit arithmetischen -Ausdrücken: partielle Ableitungen. Dafür gelten zur Zeit folgende Regeln: - -1. die Ableitung einer Summe ist die Summe der Ableitungen -2. die Ableitung einer Variablen ist eins, wenn sie die abzuleitende Variable ist, ansonsten `0` -3. die Ableitung einer Konstanten ist `0` - -Auch diese Regeln können fast wörtlich in Scala übersetzt werden: - - def derive(t: Tree, v: String): Tree = t match { - case Sum(l, r) => Sum(derive(l, v), derive(r, v)) - case Var(n) if (v == n) => Const(1) - case _ => Const(0) - } - -Diese Funktion führt zwei neue, mit dem Musterabgleich zusammenhängende Konzepte ein. Der zweite, -sich auf eine Variable beziehende Fall hat eine *Sperre* (guard), einen Ausdruck, der dem -Schlüsselwort `if` folgt. Diese Sperre verhindert eine Übereinstimmung, wenn der Ausdruck falsch -ist. In diesem Fall wird sie genutzt, die Konstante `1` nur zurückzugeben, wenn die Variable die -abzuleitende ist. Die zweite Neuerung ist der *Platzhalter* `_`, der mit allem übereinstimmt, jedoch -ohne einen Namen dafür zu verwenden. - -Die volle Funktionalität von Musterabgleichen wurde mit diesen Beispielen nicht demonstriert, doch -soll dies fürs Erste genügen. Eine Vorführung der beiden Funktionen an realen Beispielen steht immer -noch aus. Zu diesem Zweck soll eine `main`-Methode dienen, die den Ausdruck `(x+x)+(7+y)` als -Beispiel verwendet: zuerst wird der Wert in der Umgebung `{ x -> 5, y -> 7 }` berechnet und darauf -die beiden partiellen Ableitungen gebildet: - - def main(args: Array[String]) { - val exp: Tree = Sum(Sum(Var("x"),Var("x")),Sum(Const(7),Var("y"))) - val env: Environment = { - case "x" => 5 - case "y" => 7 - } - println("Ausdruck: " + exp) - println("Auswertung mit x=5, y=7: " + eval(exp, env)) - println("Ableitung von x:\n " + derive(exp, "x")) - println("Ableitung von y:\n " + derive(exp, "y")) - } - -Führt man das Programm aus, erhält man folgende Ausgabe: - - Ausdruck: Sum(Sum(Var(x),Var(x)),Sum(Const(7),Var(y))) - Auswertung mit x=5, y=7: 24 - Ableitung von x: - Sum(Sum(Const(1),Const(1)),Sum(Const(0),Const(0))) - Ableitung von y: - Sum(Sum(Const(0),Const(0)),Sum(Const(0),Const(1))) - -Beim Anblick dieser Ausgabe ist offensichtlich, dass man die Ergebnisse der Ableitungen noch -vereinfachen sollte. Eine solche Funktion zum Vereinfachen von Ausdrücken, die Musterabgleiche -nutzt, ist ein interessantes, aber gar nicht so einfaches Problem, was als Übung offen steht. - -## Traits - -Neben dem Vererben von Oberklassen ist es in Scala auch möglich von mehreren, sogenannten *Traits* -zu erben. Der beste Weg für einen Java-Programmierer einen Trait zu verstehen, ist sich eine -Schnittstelle vorzustellen, die Implementierungen enthält. Wenn in Scala eine Klasse von einem Trait -erbt, implementiert sie dessen Schnittstelle und erbt dessen Implementierungen. - -Um die Nützlichkeit von Traits zu demonstrieren, werden wir ein klassisches Beispiel implementieren: -Objekte mit einer natürlichen Ordnung oder Rangfolge. Es ist häufig hilfreich, Instanzen einer -Klasse untereinander vergleichen zu können, um sie beispielsweise sortieren zu können. In Java -müssen die Klassen solcher Objekte die Schnittstelle `Comparable` implementieren. In Scala kann dies -mit einer äquivalenten, aber besseren Variante von `Comparable` als Trait bewerkstelligt werden, die -im Folgenden `Ord` genannt wird. - -Wenn Objekte verglichen werden, sind sechs verschiedene Aussagen sinnvoll: kleiner, kleiner gleich, -gleich, ungleich, größer, und größer gleich. Allerdings ist es umständlich, immer alle sechs -Methoden dafür zu implementieren, vor allem in Anbetracht der Tatsache, dass vier dieser sechs durch -die verbliebenen zwei ausgedrückt werden können. Sind beispielsweise die Aussagen für gleich und -kleiner gegeben, kann man die anderen damit ausdrücken. In Scala können diese Beobachtungen mit -dem folgenden Trait zusammengefasst werden: - - trait Ord { - def < (that: Any): Boolean - def <=(that: Any): Boolean = (this < that) || (this == that) - def > (that: Any): Boolean = !(this <= that) - def >=(that: Any): Boolean = !(this < that) - } - -Diese Definition erzeugt sowohl einen neuen Typ namens `Ord`, welcher dieselbe Rolle wie Javas -Schnittstelle `Comparable` spielt, und drei vorgegebenen Funktionen, die auf einer vierten, -abstrakten basieren. Die Methoden für Gleichheit und Ungleichheit erscheinen hier nicht, da sie -bereits in allen Objekten von Scala vorhanden sind. - -Der Typ `Any`, welcher oben verwendet wurde, stellt den Ober-Typ aller Typen in Scala dar. Er kann -als noch allgemeinere Version von Javas `Object` angesehen werden, da er außerdem Ober-Typ der -Basis-Typen wie `Int` und `Float` ist. - -Um Objekte einer Klasse vergleichen zu können, ist es also hinreichend, Gleichheit und die -kleiner-als-Beziehung zu implementieren, und dieses Verhalten gewissermaßen mit der eigentlichen -Klasse zu vermengen (mix in). Als Beispiel soll eine Klasse für Datumsangaben dienen, die Daten -eines gregorianischen Kalenders repräsentiert. Solche Daten bestehen aus Tag, Monat und Jahr, welche -durch Ganzzahlen dargestellt werden: - - class Date(y: Int, m: Int, d: Int) extends Ord { - def year = y - def month = m - def day = d - - override def toString = year + "-" + month + "-" + day - -Der wichtige Teil dieser Definition ist die Deklaration `extends Ord`, welche dem Namen der Klasse -und deren Parametern folgt. Sie sagt aus, dass `Date` vom Trait `Ord` erbt. - -Nun folgt eine Re-Implementierung der Methode `equals`, die von `Object` geerbt wird, so dass die -Daten korrekt nach ihren Feldern verglichen werden. Die vorgegebene Implementierung von `equals` ist -dafür nicht nützlich, da in Java Objekte physisch, also nach deren Adressen im Speicher, verglichen -werden. Daher verwenden wir folgende Definition: - - override def equals(that: Any): Boolean = - that.isInstanceOf[Date] && { - val o = that.asInstanceOf[Date] - o.day == day && o.month == month && o.year == year - } - -Diese Methode verwendet die vordefinierten Methoden `isInstanceOf` und `asInstanceOf`. Erstere -entspricht Javas `instanceof`-Operator und gibt `true` zurück, wenn das zu testende Objekt eine -Instanz des angegebenen Typs ist. Letztere entspricht Javas Operator für Typ-Umwandlungen (cast): -ist das Objekt eine Instanz des angegebenen Typs, kann es als solcher angesehen und gehandhabt -werden, ansonsten wird eine `ClassCastException` ausgelöst. - -Schließlich kann die letzte Methode definiert werden, die für `Ord` notwendig ist, und die -kleiner-als-Beziehung implementiert. Diese nutzt eine andere, vordefinierte Methode, namens `error`, -des Paketes `sys`, welche eine `RuntimeException` mit der angegebenen Nachricht auslöst. - - def <(that: Any): Boolean = { - if (!that.isInstanceOf[Date]) - sys.error("cannot compare " + that + " and a Date") - - val o = that.asInstanceOf[Date] - (year < o.year) || - (year == o.year && (month < o.month || - (month == o.month && day < o.day))) - } - } - -Diese Methode vervollständigt die Definition der `Date`-Klasse. Instanzen dieser Klasse stellen -sowohl Daten als auch vergleichbare Objekte dar. Vielmehr implementiert diese Klasse alle sechs -Methoden, die für das Vergleichen von Objekten notwendig sind: `equals` und `<`, die direkt in der -Definition von `Date` vorkommen, sowie die anderen, in dem Trait `Ord` definierten Methoden. - -Traits sind nützlich in Situationen wie der obigen, den vollen Funktionsumfang hier zu zeigen, würde -allerdings den Rahmen dieses Dokumentes sprengen. - -## Generische Programmierung - -Eine weitere Charakteristik Scalas, die in diesem Tutorial vorgestellt werden soll, behandelt das -Konzept der generischen Programmierung. Java-Programmierer, die die Sprache noch vor der Version 1.5 -kennen, sollten mit den Problemen vertraut sein, die auftreten, wenn generische Programmierung nicht -unterstützt wird. - -Generische Programmierung bedeutet, Quellcode nach Typen zu parametrisieren. Beispielsweise stellt -sich die Frage für einen Programmierer bei der Implementierung einer Bibliothek für verkettete -Listen, welcher Typ für die Elemente verwendet werden soll. Da diese Liste in verschiedenen -Zusammenhängen verwendet werden soll, ist es nicht möglich, einen spezifischen Typ, wie `Int`, zu -verwenden. Diese willkürliche Wahl wäre sehr einschränkend. - -Aufgrund dieser Probleme griff man in Java vor der Einführung der generischen Programmierung zu dem -Mittel, `Object`, den Ober-Typ aller Typen, als Element-Typ zu verwenden. Diese Lösung ist -allerdings auch weit entfernt von Eleganz, da sie sowohl ungeeignet für die Basis-Typen, wie `int` -oder `float`, ist, als auch viele explizite Typ-Umwandlungen für den nutzenden Programmierer -bedeutet. - -Scala ermöglicht es, generische Klassen und Methoden zu definieren, um diesen Problemen aus dem Weg -zu gehen. Für die Demonstration soll ein einfacher, generischer Container als Referenz-Typ dienen, -der leer sein kann, oder auf ein Objekt des generischen Typs zeigt: - - class Reference[T] { - private var contents: T = _ - - def get: T = contents - - def set(value: T) { - contents = value - } - } - -Die Klasse `Reference` ist anhand des Types `T` parametrisiert, der den Element-Typ repräsentiert. -Dieser Typ wird im Körper der Klasse genutzt, wie bei dem Feld `contents`. Dessen Argument wird -durch die Methode `get` abgefragt und mit der Methode `set` verändert. - -Der obige Quellcode führt veränderbare Variablen in Scala ein, welche keiner weiteren Erklärung -erfordern sollten. Schon interessanter ist der initiale Wert dieser Variablen, der mit `_` -gekennzeichnet wurde. Dieser Standardwert ist für numerische Typen `0`, `false` für Wahrheitswerte, -`()` für den Typ `Unit` und `null` für alle anderen Typen. - -Um diese Referenz-Klasse zu verwenden, muss der generische Typ bei der Erzeugung einer Instanz -angegeben werden. Für einen Ganzzahl-Container soll folgendes Beispiel dienen: - - object IntegerReference { - def main(args: Array[String]) { - val cell = new Reference[Int] - cell.set(13) - println("Reference contains the half of " + (cell.get * 2)) - } - } - -Wie in dem Beispiel zu sehen ist, muss der Wert, der von der Methode `get` zurückgegeben wird, nicht -umgewandelt werden, wenn er als Ganzzahl verwendet werden soll. Es wäre außerdem nicht möglich, -einen Wert, der keine Ganzzahl ist, in einem solchen Container zu speichern, da er speziell und -ausschließlich für Ganzzahlen erzeugt worden ist. - -## Zusammenfassung - -Dieses Dokument hat einen kurzen Überblick über die Sprache Scala gegeben und dazu einige einfache -Beispiele verwendet. Interessierte Leser können beispielsweise mit dem Dokument *Scala by Example* -fortfahren, welches fortgeschrittenere Beispiele enthält, und die *Scala Language Specification* -konsultieren, sofern nötig. - diff --git a/de/tutorials/tour/_posts/2017-02-13-polymorphic-methods.md b/de/tutorials/tour/_posts/2017-02-13-polymorphic-methods.md deleted file mode 100644 index 08709e8b2a..0000000000 --- a/de/tutorials/tour/_posts/2017-02-13-polymorphic-methods.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -layout: tutorial -title: Polymorphe Methoden - -discourse: false - -tutorial: scala-tour -categories: tour -num: 21 -language: de ---- - -Methoden in Scala können sowohl in deren Parametern als auch in deren Typen parametrisiert werden. -Wie bei Klassen werden die Parameter von runden Klammern umschlossen, während Typ-Parameter in -eckigen Klammern deklariert werden. Das folgende Beispiel demonstriert dies: - - object PolyTest extends App { - 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)) - } - -Die Methode `dup` des Objektes `PolyTest` ist im Typ `T` sowie den Parametern `x: T` und `n: Int` -parametrisiert. Wenn die Methode `dup` aufgerufen wird, können Typ-Parameter einerseits explizit -angegeben werden, wie in Zeile 8, andererseits kann man sie auslassen, wie in Zeile 9, und von -Scalas Typ-System inferieren lassen. Diese inferierten Typen stammen von den Typen der übergebenen -Argumente, in obigem Beispiel der Wert `"three"` vom Typ `String`. - -Zu bemerken ist, dass der Trait `App` dafür entwickelt worden ist, kurze Testprogramme zu schreiben, -jedoch für wirklich produktiv gehenden Quellcode der Scala Versionen 2.8.x und früher vermieden -werden sollte. An dessen Stelle sollte die Methode `main` verwendet werden. - diff --git a/es/overviews/core/actors.md b/es/overviews/core/actors.md deleted file mode 100644 index 92f5d6e291..0000000000 --- a/es/overviews/core/actors.md +++ /dev/null @@ -1,497 +0,0 @@ ---- -layout: overview -title: API de actores en Scala -label-color: success -label-text: Available -language: es -overview: actors - -discourse: false ---- - -**Philipp Haller and Stephen Tu** - -**Traducción e interpretación: Miguel Ángel Pastor Olivar** - -## Introducción - -La presente guía describe el API del paquete `scala.actors` de Scala 2.8/2.9. El documento se estructura en diferentes grupos lógicos. La jerarquía de "traits" es tenida en cuenta para llevar a cabo la estructuración de las secciones individuales. La atención se centra en el comportamiento exhibido en tiempo de ejecución por varios de los métodos presentes en los traits anteriores, complementando la documentación existente en el Scaladoc API. - -## Traits de actores: Reactor, ReplyReactor, y Actor - -### The Reactor trait - -`Reactor` es el padre de todos los traits relacionados con los actores. Heredando de este trait podremos definir actores con una funcionalidad básica de envío y recepción de mensajes. - -El comportamiento de un `Reactor` se define mediante la implementación de su método `act`. Este método es ejecutado una vez el `Reactor` haya sido iniciado mediante la invocación del método `start`, retornando el `Reactor`. El método `start`es *idempotente*, lo cual significa que la invocación del mismo sobre un actor que ya ha sido iniciado no surte ningún efecto. - -El trait `Reactor` tiene un parámetro de tipo `Msg` el cual determina el tipo de mensajes que un actor es capaz de recibir. - -La invocación del método `!` de un `Reactor` envía un mensaje al receptor. La operación de envío de un mensaje mediante el operador `!` es asíncrona por lo que el actor que envía el mensaje no se bloquea esperando a que el mensaje sea recibido sino que su ejecución continua de manera inmediata. Por ejemplo, `a ! msg` envia `msg` a `a`. Todos los actores disponen de un *buzón* encargado de regular los mensajes entrantes hasta que son procesados. - -El trait `Reactor` trait también define el método `forward`. Este método es heredado de `OutputChannel` y tiene el mismo efecto que el método `!`. Aquellos traits que hereden de `Reactor`, en particular el trait `ReplyActor`, sobreescriben este método para habilitar lo que comunmente se conocen como *"implicit reply destinations"* (ver a continuación) - -Un `Reactor` recibe mensajes utilizando el método `react`. Este método espera un argumento de tipo `PartialFunction[Msg, Unit]` el cual define cómo los mensajes de tipo `Msg` son tratados una vez llegan al buzón de un actor. En el siguiente ejemplo, el actor espera recibir la cadena "Hello", para posteriomente imprimir un saludo: - - react { - case "Hello" => println("Hi there") - } - -La invocación del método `react` nunca retorna. Por tanto, cualquier código que deba ejecutarse tras la recepción de un mensaje deberá ser incluido dentro de la función parcial pasada al método `react`. Por ejemplo, dos mensajes pueden ser recibidos secuencialmente mediante la anidación de dos llamadas a `react`: - - react { - case Get(from) => - react { - case Put(x) => from ! x - } - } - -El trait `Reactor` también ofrece una serie de estructuras de control que facilitan la programación utilizando el mecanismo de `react`. - -#### Terminación y estados de ejecución - -La ejecución de un `Reactor` finaliza cuando el cuerpo del método `act` ha sido completado. Un `Reactor` también pueden terminarse a si mismo de manera explícita mediante el uso del método `exit`. El tipo de retorno de `exit` es `Nothing`, dado que `exit` siempre dispara una excepción. Esta excepción únicamente se utiliza de manera interna y nunca debería ser capturada. - -Un `Reactor` finalizado pueden ser reiniciado mediante la invocación de su método `restart`. La invocación del método anterior sobre un `Reactor` que no ha terminado su ejecución lanza una excepción de tipo `IllegalStateException`. El reinicio de un actor que ya ha terminado provoca que el método `act` se ejecute nuevamente. - -El tipo `Reactor` define el método `getState`, el cual retorna, como un miembro de la enumeración `Actor.State`, el estado actual de la ejecución del actor. Un actor que todavía no ha sido iniciado se encuentra en el estado `Actor.State.New`. Si el actor se está ejecutando pero no está esperando por ningún mensaje su estado será `Actor.State.Runnable`. En caso de que el actor haya sido suspendido mientras espera por un mensaje estará en el estado `Actor.State.Suspended`. Por último, un actor ya terminado se encontrará en el estado `Actor.State.Terminated`. - -#### Manejo de excepciones - -El miembro `exceptionHandler` permite llevar a cabo la definición de un manejador de excepciones que estará habilitado durante toda la vida del `Reactor`: - - def exceptionHandler: PartialFunction[Exception, Unit] - -Este manejador de excepciones (`exceptionHandler`) retorna una función parcial que se utiliza para gestionar excepciones que no hayan sido tratadas de ninguna otra manera. Siempre que una excepción se propague fuera del método `act` de un `Reactor` el manejador anterior será aplicado a dicha excepción, permitiendo al actor ejecutar código de limpieza antes de que se termine. Nótese que la visibilidad de `exceptionHandler` es `protected`. - -El manejo de excepciones mediante el uso de `exceptionHandler` encaja a la perfección con las estructuras de control utilizadas para programas con el método `react`. Siempre que una excepción es manejada por la función parcial retornada por `excepctionHandler`, la ejecución continua con la "closure" actual: - - loop { - react { - case Msg(data) => - if (cond) // process data - else throw new Exception("cannot process data") - } - } - -Assumiendo que `Reactor` sobreescribe el atributo `exceptionHandler`, tras el lanzamiento de una excepción en el cuerpo del método `react`, y una vez ésta ha sido gestionada, la ejecución continua con la siguiente iteración del bucle. - -### The ReplyReactor trait - -El trait `ReplyReactor` extiende `Reactor[Any]` y sobrescribe y/o añade los siguientes métodos: - -- El método `!` es sobrescrito para obtener una referencia al actor - actual (el emisor). Junto al mensaje actual, la referencia a dicho - emisor es enviada al buzón del actor receptor. Este último dispone de - acceso al emisor del mensaje mediante el uso del método `sender` (véase más abajo). - -- El método `forward` es sobrescrito para obtener una referencia al emisor - del mensaje que actualmente está siendo procesado. Junto con el mensaje - actual, esta referencia es enviada como el emisor del mensaje actual. - Como consuencia de este hecho, `forward` nos permite reenviar mensajes - en nombre de actores diferentes al actual. - -- El método (añadido) `sender` retorna el emisor del mensaje que está siendo - actualmente procesado. Puesto que un mensaje puede haber sido reenviado, - `sender` podría retornar un actor diferente al que realmente envió el mensaje. - -- El método (añadido) `reply` envía una respuesta al emisor del último mensaje. - `reply` también es utilizado para responder a mensajes síncronos o a mensajes - que han sido enviados mediante un "future" (ver más adelante). - -- El método (añadido) `!?` ofrece un *mecanismo síncrono de envío de mensajes*. - La invocación de `!?` provoca que el actor emisor del mensaje se bloquee hasta - que se recibe una respuesta, momento en el cual retorna dicha respuesta. Existen - dos variantes sobrecargadas. La versión con dos parámetros recibe un argumento - adicional que representa el tiempo de espera (medido en milisegundos) y su tipo - de retorno es `Option[Any]` en lugar de `Any`. En caso de que el emisor no - reciba una respuesta en el periodo de espera establecido, el método `!?` retornará - `None`; en otro caso retornará la respuesta recibida recubierta con `Some`. - -- Los métodos (añadidos) `!!` son similares al envío síncrono de mensajes en el sentido de - que el receptor puede enviar una respuesta al emisor del mensaje. Sin embargo, en lugar - de bloquear el actor emisor hasta que una respuesta es recibida, retornan una instancia de - `Future`. Esta última puede ser utilizada para recuperar la respuesta del receptor una - vez se encuentre disponible; asimismo puede ser utilizada para comprobar si la respuesta - está disponible sin la necesidad de bloquear el emisor. Existen dos versiones sobrecargadas. - La versión que acepta dos parámetros recibe un argumento adicional de tipo - `PartialFunction[Any, A]`. Esta función parcial es utilizada para realizar el post-procesado de - la respuesta del receptor. Básicamente, `!!` retorna un "future" que aplicará la anterior - función parcial a la repuesta (una vez recibida). El resultado del "future" es el resultado - de este post-procesado. - -- El método (añadido) `reactWithin` permite llevar a cabo la recepción de mensajes en un periodo - determinado de tiempo. En comparación con el método `react`, recibe un parámetro adicional, - `msec`, el cual representa el periodo de tiempo, expresado en milisegundos, hasta que el patrón `TIMEOUT` - es satisfecho (`TIMEOUT` es un "case object" presente en el paquete `scala.actors`). Ejemplo: - - reactWithin(2000) { - case Answer(text) => // process text - case TIMEOUT => println("no answer within 2 seconds") - } - -- El método `reactWithin` también permite realizar accesos no bloqueantes al buzón. Si - especificamos un tiempo de espera de 0 milisegundos, primeramente el buzón será escaneado - en busca de un mensaje que concuerde. En caso de que no exista ningún mensaje concordante - tras el primer escaneo, el patrón `TIMEOUT` será satisfecho. Por ejemplo, esto nos permite - recibir determinado tipo de mensajes donde unos tienen una prioridad mayor que otros: - - reactWithin(0) { - case HighPriorityMsg => // ... - case TIMEOUT => - react { - case LowPriorityMsg => // ... - } - } - - En el ejemplo anterior, el actor procesa en primer lugar los mensajes `HighPriorityMsg` aunque - exista un mensaje `LowPriorityMsg` más antiguo en el buzón. El actor sólo procesará mensajes - `LowPriorityMsg` en primer lugar en aquella situación donde no exista ningún `HighProrityMsg` - en el buzón. - -Adicionalmente, el tipo `ReplyActor` añade el estado de ejecución `Actor.State.TimedSuspended`. Un actor suspendido, esperando la recepción de un mensaje mediante el uso de `reactWithin` se encuentra en dicho estado. - -### El trait Actor - -El trait `Actor` extiende de `ReplyReactor` añadiendo y/o sobrescribiendo los siguientes miembros: - -- El método (añadido) `receive` se comporta del mismo modo que `react`, con la excepción - de que puede retornar un resultado. Este hecho se ve reflejado en la definición del tipo, - que es polimórfico en el tipo del resultado: `def receive[R](f: PartialFunction[Any, R]): R`. - Sin embargo, la utilización de `receive` hace que el uso del actor - sea más pesado, puesto que el hilo subyacente es bloqueado mientras - el actor está esperando por la respuesta. El hilo bloqueado no está - disponible para ejecutar otros actores hasta que la invocación del - método `receive` haya retornado. - -- El método (añadido) `link` permite a un actor enlazarse y desenlazarse de otro - actor respectivamente. El proceso de enlazado puede utilizarse para monitorizar - y responder a la terminación de un actor. En particular, el proceso de enlazado - afecta al comportamiento mostrado en la ejecución del método `exit` tal y como - se escribe en el la documentación del API del trait `Actor`. - -- El atributo `trapExit` permite responder a la terminación de un actor enlazado, - independientemente de los motivos de su terminación (es decir, carece de importancia - si la terminación del actor es normal o no). Si `trapExit` toma el valor cierto en - un actor, este nunca terminará por culpa de los actores enlazados. En cambio, siempre - y cuando uno de sus actores enlazados finalice, recibirá un mensaje de tipo `Exit`. - `Exit` es una "case class" que presenta dos atributos: `from` referenciando al actor - que termina y `reason` conteniendo los motivos de la terminación. - -#### Terminación y estados de ejecución - -Cuando la ejecución de un actor finaliza, el motivo de dicha terminación puede ser -establecida de manera explícita mediante la invocación de la siguiente variante -del método `exit`: - - def exit(reason: AnyRef): Nothing - -Un actor cuyo estado de terminación es diferente del símbolo `'normal` propaga -los motivos de su terminación a todos aquellos actores que se encuentren enlazados -a él. Si el motivo de la terminación es una excepción no controlada, el motivo de -finalización será una instancia de la "case class" `UncaughtException`. - -El trait `Actor` incluye dos nuevos estados de ejecución. Un actor que se encuentra -esperando la recepción de un mensaje mediante la utilización del método `receive` se -encuentra en el método `Actor.State.Blocked`. Un actor esperado la recepción de un -mensaje mediante la utilización del método `receiveWithin` se encuentra en el estado -`Actor.State.TimeBlocked`. - -## Estructuras de control - -El trait `Reactor` define una serie de estructuras de control que simplifican el mecanismo -de programación con la función sin retorno `react`. Normalmente, una invocación al método -`react` no retorna nunca. Si el actor necesita ejecutar código a continuación de la invocación -anterior, tendrá que pasar, de manera explícita, dicho código al método `react` o utilizar -algunas de las estructuras que encapsulan este comportamiento. - -La estructura de control más basica es `andThen`. Permite registrar una `closure` que será -ejecutada una vez el actor haya terminado la ejecución de todo lo demas. - - actor { - { - react { - case "hello" => // processing "hello" - }: Unit - } andThen { - println("hi there") - } - } - -Por ejemplo, el actor anterior imprime un saludo tras realizar el procesado -del mensaje `hello`. Aunque la invocación del método `react` no retorna, -podemos utilizar `andThen` para registrar el código encargado de imprimir -el saludo a continuación de la ejecución del actor. - -Nótese que existe una *atribución de tipo* a continuación de la invocación -de `react` (`:Unit`). Básicamente, nos permite tratar el resultado de -`react` como si fuese de tipo `Unit`, lo cual es legal, puesto que el resultado -de una expresión siempre se puede eliminar. Es necesario llevar a cabo esta operación -dado que `andThen` no puede ser un miembro del tipo `Unit`, que es el tipo del resultado -retornado por `react`. Tratando el tipo de resultado retornado por `react` como -`Unit` permite llevar a cabo la aplicación de una conversión implícita la cual -hace que el miembro `andThen` esté disponible. - -El API ofrece unas cuantas estructuras de control adicionales: - -- `loop { ... }`. Itera de manera indefinidia, ejecutando el código entre -las llaves en cada una de las iteraciones. La invocación de `react` en el -cuerpo del bucle provoca que el actor se comporte de manera habitual ante -la llegada de un nuevo mensaje. Posteriormente a la recepción del mensaje, -la ejecución continua con la siguiente iteración del bucle actual. - -- `loopWhile (c) { ... }`. Ejecuta el código entre las llaves mientras la -condición `c` tome el valor `true`. La invocación de `react` en el cuerpo -del bucle ocasiona el mismo efecto que en el caso de `loop`. - -- `continue`. Continua con la ejecución de la closure actual. La invocación -de `continue` en el cuerpo de un `loop`o `loopWhile` ocasionará que el actor -termine la iteración en curso y continue con la siguiente. Si la iteración en -curso ha sido registrada utilizando `andThen`, la ejecución continua con la -segunda "closure" pasada como segundo argumento a `andThen`. - -Las estructuras de control pueden ser utilizadas en cualquier parte del cuerpo -del método `act` y en los cuerpos de los métodos que, transitivamente, son -llamados por `act`. Aquellos actores creados utilizando la sintáxis `actor { ... }` -pueden importar las estructuras de control desde el objeto `Actor`. - -#### Futures - -Los traits `RepyActor` y `Actor` soportan operaciones de envío de mensajes -(métodos `!!`) que, de manera inmediata, retornan un *future*. Un *future*, -es una instancia del trait `Future` y actúa como un manejador que puede -ser utilizado para recuperar la respuesta a un mensaje "send-with-future". - -El emisor de un mensaje "send-with-future" puede esperar por la respuesta del -future *aplicando* dicha future. Por ejemplo, el envío de un mensaje mediante -`val fut = a !! msg` permite al emisor esperar por el resultado del future -del siguiente modo: `val res = fut()`. - -Adicionalmente, utilizando el método `isSet`, un `Future` puede ser consultado -de manera no bloqueante para comprobar si el resultado está disponible. - -Un mensaje "send-with-future" no es el único modo de obtener una referencia a -un future. Estos pueden ser creados utilizando el método `future`. En el siguiente -ejemplo, `body` se ejecuta de manera concurrente, retornando un future como -resultado. - - val fut = Future { body } - // ... - fut() // wait for future - -Lo que hace especial a los futures en el contexto de los actores es la posibilidad -de recuperar su resultado utilizando las operaciones estándar de actores de -recepción de mensajes como `receive`, etc. Además, es posible utilizar las operaciones -basadas en eventos `react`y `reactWithin`. Esto permite a un actor esperar por el -resultado de un future sin la necesidad de bloquear el hilo subyacente. - -Las operaciones de recepción basadas en actores están disponibles a través del -atributo `inputChannel` del future. Dado un future de tipo `Future[T]`, el tipo -de `inputChannel` es `InputChannel[T]`. Por ejemplo: - - val fut = a !! msg - // ... - fut.inputChannel.react { - case Response => // ... - } - -## Canales - -Los canales pueden ser utilizados para simplificar el manejo de mensajes -que presentan tipos diferentes pero que son enviados al mismo actor. La -jerarquía de canales se divide en `OutputChannel` e `InputChannel`. - -Los `OutputChannel` pueden ser utilizados para enviar mensajes. Un -`OutputChannel` `out` soporta las siguientes operaciones: - -- `out ! msg`. Envía el mensaje `msg` a `out` de manera asíncrona. Cuando `msg` - es enviado directamente a un actor se incluye un referencia al actor emisor - del mensaje. - -- `out forward msg`. Reenvía el mensaje `msg` a `out` de manera asíncrona. - El actor emisor se determina en el caso en el que `msg` es reenviado a - un actor. - -- `out.receiver`. Retorna el único actor que está recibiendo mensajes que están - siendo enviados al canal `out`. - -- `out.send(msg, from)`. Envía el mensaje `msg` a `out` de manera asíncrona, - proporcionando a `from` como el emisor del mensaje. - -Nótese que el trait `OutputChannel` tiene un parámetro de tipo que especifica el -tipo de los mensajes que pueden ser enviados al canal (utilizando `!`, `forward`, -y `send`). Este parámetro de tipo es contra-variante: - - trait OutputChannel[-Msg] - -Los actores pueden recibir mensajes de un `InputChannel`. Del mismo modo que -`OutputChannel`, el trait `InputChannel` presenta un parámetro de tipo que -especifica el tipo de mensajes que pueden ser recibidos por el canal. En este caso, -el parámetro de tipo es covariante: - - trait InputChannel[+Msg] - -Un `InputChannel[Msg]` `in` soportal las siguientes operaciones. - -- `in.receive { case Pat1 => ... ; case Patn => ... }` (y de manera similar, - `in.receiveWithin`) recibe un mensaje proveniente de `in`. La invocación - del método `receive` en un canal de entrada presenta la misma semántica - que la operación estándar de actores `receive`. La única diferencia es que - la función parcial pasada como argumento tiene tipo `PartialFunction[Msg, R]` - donde `R` es el tipo de retorno de `receive`. - -- `in.react { case Pat1 => ... ; case Patn => ... }` (y de manera similar, - `in.reactWithin`). Recibe un mensaje de `in` utilizando la operación basada en - eventos `react`. Del mismo modo que la operación `react` en actores, el tipo - de retorno es `Nothing`, indicando que las invocaciones de este método nunca - retornan. Al igual que la operación `receive` anterior, la función parcial - que se pasa como argumento presenta un tipo más específico: - - PartialFunction[Msg, Unit] - -### Creando y compartiendo canales - -Los canales son creados utilizando la clase concreta `Channel`. Esta clase extiende -de `InputChannel` y `OutputChannel`. Un canal pueden ser compartido haciendo dicho -canal visible en el ámbito de múltiples actores o enviándolo como mensaje. - -El siguiente ejemplo muestra la compartición mediante publicación en ámbitos: - - actor { - var out: OutputChannel[String] = null - val child = actor { - react { - case "go" => out ! "hello" - } - } - val channel = new Channel[String] - out = channel - child ! "go" - channel.receive { - case msg => println(msg.length) - } - } - -La ejecución de este ejemplo imprime la cadena "5" en la consola. Nótese que el -actor `child` únicamente tiene acceso a `out`, que es un `OutputChannel[String]`. -La referencia al canal, la cual puede ser utilizada para llevar a cabo la recepción -de mensajes, se encuentra oculta. Sin embargo, se deben tomar precauciones y -asegurarse que el canal de salida es inicializado con un canal concreto antes de que -`child` le envíe ningún mensaje. En el ejemplo que nos ocupa, esto es llevado a cabo -mediante el mensaje "go". Cuando se está recibiendo de `channel` utilizando el método -`channel.receive` podemos hacer uso del hecho que `msg` es de tipo `String`, y por -lo tanto tiene un miembro `length`. - -Una alternativa a la compartición de canales es enviarlos a través de mensajes. -El siguiente fragmento de código muestra un sencillo ejemplo de aplicación: - - case class ReplyTo(out: OutputChannel[String]) - - val child = actor { - react { - case ReplyTo(out) => out ! "hello" - } - } - - actor { - val channel = new Channel[String] - child ! ReplyTo(channel) - channel.receive { - case msg => println(msg.length) - } - } - -La "case class" `ReplyTo` es un tipo de mensajes que utilizamos para distribuir -una referencia a un `OutputChannel[String]`. Cuando el actor `child` recibe un -mensaje de tipo `ReplyTo` éste envía una cadena a su canal de salida. El segundo -actor recibe en el canal del mismo modo que anteriormente. - -## Planificadores - -Un `Reactor`(o una instancia de uno de sus subtipos) es ejecutado utilizando un -*planificador*. El trait `Reactor` incluye el miembro `scheduler` el cual retorna el -planificador utilizado para ejecutar sus instancias: - - def scheduler: IScheduler - -La plataforma de ejecución ejecuta los actores enviando tareas al planificador mediante -el uso de los métodos `execute` definidos en el trait `IScheduler`. La mayor parte -del resto de métodos definidos en este trait únicamente adquieren cierto protagonismo -cuando se necesita implementar un nuevo planificador desde cero; algo que no es necesario -en muchas ocasiones. - -Los planificadores por defecto utilizados para ejecutar instancias de `Reactor` y -`Actor` detectan cuando los actores han finalizado su ejecución. En el momento que esto -ocurre, el planificador se termina a si mismo (terminando con cualquier hilo que estuviera -en uso por parte del planificador). Sin embargo, algunos planificadores como el -`SingleThreadedScheduler` (definido en el paquete `scheduler`) necesita ser terminado de -manera explícita mediante la invocación de su método `shutdown`). - -La manera más sencilla de crear un planificador personalizado consisten en extender la clase -`SchedulerAdapter`, implementando el siguiente método abstracto: - - def execute(fun: => Unit): Unit - -Por norma general, una implementación concreata utilizaría un pool de hilos para llevar a cabo -la ejecución del argumento por nombre `fun`. - -## Actores remotos - -Esta sección describe el API de los actores remotos. Su principal interfaz es el objecto -[`RemoteActor`](http://www.scala-lang.org/api/2.9.1/scala/actors/remote/RemoteActor$.html) definido -en el paquete `scala.actors.remote`. Este objeto facilita el conjunto de métodos necesarios para crear -y establecer conexiones a instancias de actores remotos. En los fragmentos de código que se muestran a -continuación se asume que todos los miembros de `RemoteActor` han sido importados; la lista completa -de importaciones utilizadas es la siguiente: - - import scala.actors._ - import scala.actors.Actor._ - import scala.actors.remote._ - import scala.actors.remote.RemoteActor._ - -### Iniciando actores remotos - -Un actore remot es identificado de manera unívoca por un -[`Symbol`](http://www.scala-lang.org/api/2.9.1/scala/Symbol.html). Este símbolo es único para la instancia -de la máquina virual en la que se está ejecutando un actor. Un actor remoto identificado con el nombre -`myActor` puede ser creado del siguiente modo. - - class MyActor extends Actor { - def act() { - alive(9000) - register('myActor, self) - // ... - } - } - -Nótese que el nombre únicamente puede ser registrado con un único actor al mismo tiempo. -Por ejemplo, para registrar el actor *A* como `'myActor` y posteriormente registrar otro -actor *B* como `'myActor`, debería esperar hasta que *A* haya finalizado. Este requisito -aplica a lo largo de todos los puertos, por lo que registrando a *B* en un puerto diferente -no sería suficiente. - -### Connecting to remote actors - -Establecer la conexión con un actor remoto es un proceso simple. Para obtener una referencia remota -a un actor remoto que está ejecutándose en la máquina `myMachine` en el puerto 8000 con el nombre -`'anActor`, tendremos que utilizar `select`del siguiente modo: - - val myRemoteActor = select(Node("myMachine", 8000), 'anActor) - -El actor retornado por `select` es de tipo `AbstractActor`, que proporciona esencialmente el mismo -interfaz que un actor normal, y por lo tanto es compatible con las habituales operaciones de envío -de mensajes: - - myRemoteActor ! "Hello!" - receive { - case response => println("Response: " + response) - } - myRemoteActor !? "What is the meaning of life?" match { - case 42 => println("Success") - case oops => println("Failed: " + oops) - } - val future = myRemoteActor !! "What is the last digit of PI?" - -Nótese que la operación `select` es perezosa; no inicializa ninguna conexión de red. Simplemente crea -una nueva instancia de `AbstractActor` que está preparada para iniciar una nueva conexión de red en el -momento en que sea necesario (por ejemplo cuando el método '!' es invocado). diff --git a/es/overviews/core/parallel-collections.md b/es/overviews/core/parallel-collections.md deleted file mode 100644 index d731520cef..0000000000 --- a/es/overviews/core/parallel-collections.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -layout: overview -overview: parallel-collections -partof: parallel-collections -language: es -title: Las Colecciones Paralelizadas - -discourse: false ---- diff --git a/es/overviews/core/string-interpolation.md b/es/overviews/core/string-interpolation.md deleted file mode 100644 index b1a63abfd1..0000000000 --- a/es/overviews/core/string-interpolation.md +++ /dev/null @@ -1,133 +0,0 @@ ---- -layout: overview -title: Interpolación de cadenas -discourse: true -label-color: success -label-text: New in 2.10 -language: es -overview: string-interpolation - -discourse: false ---- - -**Josh Suereth** - -**Traducción e interpretación: Miguel Ángel Pastor Olivar** - -## Introducción - -Desde la versión 2.10.0, Scala ofrece un nuevo mecanismo para la creación de cadenas a partir de nuestros datos mediante la técnica de interpolación de cadenas. -Este nuevo mecanismo permite a los usuarios incluir referencias a variables de manera directa en cadenas de texto "procesadas". Por ejemplo: - - val name = "James" - println(s"Hello, $name") // Hello, James - -En el ejemplo anterior, el literal `s"Hello, $name"` es una cadena "procesada". Esto significa que el compilador debe realizar un trabajo adicional durante el tratamiento de dicha cadena. Una cadena "procesada" se denota mediante un conjunto de caracteres que preceden al símbolo `"`. La interpolación de cadenas ha sido introducida por [SIP-11](http://docs.scala-lang.org/sips/pending/string-interpolation.html), el cual contiene todos los detalles de implementación. - -## Uso - -Scala ofrece tres métodos de interpolación de manera nativa: `s`, `f` and `raw`. - -### Interpolador `s` - -El uso del prefijo `s` en cualquier cadena permite el uso de variables de manera directa dentro de la propia cadena. Ya hemos visto el ejemplo anterior: - - val name = "James" - println(s"Hello, $name") // Hello, James - -`$name` se anida dentro de la cadena "procesada" de tipo `s`. El interpolador `s` sabe como insertar el valor de la variable `name` en lugar indicado, dando como resultado la cadena `Hello, James`. Mediante el uso del interpolador `s`, cualquier nombre disponible en el ámbito puede ser utilizado dentro de la cadena. - -Las interpolaciones pueden recibir expresiones arbitrarias. Por ejemplo: - - println(s"1 + 1 = ${1 + 1}") - -imprimirá la cadena `1 + 1 = 2`. Cualquier expresión puede ser embebida en `${}` - -### Interpolador `f` - -Prefijando `f` a cualquier cadena permite llevar a cabo la creación de cadenas formateadas, del mismo modo que `printf` es utilizado en otros lenguajes. Cuando utilizamos este interpolador, todas las referencias a variables deben estar seguidas por una cadena de formateo que siga el formato `printf-`, como `%d`. Veamos un ejemplo: - - val height = 1.9d - val name = "James" - println(f"$name%s is $height%2.2f meters tall") // James is 1.90 meters tall - -El interpolador `f` es seguro respecto a tipos. Si pasamos un número real a una cadena de formateo que sólo funciona con números enteros, el compilador emitirá un error. Por ejemplo: - - val height: Double = 1.9d - - scala> f"$height%4d" - :9: error: type mismatch; - found : Double - required: Int - f"$height%4d" - ^ - -El interpolador `f` hace uso de las utilidades de formateo de cadenas disponibles en java. Los formatos permitidos tras el carácter `%` son descritos en [Formatter javadoc](http://docs.oracle.com/javase/1.6.0/docs/api/java/util/Formatter.html#detail). Si el carácter `%` no aparece tras la definición de una variable, `%s` es utilizado por defecto. - -### Interpolador `raw` - -El interpolador `raw` difiere del interpolador `s` en que el primero no realiza el escapado de literales contenidos en la cadena. A continuación se muestra un ejemplo de una cadena procesada: - - scala> s"a\nb" - res0: String = - a - b - -En el ejemplo anterior, el interpolador `s` ha reemplazado los caracteres `\n` con un salto de linea. El interpolador `raw` no llevará a cabo esta acción: - - scala> raw"a\nb" - res1: String = a\nb - -Esta cadena de interpolación es muy útil cuando se desea evitar que expresiones como `\n` se conviertan en un salto de línea. - -Adicionalmente a los interpoladores ofrecidos de serie por Scala, nosotros podremos definir nuestras propias cadenas de interpolación. - -## Uso avanzado - -En Scala, todas las cadenas "procesadas" son simples transformaciones de código. En cualquier punto en el que el compilador encuentra una cadena de texto con la forma: - - id"string content" - -la transforma en la llamada a un método (`id`) sobre una instancia de [StringContext](http://www.scala-lang.org/api/current/index.html#scala.StringContext). Este método también puede estar disponible en un ámbito implícito. Para definiir nuestra propia cadena de interpolación simplemente necesitamos crear una clase implícita que añada un nuevo método a la clase `StringContext`. A continuación se muestra un ejemplo: - - // Note: We extends AnyVal to prevent runtime instantiation. See - // value class guide for more info. - implicit class JsonHelper(val sc: StringContext) extends AnyVal { - def json(args: Any*): JSONObject = sys.error("TODO - IMPLEMENT") - } - - def giveMeSomeJson(x: JSONObject): Unit = ... - - giveMeSomeJson(json"{ name: $name, id: $id }") - -En este ejemplo, estamos intentando crear una cadena JSON mediante el uso de la interpolación de cadenas. La clase implícita `JsonHelper` debe estar disponible en el ámbito donde deseemos utilizar esta sintaxis, y el método `json` necesitaría ser implementado completamente. Sin embargo, el resutlado de dicha cadena de formateo no sería una cadena sino un objeto de tipo `JSONObject` - -Cuando el compilador encuentra la cadena `json"{ name: $name, id: $id }"` reescribe la siguiente expresión: - - new StringContext("{ name: ", ", id: ", " }").json(name, id) - -La clase implícita es utilizada para reescribir el fragmento anterior de la siguiente forma: - - new JsonHelper(new StringContext("{ name: ", ", id: ", " }")).json(name, id) - -De este modo, el método `json` tiene acceso a las diferentes partes de las cadenas así como cada una de las expresiones. Una implementación simple, y con errores, de este método podría ser: - - implicit class JsonHelper(val sc: StringContext) extends AnyVal { - def json(args: Any*): JSONObject = { - val strings = sc.parts.iterator - val expressions = args.iterator - var buf = new StringBuffer(strings.next) - while(strings.hasNext) { - buf append expressions.next - buf append strings.next - } - parseJson(buf) - } - } - -Cada una de las diferentes partes de la cadena "procesada" son expuestas en el atributo `parts` de la clase `StringContext`. Cada uno de los valores de la expresión se pasa en el argumento `args` del método `json`. Este método acepta dichos argumentos y genera una gran cadena que posteriormente convierte en un objecto de tipo JSON. Una implementación más sofisticada podría evitar la generación de la cadena anterior y llevar a cabo de manera directa la construcción del objeto JSON a partir de las cadenas y los valores de la expresión. - - -## Limitaciones - -La interpolación de cadenas no funciona con sentencias "pattern matching". Esta funcionalidad está planificada para su inclusión en la versión 2.11 de Scala. diff --git a/es/overviews/index.md b/es/overviews/index.md deleted file mode 100644 index 9ba1a79b6d..0000000000 --- a/es/overviews/index.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -layout: guides-index -language: es -title: Guías y resúmenes ---- - -
        -

        Core Lo básico ...

        -
        - -{% include localized-overview-index.txt %} - - - diff --git a/es/overviews/parallel-collections/architecture.md b/es/overviews/parallel-collections/architecture.md deleted file mode 100644 index bb1680f0b4..0000000000 --- a/es/overviews/parallel-collections/architecture.md +++ /dev/null @@ -1,116 +0,0 @@ ---- -layout: overview-large -title: Arquitectura de la librería de colecciones paralelas de Scala - -discourse: false - -partof: parallel-collections -num: 5 -language: es ---- - -Del mismo modo que la librería de colecciones secuencial, la versión paralela -ofrece un gran número de operaciones uniformes sobre un amplio abanico de -implementaciones de diversas colecciones. Siguiendo la filosofía de la versión -secuencial, se pretende evitar la duplicación de código mediante el uso de -"plantillas" de colecciones paralelas, las cuales permiten que las operaciones -sean definidas una sola vez, pudiendo ser heredadas por las diferentes implementaciones. - -El uso de este enfoque facilita de manera notable el **mantenimiento** y la **extensibilidad** -de la librería. En el caso del primero -- gracias a que cada operación se implementa una única -vez y es heredada por todas las colecciones, el mantenimiento es más sencillo y robusto; la -corrección de posibles errores se progaga hacia abajo en la jerarquía de clases en lugar de -duplicar las implementaciones. Del mismo modo, los motivos anteriores facilitan que la librería al completo sea -más sencilla de extender -- la mayor parte de las nuevas colecciones podrán heredar la mayoría de sus -operaciones. - -## Core Abstractions - -El anteriormente mencionado trait "template" implementa la mayoría de las operaciones en términos -de dos abstracciones básicas -- `Splitter`s y `Combiner`s - -### Splitters - -El trabajo de un `Splitter`, como su propio nombre indica, consiste en dividir una -colección paralela en una partición no trivial de sus elementos. La idea principal -es dividir dicha colección en partes más pequeñas hasta alcanzar un tamaño en el que -se pueda operar de manera secuencial sobre las mismas. - - trait Splitter[T] extends Iterator[T] { - def split: Seq[Splitter[T]] - } - -Curiosamente, los `Splitter` son implementados como `Iterator`s, por lo que además de -particionar, son utilizados por el framework para recorrer una colección paralela -(dado que heredan los métodos `next`y `hasNext` presentes en `Iterator`). -Este "splitting iterator" presenta una característica única: su método `split` -divide `this` (recordad que un `Splitter` es de tipo `Iterator`) en un conjunto de -`Splitter`s cada uno de los cuales recorre un subconjunto disjunto del total de -elementos presentes en la colección. Del mismo modo que un `Iterator` tradicional, -un `Splitter` es invalidado una vez su método `split` es invocado. - -Generalmente las colecciones son divididas, utilizando `Splitter`s, en subconjuntos -con un tamaño aproximadamente idéntico. En situaciones donde se necesitan un tipo de -particiones más arbitrarias, particularmente en las secuencias paralelas, se utiliza un -`PreciseSplitter`, el cual hereda de `Splitter` y define un meticuloso método de - particionado: `psplit`. - -### Combiners - -Podemos ver los `Combiner`s como una generalización de los `Builder`, provenientes -de las secuencias en Scala. Cada una de las colecciones paralelas proporciona un -`Combiner` independiente, del mismo modo que cada colección secuencial ofrece un -`Builder`. - -Mientras que en las colecciones secuenciales los elementos pueden ser añadidos a un -`Builder`, y una colección puede ser construida mediante la invocación del método -`result`, en el caso de las colecciones paralelas los `Combiner` presentan un método -llamado `combine` que acepta otro `Combiner`como argumento y retona un nuevo `Combiner`, -el cual contiene la unión de ambos. Tras la invocación del método `combine` ambos -`Combiner` son invalidados. - - trait Combiner[Elem, To] extends Builder[Elem, To] { - def combine(other: Combiner[Elem, To]): Combiner[Elem, To] - } - -Los dos parametros de tipo `Elem` y `To` presentes en el fragmento de código anterior -representan el tipo del elemento y de la colección resultante respectivamente. - -_Nota:_ Dados dos `Combiner`s, `c1` y `c2` donde `c1 eq c2` toma el valor `true` -(esto implica que son el mismo `Combiner`), la invocación de `c1.combine(c2)` -simplemente retona el `Combiner` receptor de la llamada, `c1` en el ejemplo que -nos ocupa. - -## Hierarchy - -La librería de colecciones paralelas está inspirada en gran parte en el diseño -de la librería de colecciones secuenciales -- de hecho, "replican" los correspondientes -traits presentes en el framework de colecciones secuenciales, tal y como se muestra -a continuación. - -[]({{ site.baseurl }}/resources/images/parallel-collections-hierarchy.png) - -
        Jerarquía de clases de las librerías de colecciones secuenciales y paralelas de Scala
        -
        - -El objetivo es, por supuesto, integrar tan estrechamente como sea posible las colecciones -secuenciales y paralelas, permitendo llevar a cabo una sustitución directa entre ambos -tipos de colecciones. - -Con el objetivo de tener una referencia a una colección que podría ser secuencial o -paralela (de modo que sea posible "intercambiar" la colección paralela y la secuencial -mediante la invocación de `par` y `seq` respectivamente), necesitamos un supertipo común a -los tipos de las dos colecciones. Este es el origen de los traits "generales" mostrados -anteriormente: `GenTraversable`, `GenIterable`, `GenSeq`, `GenMap` and `GenSet`, los cuales -no garantizan el orden ni el "one-at-a-time" del recorrido. Los correspondientes traits paralelos -o secuenciales heredan de los anteriores. Por ejemplo, el tipo `ParSeq`y `Seq` son subtipos -de una secuencia más general: `GenSeq`, pero no presentan un relación de herencia entre ellos. - -Para una discusión más detallada de la jerarquía de clases compartida por las colecciones secuenciales y -paralelas referirse al artículo \[[1][1]\] - -## References - -1. [On a Generic Parallel Collection Framework, Aleksandar Prokopec, Phil Bawgell, Tiark Rompf, Martin Odersky, June 2011][1] - -[1]: http://infoscience.epfl.ch/record/165523/files/techrep.pdf "flawed-benchmark" diff --git a/es/overviews/parallel-collections/concrete-parallel-collections.md b/es/overviews/parallel-collections/concrete-parallel-collections.md deleted file mode 100644 index add910840e..0000000000 --- a/es/overviews/parallel-collections/concrete-parallel-collections.md +++ /dev/null @@ -1,184 +0,0 @@ ---- -layout: overview-large -title: Clases Concretas de las Colecciones Paralelas - -discourse: false - -partof: parallel-collections -num: 2 -language: es ---- - -## Array Paralelo - -Una secuencia [ParArray](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/parallel/mutable/ParArray.html) contiene un conjunto de elementos contiguos lineales. Esto significa que los elementos pueden ser accedidos y actualizados (modificados) eficientemente al modificar la estructura subyacente (un array). El iterar sobre sus elementos es también muy eficiente por esta misma razón. Los Arrays Paralelos son como arrays en el sentido de que su tamaño es constante. - - scala> val pa = scala.collection.parallel.mutable.ParArray.tabulate(1000)(x => 2 * x + 1) - pa: scala.collection.parallel.mutable.ParArray[Int] = ParArray(1, 3, 5, 7, 9, 11, 13,... - - scala> pa reduce (_ + _) - res0: Int = 1000000 - - scala> pa map (x => (x - 1) / 2) - res1: scala.collection.parallel.mutable.ParArray[Int] = ParArray(0, 1, 2, 3, 4, 5, 6, 7,... - -Internamente, para partir el array para que sea procesado de forma paralela se utilizan [splitters]({{ site.baseurl }}/es/overviews/parallel-collections/architecture.html#core_abstractions) (o "partidores"). El Splitter parte y crea dos nuevos splitters con sus índices actualizados. A continuación son utilizados los [combiners]({{ site.baseurl }}/es/overviews/parallel-collections/architecture.html#core_abstractions) (o "combinadores"), que necesitan un poco más de trabajo. Ya que en la mayoría de los métodos transformadores (ej: `flatMap`, `filter`, `takeWhile`, etc.) previamente no es sabido la cantidad de elementos (y por ende, el tamaño del array), cada combiner es esencialmente una variante de un array buffer con un tiempo constante de la operación `+=`. Diferentes procesadores añaden elementos a combiners de arrays separados, que después son combinados al encadenar sus arrays internos. El array subyacente se crea en memoria y se rellenan sus elementos después que el número total de elementos es conocido. Por esta razón, los métodos transformadores son un poco más caros que los métodos de acceso. También, nótese que la asignación de memoria final procede secuencialmente en la JVM, lo que representa un cuello de botella si la operación de mapeo (el método transformador aplicado) es en sí económico (en términos de procesamiento). - -Al invocar el método `seq`, los arrays paralelos son convertidos al tipo de colección `ArraySeq`, que vendría a ser la contraparte secuencial del `ParArray`. Esta conversión es eficiente, y el `ArraySeq` utiliza a bajo nivel el mismo array que había sido obtenido por el array paralelo. - - -## Vector Paralelo - -Un [ParVector](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/parallel/immutable/ParVector.html) es una secuencia inmutable con un tiempo de acceso y modificación logarítimico bajo a constante. - - scala> val pv = scala.collection.parallel.immutable.ParVector.tabulate(1000)(x => x) - pv: scala.collection.parallel.immutable.ParVector[Int] = ParVector(0, 1, 2, 3, 4, 5, 6, 7, 8, 9,... - - scala> pv filter (_ % 2 == 0) - res0: scala.collection.parallel.immutable.ParVector[Int] = ParVector(0, 2, 4, 6, 8, 10, 12, 14, 16, 18,... - -Los vectores inmutables son representados por árboles, por lo que los splitters dividen pasándose subárboles entre ellos. Los combiners concurrentemente mantienen un vector de elementos y son combinados al copiar dichos elementos de forma "retardada". Es por esta razón que los métodos tranformadores son menos escalables que sus contrapartes en arrays paralelos. Una vez que las operaciones de concatenación de vectores estén disponibles en una versión futura de Scala, los combiners podrán usar dichas características y hacer más eficientes los métodos transformadores. - -Un vector paralelo es la contraparte paralela de un [Vector](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Vector.html) secuencial, por lo tanto la conversión entre estas dos estructuras se efectúa en tiempo constante. - -## Rango (Range) Paralelo - -Un [ParRange](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/parallel/immutable/ParRange.html) es una secuencia ordenada separada por intervalos iguales (ej: 1, 2, 3 o 1, 3, 5, 7). Un rango paralelo es creado de forma similar al [Rango](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Range.html) secuencial: - - scala> 1 to 3 par - res0: scala.collection.parallel.immutable.ParRange = ParRange(1, 2, 3) - - scala> 15 to 5 by -2 par - res1: scala.collection.parallel.immutable.ParRange = ParRange(15, 13, 11, 9, 7, 5) - -Tal como los rangos secuenciales no tienen constructores, los rangos paralelos no tienen [combiner]({{ site.baseurl }}/overviews/parallel-collections/architecture.html#core_abstractions)s. Mapear elementos de un rango paralelo produce un vector paralelo. Los rangos secuenciales y paralelos pueden ser convertidos de uno a otro utilizando los métodos `seq` y `par`. - - scala> (1 to 5 par) map ((x) => x * 2) - res2: scala.collection.parallel.immutable.ParSeq[Int] = ParVector(2, 4, 6, 8, 10) - - -## Tablas Hash Paralelas - -Las tablas hash paralelas almacenan sus elementos en un array subyacente y los almacenan en una posición determinada por el código hash del elemento respectivo. Las versiones mutables de los hash sets paralelos ([mutable.ParHashSet](http://www.scala-lang.org/api/{{ site.scala-version}}/scala/collection/parallel/mutable/ParHashSet.html)) y los hash maps paraleos ([mutable.ParHashMap](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/parallel/mutable/ParHashMap.html)) están basados en tablas hash. - - scala> val phs = scala.collection.parallel.mutable.ParHashSet(1 until 2000: _*) - phs: scala.collection.parallel.mutable.ParHashSet[Int] = ParHashSet(18, 327, 736, 1045, 773, 1082,... - - scala> phs map (x => x * x) - res0: scala.collection.parallel.mutable.ParHashSet[Int] = ParHashSet(2181529, 2446096, 99225, 2585664,... - -Los combiners de las tablas hash ordenan los elementos en posiciones de acuerdo a su código hash. Se combinan simplemente concatenando las estructuras que contienen dichos elementos. Una vez que la tabla hash final es construida (es decir, se invoca el método `result` del combiner), el array subyacente es rellenado y los elementos de las diferentes estructuras previas son copiados en paralelo a diferentes segmentos continuos del array de la tabla hash. - -Los "Mapas Hash" (Hash Maps) y los "Conjuntos Hash" (Hash Sets) secuenciales pueden ser convertidos a sus variantes paralelas usando el método `par`. Las tablas hash paralelas internamente necesitan de un mapa de tamaño que mantiene un registro del número de elementos en cada pedazo de la hash table. Lo que esto significa es que la primera vez que una tabla hash secuencial es convertida a una tabla hash paralela, la tabla es recorrida y el mapa de tamaño es creado - es por esta razón que la primera llamada a `par` requiere un tiempo lineal con respecto al tamaño total de la tabla. Cualquier otra modificación que le siga mantienen el estado del mapa de tamaño, por lo que conversiones sucesivas entre `par` y `seq` tienen una complejidad constante. El mantenimiento del tamaño del mapa puede ser habilitado y deshabilitado utilizando el método `useSizeMap` de la tabla hash. Es importante notar que las modificaciones en la tabla hash secuencial son visibles en la tabla hash paralela, y viceversa. - -## Hash Tries Paralelos - -Los Hash Tries paralelos son la contraparte paralela de los hash tries inmutables, que son usados para representar conjuntos y mapas inmutables de forma eficiente. Las clases involucradas son: [immutable.ParHashSet](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/parallel/immutable/ParHashSet.html) -y -[immutable.ParHashMap](http://www.scala-lang.org/api/{{ site.scala-version}}/scala/collection/parallel/immutable/ParHashMap.html). - - scala> val phs = scala.collection.parallel.immutable.ParHashSet(1 until 1000: _*) - phs: scala.collection.parallel.immutable.ParHashSet[Int] = ParSet(645, 892, 69, 809, 629, 365, 138, 760, 101, 479,... - - scala> phs map { x => x * x } sum - res0: Int = 332833500 - -De forma similar a las tablas hash paralelas, los [combiners]({{ site.baseurl }}/overviews/parallel-collections/architecture.html#core_abstractions) de los hash tries paralelos pre-ordenan los elementos en posiciones y construyen el hash trie resultante en paralelo al asignarle distintos grupos de posiciones a diferentes procesadores, los cuales contruyen los sub-tries independientemente. - -Los hash tries paralelos pueden ser convertidos hacia y desde hash tries secuenciales por medio de los métodos `seq` y `par`. - - -## Tries Paralelos Concurrentes - -Un [concurrent.TrieMap](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/concurrent/TrieMap.html) es un mapa thread-safe (seguro ante la utilización de múltiples hilos concurrentes) mientras que [mutable.ParTrieMap](http://www.scala-lang.org/api/{{ site.scala-version}}/scala/collection/parallel/mutable/ParTrieMap.html) es su contraparte paralela. Si bien la mayoría de las estructuras de datos no garantizan una iteración consistente si la estructura es modificada en medio de dicha iteración, los tries concurrentes garantizan que las actualizaciones sean solamente visibles en la próxima iteración. Esto significa que es posible mutar el trie concurrente mientras se está iterando sobre este, como en el siguiente ejemplo, que computa e imprime las raíces cuadradas de los números entre 1 y 99: - - scala> val numbers = scala.collection.parallel.mutable.ParTrieMap((1 until 100) zip (1 until 100): _*) map { case (k, v) => (k.toDouble, v.toDouble) } - numbers: scala.collection.parallel.mutable.ParTrieMap[Double,Double] = ParTrieMap(0.0 -> 0.0, 42.0 -> 42.0, 70.0 -> 70.0, 2.0 -> 2.0,... - - scala> while (numbers.nonEmpty) { - | numbers foreach { case (num, sqrt) => - | val nsqrt = 0.5 * (sqrt + num / sqrt) - | numbers(num) = nsqrt - | if (math.abs(nsqrt - sqrt) < 0.01) { - | println(num, nsqrt) - | numbers.remove(num) - | } - | } - | } - (1.0,1.0) - (2.0,1.4142156862745097) - (7.0,2.64576704419029) - (4.0,2.0000000929222947) - ... - - -Para ofrecer más detalles de lo que sucede bajo la superficie, los [Combiners]({{ site.baseurl }}/overviews/parallel-collections/architecture.html#core_abstractions) son implementados como `TrieMap`s --ya que esta es una estructura de datos concurrente, solo un combiner es construido para todo la invocación al método transformador y compartido por todos los procesadores. - -Al igual que todas las colecciones paralelas mutables, `TrieMap`s y la versión paralela, `ParTrieMap`s obtenidas mediante los métodos `seq` o `par` subyacentemente comparten la misma estructura de almacenamiento, por lo tanto modificaciones en una es visible en la otra. - - -## Características de desmpeño (performance) - -Performance de los tipo secuencia: - -| | head | tail | apply | update| prepend | append | insert | -| -------- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | -| `ParArray` | C | L | C | C | L | L | L | -| `ParVector` | eC | eC | eC | eC | eC | eC | - | -| `ParRange` | C | C | C | - | - | - | - | - -Performance para los sets y maps: - -| | lookup | add | remove | -| -------- | ---- | ---- | ---- | -| **inmutables** | | | | -| `ParHashSet`/`ParHashMap`| eC | eC | eC | -| **mutables** | | | | -| `ParHashSet`/`ParHashMap`| C | C | C | -| `ParTrieMap` | eC | eC | eC | - - -Los valores en las tablas de arriba se explican de la siguiente manera: - -| | | -| --- | ---- | -| **C** | La operación toma un tiempo constante (rápido) | -| **eC** | La opración toma tiempo efectivamente constante, pero esto puede depender de algunas suposiciones como el tamaño máximo de un vector o la distribución de las claves hash. | -| **aC** | La operación requiere un tiempo constante amortizado. Algunas invocaciones de la operación pueden tomar un poco más de tiempo, pero si en promedio muchas operaciones son realizadas solo es requerido tiempo constante. | -| **Log** | La operación requiere de un tiempo proporcional al logaritmo del tamaño de la colección. | -| **L** | La operación es linea, es decir que requiere tiempo proporcional al tamaño de la colección. | -| **-** | La operación no es soportada. | - -La primer tabla considera tipos secuencia --ambos mutables e inmutables-- con las siguientes operaciones: - -| | | -| --- | ---- | -| **head** | Seleccionar el primer elemento de la secuencia. | -| **tail** | Produce una nueva secuencia que contiene todos los elementos menos el primero. | -| **apply** | Indexación. Seleccionar un elemento por posición. | -| **update** | Actualización funcional (con `updated`) para secuencias inmutables, actualización real con efectos laterales para secuencias mutables. | -| **prepend**| Añadir un elemento al principio de la colección. Para secuencias inmutables, esto produce una nueva secuencia. Para secuencias mutables, modifica la secuencia existente. | -| **append** | Añadir un elemento al final de la secuencia. Para secuencias inmutables, produce una nueva secuencia. Para secuencias mutables modifica la secuencia existente. | -| **insert** | Inserta un elemento a una posición arbitraria. Solamente es posible en secuencias mutables. | - -La segunda tabla trata sets y maps, tanto mutables como inmutables, con las siguientes operaciones: - -| | | -| --- | ---- | -| **lookup** | Comprueba si un elemento es contenido en un set, o selecciona un valor asociado con una clave en un map. | -| **add** | Añade un nuevo elemento a un set o un par clave/valor a un map. | -| **remove** | Removing an element from a set or a key from a map. | -| **remove** | Elimina un elemento de un set o una clave de un map. | -| **min** | El menor elemento de un set o la menor clave de un mapa. | - - - - - - - - - - - - diff --git a/es/overviews/parallel-collections/configuration.md b/es/overviews/parallel-collections/configuration.md deleted file mode 100644 index 86e19d7ecb..0000000000 --- a/es/overviews/parallel-collections/configuration.md +++ /dev/null @@ -1,81 +0,0 @@ ---- -layout: overview-large -title: Configurando las colecciones paralelas - -discourse: false - -partof: parallel-collections -num: 7 -language: es ---- - -## "Task support" - -Las colecciones paralelas son modulares respecto al modo en que las operaciones -son planificadas. Cada colección paralela es planificada con un objeto "task support" -el cual es responsable de la planificación y el balanceo de las tareas a los -distintos procesadores. - -El objeto "task support" mantiene internamente un referencia a un pool de hilos y decide -cómo y cuando las tareas son divididas en tareas más pequeñas. Para conocer más en detalle -cómo funciona internamente diríjase al informe técnico \[[1][1]\]. - -En la actualidad las colecciones paralelas disponen de unas cuantas implementaciones de -"task support". El `ForkJoinTaskSupport` utiliza internamente un fork-join pool y es utilizado -por defecto en JVM 1.6 o superiores. `ThreadPoolTaskSupport`, menos eficiente, es utilizado como -mecanismo de reserva para JVM 1.5 y máquinas virtuales que no soporten los fork join pools. El -`ExecutionContextTaskSupport` utiliza el contexto de ejecución por defecto que viene definido -en `scala.concurrent`, y reutiliza el thread pool utilizado en dicho paquete (podrá ser un fork -join pool o un thread pool executor dependiendo de la versión de la JVM). El "task support" basado -en el contexto de ejecución es establecido en cada una de las colecciones paralelas por defecto, de modo -que dichas colecciones reutilizan el mismo fork-join pool del mismo modo que el API de las "futures". - -A continuación se muestra cómo se puede modificar el objeto "task support" de una colección paralela: - - scala> import scala.collection.parallel._ - import scala.collection.parallel._ - - scala> val pc = mutable.ParArray(1, 2, 3) - pc: scala.collection.parallel.mutable.ParArray[Int] = ParArray(1, 2, 3) - - scala> pc.tasksupport = new ForkJoinTaskSupport(new scala.concurrent.forkjoin.ForkJoinPool(2)) - pc.tasksupport: scala.collection.parallel.TaskSupport = scala.collection.parallel.ForkJoinTaskSupport@4a5d484a - - scala> pc map { _ + 1 } - res0: scala.collection.parallel.mutable.ParArray[Int] = ParArray(2, 3, 4) - -El fragmento de código anterior determina que la colección paralela utilice un fork-join pool con un nivel 2 de -paralelismo. Para indicar que la colección utilice un thread pool executor tendremos que hacerlo del siguiente modo: - - scala> pc.tasksupport = new ThreadPoolTaskSupport() - pc.tasksupport: scala.collection.parallel.TaskSupport = scala.collection.parallel.ThreadPoolTaskSupport@1d914a39 - - scala> pc map { _ + 1 } - res1: scala.collection.parallel.mutable.ParArray[Int] = ParArray(2, 3, 4) - -Cuando una colección paralela es serializada, el atributo que almacena la referencia -al objeto "task support" es omitido en el proceso de serialización. Cuando una colección -paralela es deserializada, dicho atributo toma el valor por defecto -- el objeto "task support" -basado en el contexto de ejecución. - -Para llevar a cabo una implementación personalizada de un nuevo objeto "task support" necesitamos -extender del trait `TaskSupport` e implementar los siguientes métodos: - - def execute[R, Tp](task: Task[R, Tp]): () => R - - def executeAndWaitResult[R, Tp](task: Task[R, Tp]): R - - def parallelismLevel: Int - -El método `execute` planifica una tarea asíncrona y retorna una "future" sobre la que -esperar el resultado de la computación. El método `executeAndWait` lleva a cabo el mismo -trabajo, pero retorna única y exclusivamente una vez la tarea haya finalizado. `parallelismLevel` -simplemente retorna el número de núcleos que el objeto "task support" utiliza para planificar -las diferentes tareas. - - -## Referencias - -1. [On a Generic Parallel Collection Framework, June 2011][1] - - [1]: http://infoscience.epfl.ch/record/165523/files/techrep.pdf "parallel-collections" diff --git a/es/overviews/parallel-collections/conversions.md b/es/overviews/parallel-collections/conversions.md deleted file mode 100644 index 1c467efe76..0000000000 --- a/es/overviews/parallel-collections/conversions.md +++ /dev/null @@ -1,77 +0,0 @@ ---- -layout: overview-large -title: Conversiones en colecciones paralelas -discourse: false - -partof: parallel-collections -num: 3 -language: es ---- - -## Conversiones entre colecciones secuenciales y paralelas - -Cada una de las colecciones secuenciales puede convertirse es su versión -paralela mediante la utilización del método `par`. Determinadas colecciones -secuenciales disponen de una versión homóloga paralela. Para estas colecciones el -proceso de conversión es eficiente -- ocurre en tiempo constante dado que ambas -versiones utilizan la misma estructura de datos interna. Una excepción al caso -anterior es el caso de los hash maps y hash sets mutables, donde el proceso de -conversión es un poco más costoso la primera vez que el método `par` es llamado, -aunque las posteriores invocaciones de dicho método ofrecerán un tiempo de ejecución -constante. Nótese que en el caso de las colecciones mutables, los cambios en la -colección secuencial son visibles en su homóloga paralela en el caso de que compartan -la estructura de datos subyacente. - -| Secuencial | Paralelo | -| ------------- | -------------- | -| **mutable** | | -| `Array` | `ParArray` | -| `HashMap` | `ParHashMap` | -| `HashSet` | `ParHashSet` | -| `TrieMap` | `ParTrieMap` | -| **inmutable** | | -| `Vector` | `ParVector` | -| `Range` | `ParRange` | -| `HashMap` | `ParHashMap` | -| `HashSet` | `ParHashSet` | - -Otro tipo de colecciones, como las listas, colas o `streams`, son inherentemente -secuenciales en el sentido de que los elementos deben ser accedidos uno tras otro. -La versión paralela de estas estructuras se obtiene mediante la copia de los elementos -en una colección paralela. Por ejemplo, una lista funcional es convertida en una -secuencia paralela inmutable; un vector paralelo. - -Cada colección paralela puede ser convertida a su variante secuencial mediante el uso -del método `seq`. La conversión de una colección paralela a su homóloga secuencial es -siempre un proceso eficiente -- tiempo constante. La invocación del método `seq` sobre -una colección paralela mutable retorna una colección secuencial cuya representación interna -es la misma que la de la versión paralela, por lo que posibles actualizaciones en una de las -colecciones serán visibles en la otra. - -## Conversiones entre diferentes tipo de colecciones - -Ortogonal a la conversión entre colecciones secuenciales y paralelas, las colecciones -pueden convertirse entre diferentes tipos. Por ejemplo, la llamada al método `toSeq` -convierte un conjunto secuencial en una secuencia secuencial, mientras que si invocamos -dicho método sobre un conjunto paralelo obtendremos una secuencia paralela. La regla -general is que si existe una versión paralela de `X`, el método `toX` convierte la colección -en una colección `ParX` - -A continuación se muestra un resumen de todos los métodos de conversión: - -| método | Tipo de Retorno| -| -------------- | -------------- | -| `toArray` | `Array` | -| `toList` | `List` | -| `toIndexedSeq` | `IndexedSeq` | -| `toStream` | `Stream` | -| `toIterator` | `Iterator` | -| `toBuffer` | `Buffer` | -| `toTraversable`| `GenTraverable`| -| `toIterable` | `ParIterable` | -| `toSeq` | `ParSeq` | -| `toSet` | `ParSet` | -| `toMap` | `ParMap` | - - - diff --git a/es/overviews/parallel-collections/ctries.md b/es/overviews/parallel-collections/ctries.md deleted file mode 100644 index ce5c825371..0000000000 --- a/es/overviews/parallel-collections/ctries.md +++ /dev/null @@ -1,166 +0,0 @@ ---- -layout: overview-large -title: Tries Concurrentes - -discourse: false - -partof: parallel-collections -num: 4 -language: es ---- - -La mayoría de las estructuras de datos no garantizan un recorrido consistente -si la estructura es modificada durante el recorrido de la misma. De hecho, -esto también sucede en la mayor parte de las colecciones mutables. Los "tries" -concurrentes presentan una característica especial, permitiendo la modificación -de los mismos mientras están siendo recorridos. Las modificaciones solo son visibles -en los recorridos posteriores a las mismas. Ésto aplica tanto a los "tries" secuenciales -como a los paralelos. La única diferencia entre ambos es que el primero de ellos -recorre todos los elementos de la estructura de manera secuencial mientras que -el segundo lo hace en paralelo. - -Esta propiedad nos permite escribir determinados algoritmos de un modo mucho más -sencillo. Por lo general, son algoritmos que procesan un conjunto de elementos de manera -iterativa y diferentes elementos necesitan distinto número de iteraciones para ser -procesados. - -El siguiente ejemplo calcula la raíz cuadrada de un conjunto de números. Cada iteración -actualiza, de manera iterativa, el valor de la raíz cuadrada. Aquellos números cuyas -raíces convergen son eliminados del mapa. - - case class Entry(num: Double) { - var sqrt = num - } - - val length = 50000 - - // prepare the list - val entries = (1 until length) map { num => Entry(num.toDouble) } - val results = ParTrieMap() - for (e <- entries) results += ((e.num, e)) - - // compute square roots - while (results.nonEmpty) { - for ((num, e) <- results) { - val nsqrt = 0.5 * (e.sqrt + e.num / e.sqrt) - if (math.abs(nsqrt - e.sqrt) < 0.01) { - results.remove(num) - } else e.sqrt = nsqrt - } - } - -Fíjese que en el anterior método de cálculo de la raíz cuadrada (método Babylonian) -(\[[3][3]\]) algunos números pueden converger mucho más rápidamente que otros. Por esta razón, -queremos eliminar dichos números de la variable `results` de manera que solo aquellos -elementos sobre los que realmente necesitamos trabajar son recorridos. - -Otro ejemplo es el algoritmo de búsqueda en anchura, el cual iterativamente expande el "nodo cabecera" -hasta que encuentra un camino hacia el objetivo o no existen más nodos a expandir. Definamos -un nodo en mapa 2D como una tupla de enteros (`Int`s). Definamos un `map` como un array de -booleanos de dos dimensiones el cual determina si un determinado slot está ocupado o no. Posteriormente, -declaramos dos "concurrent tries maps" -- `open` contiene todos los nodos que deben ser expandidos -("nodos cabecera") mientras que `close` continene todos los nodos que ya han sido expandidos. Comenzamos -la búsqueda desde las esquinas del mapa en busca de un camino hasta el centro del mismo -- -e inicializamos el mapa `open` con los nodos apropiados. Iterativamamente, y en paralelo, -expandimos todos los nodos presentes en el mapa `open` hasta que agotamos todos los elementos -que necesitan ser expandidos. Cada vez que un nodo es expandido, se elimina del mapa `open` y se -añade en el mapa `closed`. Una vez finalizado el proceso, se muestra el camino desde el nodo -destino hasta el nodo inicial. - - val length = 1000 - - // define the Node type - type Node = (Int, Int); - type Parent = (Int, Int); - - // operations on the Node type - def up(n: Node) = (n._1, n._2 - 1); - def down(n: Node) = (n._1, n._2 + 1); - def left(n: Node) = (n._1 - 1, n._2); - def right(n: Node) = (n._1 + 1, n._2); - - // create a map and a target - val target = (length / 2, length / 2); - val map = Array.tabulate(length, length)((x, y) => (x % 3) != 0 || (y % 3) != 0 || (x, y) == target) - def onMap(n: Node) = n._1 >= 0 && n._1 < length && n._2 >= 0 && n._2 < length - - // open list - the nodefront - // closed list - nodes already processed - val open = ParTrieMap[Node, Parent]() - val closed = ParTrieMap[Node, Parent]() - - // add a couple of starting positions - open((0, 0)) = null - open((length - 1, length - 1)) = null - open((0, length - 1)) = null - open((length - 1, 0)) = null - - // greedy bfs path search - while (open.nonEmpty && !open.contains(target)) { - for ((node, parent) <- open) { - def expand(next: Node) { - if (onMap(next) && map(next._1)(next._2) && !closed.contains(next) && !open.contains(next)) { - open(next) = node - } - } - expand(up(node)) - expand(down(node)) - expand(left(node)) - expand(right(node)) - closed(node) = parent - open.remove(node) - } - } - - // print path - var pathnode = open(target) - while (closed.contains(pathnode)) { - print(pathnode + "->") - pathnode = closed(pathnode) - } - println() - - -Los "tries" concurrentes también soportan una operación atómica, no bloqueante y de -tiempo constante conocida como `snapshot`. Esta operación genera un nuevo `trie` -concurrente en el que se incluyen todos los elementos es un instante determinado de -tiempo, lo que en efecto captura el estado del "trie" en un punto específico. -Esta operación simplemente crea una nueva raíz para el "trie" concurrente. Posteriores -actualizaciones reconstruyen, de manera perezosa, la parte del "trie" concurrente que se -ha visto afectada por la actualización, manteniendo intacto el resto de la estructura. -En primer lugar, esto implica que la propia operación de `snapshot` no es costosa en si misma -puesto que no necesita copiar los elementos. En segundo lugar, dado que la optimización -"copy-and-write" solo copia determinadas partes del "trie" concurrente, las sucesivas -actualizaciones escalan horizontalmente. El método `readOnlySnapshot` es ligeramente -más efeciente que el método `snapshot`, pero retorna un mapa en modo solo lectura que no -puede ser modificado. Este tipo de estructura de datos soporta una operación atómica y en tiempo -constante llamada `clear` la cual está basada en el anterior mecanismo de `snapshot`. - -Si desea conocer en más detalle cómo funcionan los "tries" concurrentes y el mecanismo de -snapshot diríjase a \[[1][1]\] y \[[2][2]\]. - -Los iteradores para los "tries" concurrentes están basados en snapshots. Anteriormente a la creación -del iterador se obtiene un snapshot del "trie" concurrente, de modo que el iterador solo recorrerá -los elementos presentes en el "trie" en el momento de creación del snapshot. Naturalmente, -estos iteradores utilizan un snapshot de solo lectura. - -La operación `size` también está basada en el mecanismo de snapshot. En una sencilla implementación, -la llamada al método `size` simplemente crearía un iterador (i.e., un snapshot) y recorrería los -elementos presentes en el mismo realizando la cuenta. Cada una de las llamadas a `size` requeriría -tiempo lineal en relación al número de elementos. Sin embargo, los "tries" concurrentes han sido -optimizados para cachear los tamaños de sus diferentes partes, reduciendo por tanto la complejidad -del método a un tiempo logarítmico amortizado. En realidad, esto significa que tras la primera -llamada al método `size`, las sucesivas llamadas requerirán un esfuerzo mínimo, típicamente recalcular -el tamaño para aquellas ramas del "trie" que hayan sido modificadas desde la última llamada al método -`size`. Adicionalmente, el cálculo del tamaño para los "tries" concurrentes y paralelos se lleva a cabo -en paralelo. - -## Referencias - -1. [Cache-Aware Lock-Free Concurrent Hash Tries][1] -2. [Concurrent Tries with Efficient Non-Blocking Snapshots][2] -3. [Methods of computing square roots][3] - - [1]: http://infoscience.epfl.ch/record/166908/files/ctries-techreport.pdf "Ctries-techreport" - [2]: http://lampwww.epfl.ch/~prokopec/ctries-snapshot.pdf "Ctries-snapshot" - [3]: http://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method "babylonian-method" diff --git a/es/overviews/parallel-collections/custom-parallel-collections.md b/es/overviews/parallel-collections/custom-parallel-collections.md deleted file mode 100644 index b3ffc27c93..0000000000 --- a/es/overviews/parallel-collections/custom-parallel-collections.md +++ /dev/null @@ -1,332 +0,0 @@ ---- -layout: overview-large -title: Creating Custom Parallel Collections - -discourse: false - -partof: parallel-collections -num: 6 -language: es ---- - -## Parallel collections without combiners - -Just as it is possible to define custom sequential collections without -defining their builders, it is possible to define parallel collections without -defining their combiners. The consequence of not having a combiner is that -transformer methods (e.g. `map`, `flatMap`, `collect`, `filter`, ...) will by -default return a standard collection type which is nearest in the hierarchy. -For example, ranges do not have builders, so mapping elements of a range -creates a vector. - -In the following example we define a parallel string collection. Since strings -are logically immutable sequences, we have parallel strings inherit -`immutable.ParSeq[Char]`: - - class ParString(val str: String) - extends immutable.ParSeq[Char] { - -Next, we define methods found in every immutable sequence: - - def apply(i: Int) = str.charAt(i) - - def length = str.length - -We have to also define the sequential counterpart of this parallel collection. -In this case, we return the `WrappedString` class: - - def seq = new collection.immutable.WrappedString(str) - -Finally, we have to define a splitter for our parallel string collection. We -name the splitter `ParStringSplitter` and have it inherit a sequence splitter, -that is, `SeqSplitter[Char]`: - - def splitter = new ParStringSplitter(str, 0, str.length) - - class ParStringSplitter(private var s: String, private var i: Int, private val ntl: Int) - extends SeqSplitter[Char] { - - final def hasNext = i < ntl - - final def next = { - val r = s.charAt(i) - i += 1 - r - } - -Above, `ntl` represents the total length of the string, `i` is the current -position and `s` is the string itself. - -Parallel collection iterators or splitters require a few more methods in -addition to `next` and `hasNext` found in sequential collection iterators. -First of all, they have a method called `remaining` which returns the number -of elements this splitter has yet to traverse. Next, they have a method called -`dup` which duplicates the current splitter. - - def remaining = ntl - i - - def dup = new ParStringSplitter(s, i, ntl) - -Finally, methods `split` and `psplit` are used to create splitters which -traverse subsets of the elements of the current splitter. Method `split` has -the contract that it returns a sequence of splitters which traverse disjoint, -non-overlapping subsets of elements that the current splitter traverses, none -of which is empty. If the current splitter has 1 or less elements, then -`split` just returns a sequence of this splitter. Method `psplit` has to -return a sequence of splitters which traverse exactly as many elements as -specified by the `sizes` parameter. If the `sizes` parameter specifies less -elements than the current splitter, then an additional splitter with the rest -of the elements is appended at the end. If the `sizes` parameter requires more -elements than there are remaining in the current splitter, it will append an -empty splitter for each size. Finally, calling either `split` or `psplit` -invalidates the current splitter. - - def split = { - val rem = remaining - if (rem >= 2) psplit(rem / 2, rem - rem / 2) - else Seq(this) - } - - def psplit(sizes: Int*): Seq[ParStringSplitter] = { - val splitted = new ArrayBuffer[ParStringSplitter] - for (sz <- sizes) { - val next = (i + sz) min ntl - splitted += new ParStringSplitter(s, i, next) - i = next - } - if (remaining > 0) splitted += new ParStringSplitter(s, i, ntl) - splitted - } - } - } - -Above, `split` is implemented in terms of `psplit`, which is often the case -with parallel sequences. Implementing a splitter for parallel maps, sets or -iterables is often easier, since it does not require `psplit`. - -Thus, we obtain a parallel string class. The only downside is that calling transformer methods -such as `filter` will not produce a parallel string, but a parallel vector instead, which -may be suboptimal - producing a string again from the vector after filtering may be costly. - - -## Parallel collections with combiners - -Lets say we want to `filter` the characters of the parallel string, to get rid -of commas for example. As noted above, calling `filter` produces a parallel -vector and we want to obtain a parallel string (since some interface in the -API might require a sequential string). - -To avoid this, we have to write a combiner for the parallel string collection. -We will also inherit the `ParSeqLike` trait this time to ensure that return -type of `filter` is more specific - a `ParString` instead of a `ParSeq[Char]`. -The `ParSeqLike` has a third type parameter which specifies the type of the -sequential counterpart of the parallel collection (unlike sequential `*Like` -traits which have only two type parameters). - - class ParString(val str: String) - extends immutable.ParSeq[Char] - with ParSeqLike[Char, ParString, collection.immutable.WrappedString] - -All the methods remain the same as before, but we add an additional protected method `newCombiner` which -is internally used by `filter`. - - protected[this] override def newCombiner: Combiner[Char, ParString] = new ParStringCombiner - -Next we define the `ParStringCombiner` class. Combiners are subtypes of -builders and they introduce an additional method called `combine`, which takes -another combiner as an argument and returns a new combiner which contains the -elements of both the current and the argument combiner. The current and the -argument combiner are invalidated after calling `combine`. If the argument is -the same object as the current combiner, then `combine` just returns the -current combiner. This method is expected to be efficient, having logarithmic -running time with respect to the number of elements in the worst case, since -it is called multiple times during a parallel computation. - -Our `ParStringCombiner` will internally maintain a sequence of string -builders. It will implement `+=` by adding an element to the last string -builder in the sequence, and `combine` by concatenating the lists of string -builders of the current and the argument combiner. The `result` method, which -is called at the end of the parallel computation, will produce a parallel -string by appending all the string builders together. This way, elements are -copied only once at the end instead of being copied every time `combine` is -called. Ideally, we would like to parallelize this process and copy them in -parallel (this is being done for parallel arrays), but without tapping into -the internal representation of strings this is the best we can do-- we have to -live with this sequential bottleneck. - - private class ParStringCombiner extends Combiner[Char, ParString] { - var sz = 0 - val chunks = new ArrayBuffer[StringBuilder] += new StringBuilder - var lastc = chunks.last - - def size: Int = sz - - def +=(elem: Char): this.type = { - lastc += elem - sz += 1 - this - } - - def clear = { - chunks.clear - chunks += new StringBuilder - lastc = chunks.last - sz = 0 - } - - def result: ParString = { - val rsb = new StringBuilder - for (sb <- chunks) rsb.append(sb) - new ParString(rsb.toString) - } - - def combine[U <: Char, NewTo >: ParString](other: Combiner[U, NewTo]) = if (other eq this) this else { - val that = other.asInstanceOf[ParStringCombiner] - sz += that.sz - chunks ++= that.chunks - lastc = chunks.last - this - } - } - - -## How do I implement my combiner in general? - -There are no predefined recipes-- it depends on the data-structure at -hand, and usually requires a bit of ingenuity on the implementer's -part. However there are a few approaches usually taken: - -1. Concatenation and merge. Some data-structures have efficient -implementations (usually logarithmic) of these operations. -If the collection at hand is backed by such a data-structure, -its combiner can be the collection itself. Finger trees, -ropes and various heaps are particularly suitable for such an approach. - -2. Two-phase evaluation. An approach taken in parallel arrays and -parallel hash tables, it assumes the elements can be efficiently -partially sorted into concatenable buckets from which the final -data-structure can be constructed in parallel. In the first phase -different processors populate these buckets independently and -concatenate the buckets together. In the second phase, the data -structure is allocated and different processors populate different -parts of the datastructure in parallel using elements from disjoint -buckets. -Care must be taken that different processors never modify the same -part of the datastructure, otherwise subtle concurrency errors may occur. -This approach is easily applicable to random access sequences, as we -have shown in the previous section. - -3. A concurrent data-structure. While the last two approaches actually -do not require any synchronization primitives in the data-structure -itself, they assume that it can be constructed concurrently in a way -such that two different processors never modify the same memory -location. There exists a large number of concurrent data-structures -that can be modified safely by multiple processors-- concurrent skip lists, -concurrent hash tables, split-ordered lists, concurrent avl trees, to -name a few. -An important consideration in this case is that the concurrent -data-structure has a horizontally scalable insertion method. -For concurrent parallel collections the combiner can be the collection -itself, and a single combiner instance is shared between all the -processors performing a parallel operation. - - -## Integration with the collections framework - -Our `ParString` class is not complete yet. Although we have implemented a -custom combiner which will be used by methods such as `filter`, `partition`, -`takeWhile` or `span`, most transformer methods require an implicit -`CanBuildFrom` evidence (see Scala collections guide for a full explanation). -To make it available and completely integrate `ParString` with the collections -framework, we have to mix an additional trait called `GenericParTemplate` and -define the companion object of `ParString`. - - class ParString(val str: String) - extends immutable.ParSeq[Char] - with GenericParTemplate[Char, ParString] - with ParSeqLike[Char, ParString, collection.immutable.WrappedString] { - - def companion = ParString - -Inside the companion object we provide an implicit evidence for the `CanBuildFrom` parameter. - - object ParString { - implicit def canBuildFrom: CanCombineFrom[ParString, Char, ParString] = - new CanCombinerFrom[ParString, Char, ParString] { - def apply(from: ParString) = newCombiner - def apply() = newCombiner - } - - def newBuilder: Combiner[Char, ParString] = newCombiner - - def newCombiner: Combiner[Char, ParString] = new ParStringCombiner - - def apply(elems: Char*): ParString = { - val cb = newCombiner - cb ++= elems - cb.result - } - } - - - -## Further customizations-- concurrent and other collections - -Implementing a concurrent collection (unlike parallel collections, concurrent -collections are ones that can be concurrently modified, like -`collection.concurrent.TrieMap`) is not always straightforward. Combiners in -particular often require a lot of thought. In most _parallel_ collections -described so far, combiners use a two-step evaluation. In the first step the -elements are added to the combiners by different processors and the combiners -are merged together. In the second step, after all the elements are available, -the resulting collection is constructed. - -Another approach to combiners is to construct the resulting collection as the -elements. This requires the collection to be thread-safe-- a combiner must -allow _concurrent_ element insertion. In this case one combiner is shared by -all the processors. - -To parallelize a concurrent collection, its combiners must override the method -`canBeShared` to return `true`. This will ensure that only one combiner is -created when a parallel operation is invoked. Next, the `+=` method must be -thread-safe. Finally, method `combine` still returns the current combiner if -the current combiner and the argument combiner are the same, and is free to -throw an exception otherwise. - -Splitters are divided into smaller splitters to achieve better load balancing. -By default, information returned by the `remaining` method is used to decide -when to stop dividing the splitter. For some collections, calling the -`remaining` method may be costly and some other means should be used to decide -when to divide the splitter. In this case, one should override the -`shouldSplitFurther` method in the splitter. - -The default implementation divides the splitter if the number of remaining -elements is greater than the collection size divided by eight times the -parallelism level. - - def shouldSplitFurther[S](coll: ParIterable[S], parallelismLevel: Int) = - remaining > thresholdFromSize(coll.size, parallelismLevel) - -Equivalently, a splitter can hold a counter on how many times it was split and -implement `shouldSplitFurther` by returning `true` if the split count is -greater than `3 + log(parallelismLevel)`. This avoids having to call -`remaining`. - -Furthermore, if calling `remaining` is not a cheap operation for a particular -collection (i.e. it requires evaluating the number of elements in the -collection), then the method `isRemainingCheap` in splitters should be -overridden to return `false`. - -Finally, if the `remaining` method in splitters is extremely cumbersome to -implement, you can override the method `isStrictSplitterCollection` in its -collection to return `false`. Such collections will fail to execute some -methods which rely on splitters being strict, i.e. returning a correct value -in the `remaining` method. Importantly, this does not effect methods used in -for-comprehensions. - - - - - - - diff --git a/es/overviews/parallel-collections/overview.md b/es/overviews/parallel-collections/overview.md deleted file mode 100644 index 023d5c7afd..0000000000 --- a/es/overviews/parallel-collections/overview.md +++ /dev/null @@ -1,197 +0,0 @@ ---- -layout: overview-large -title: Overview - -discourse: false - -partof: parallel-collections -num: 1 -language: es ---- - -**Autores originales: Aleksandar Prokopec, Heather Miller** - -**Traducción y arreglos: Santiago Basulto** - -## Motivación - -En el medio del cambio en los recientes años de los fabricantes de procesadores de arquitecturas simples a arquitecturas multi-nucleo, tanto el ámbito académico, como el industrial coinciden que la _Programación Paralela_ sigue siendo un gran desafío. - -Las Colecciones Paralelizadas (Parallel collections, en inglés) fueron incluidas en la librería del lenguaje Scala en un esfuerzo de facilitar la programación paralela al abstraer a los usuarios de detalles de paralelización de bajo nivel, mientras se provee con una abstracción de alto nivel, simple y familiar. La esperanza era, y sigue siendo, que el paralelismo implícito detrás de una abstracción de colecciones (como lo es el actual framework de colecciones del lenguaje) acercara la ejecución paralela confiable, un poco más al trabajo diario de los desarrolladores. - -La idea es simple: las colecciones son abstracciones de programación ficientemente entendidas y a su vez son frecuentemente usadas. Dada su regularidad, es posible que sean paralelizadas eficiente y transparentemente. Al permitirle al usuario intercambiar colecciones secuenciales por aquellas que son operadas en paralelo, las colecciones paralelizadas de Scala dan un gran paso hacia la posibilidad de que el paralelismo sea introducido cada vez más frecuentemente en nuestro código. - -Veamos el siguiente ejemplo secuencial, donde realizamos una operación monádica en una colección lo suficientemente grande. - - val list = (1 to 10000).toList - list.map(_ + 42) - -Para realizar la misma operación en paralelo, lo único que devemos incluir, es la invocación al método `par` en la colección secuencial `list`. Después de eso, es posible utilizar la misma colección paralelizada de la misma manera que normalmente la usariamos si fuera una colección secuencial. El ejemplo superior puede ser paralelizado al hacer simplemente lo siguiente: - - list.par.map(_ + 42) - -El diseño de la librería de colecciones paralelizadas de Scala está inspirada y fuertemente integrada con la librería estandar de colecciones (secuenciales) del lenguaje (introducida en la versión 2.8). Se provee te una contraparte paralelizada a un número importante de estructuras de datos de la librería de colecciones (secuenciales) de Scala, incluyendo: - -* `ParArray` -* `ParVector` -* `mutable.ParHashMap` -* `mutable.ParHashSet` -* `immutable.ParHashMap` -* `immutable.ParHashSet` -* `ParRange` -* `ParTrieMap` (`collection.concurrent.TrieMap`s are new in 2.10) - -Además de una arquitectura común, la librería de colecciones paralelizadas de Scala también comparte la _extensibilidad_ con la librería de colecciones secuenciales. Es decir, de la misma manera que los usuarios pueden integrar sus propios tipos de tipos de colecciones de la librería normal de colecciones secuenciales, pueden realizarlo con la librería de colecciones paralelizadas, heredando automáticamente todas las operaciones paralelas disponibles en las demás colecciones paralelizadas de la librería estandar. - -## Algunos Ejemplos - -To attempt to illustrate the generality and utility of parallel collections, -we provide a handful of simple example usages, all of which are transparently -executed in parallel. - -De forma de ilustrar la generalidad y utilidad de las colecciones paralelizadas, proveemos un conjunto de ejemplos de uso útiles, todos ellos siendo ejecutados en paralelo de forma totalmente transparente al usuario. - -_Nota:_ Algunos de los siguientes ejemplos operan en colecciones pequeñas, lo cual no es recomendado. Son provistos como ejemplo para ilustrar solamente el propósito. Como una regla heurística general, los incrementos en velocidad de ejecución comienzan a ser notados cuando el tamaño de la colección es lo suficientemente grande, tipicamente algunos cuantos miles de elementos. (Para más información en la relación entre tamaño de una coleccion paralelizada y su performance, por favor véase [appropriate subsection]({{ site.baseurl}}/es/overviews/parallel-collections/performance.html#how_big_should_a_collection_be_to_go_parallel) en la sección [performance]({{ site.baseurl }}/es/overviews/parallel-collections/performance.html) (en inglés). - -#### map - -Usando un `map` paralelizado para transformar una colección de elementos tipo `String` a todos caracteres en mayúscula: - - scala> val apellidos = List("Smith","Jones","Frankenstein","Bach","Jackson","Rodin").par - apellidos: scala.collection.parallel.immutable.ParSeq[String] = ParVector(Smith, Jones, Frankenstein, Bach, Jackson, Rodin) - - scala> apellidos.map(_.toUpperCase) - res0: scala.collection.parallel.immutable.ParSeq[String] = ParVector(SMITH, JONES, FRANKENSTEIN, BACH, JACKSON, RODIN) - -#### fold - -Sumatoria mediante `fold` en un `ParArray`: - - scala> val parArray = (1 to 10000).toArray.par - parArray: scala.collection.parallel.mutable.ParArray[Int] = ParArray(1, 2, 3, ... - - scala> parArray.fold(0)(_ + _) - res0: Int = 50005000 - -#### filtrando - - -Usando un filtrado mediante `filter` paralelizado para seleccionar los apellidos que alfabéticamente preceden la letra "K": - - scala> val apellidos = List("Smith","Jones","Frankenstein","Bach","Jackson","Rodin").par - apellidos: scala.collection.parallel.immutable.ParSeq[String] = ParVector(Smith, Jones, Frankenstein, Bach, Jackson, Rodin) - - scala> apellidos.filter(_.head >= 'J') - res0: scala.collection.parallel.immutable.ParSeq[String] = ParVector(Smith, Jones, Jackson, Rodin) - -## Creación de colecciones paralelizadas - -Las colecciones paralelizadas están pensadas para ser usadas exactamente de la misma manera que las colecciones secuenciales --la única diferencia notoria es cómo _obtener_ una colección paralelizada. - -Generalmente se tienen dos opciones para la creación de colecciones paralelizadas: - -Primero al utilizar la palabra clave `new` y una sentencia de importación apropiada: - - import scala.collection.parallel.immutable.ParVector - val pv = new ParVector[Int] - -Segundo, al _convertir_ desde una colección secuencial: - - val pv = Vector(1,2,3,4,5,6,7,8,9).par - -Lo que es importante desarrollar aquí son estos métodos para la conversión de colecciones. Las colecciones secuenciales pueden ser convertiadas a colecciones paralelizadas mediante la invocación del método `par`, y de la misma manera, las colecciones paralelizadas pueden ser convertidas a colecciones secuenciales mediante el método `seq`. - -_Nota:_ Las colecciones que son inherentemente secuenciales (en el sentido que sus elementos deben ser accedidos uno a uno), como las listas, colas y streams (a veces llamados flujos), son convertidos a sus contrapartes paralelizadas al copiar los todos sus elementos. Un ejemplo es la clase `List` --es convertida a una secuencia paralelizada inmutable común, que es un `ParVector`. Por supuesto, el tener que copiar los elementos para estas colecciones involucran una carga más de trabajo que no se sufre con otros tipos como: `Array`, `Vector`, `HashMap`, etc. - -For more information on conversions on parallel collections, see the -[conversions]({{ site.baseurl }}/overviews/parallel-collections/conversions.html) -and [concrete parallel collection classes]({{ site.baseurl }}/overviews/parallel-collections/concrete-parallel-collections.html) -sections of thise guide. - -Para más información sobre la conversión de colecciones paralelizadas, véase los artículos sobre [conversiones]({{ site.baseurl }}/es/overviews/parallel-collections/conversions.html) y [clases concretas de colecciones paralelizadas]({{ site.baseurl }}/es/overviews/parallel-collections/concrete-parallel-collections.html) de esta misma serie. - -## Entendiendo las colecciones paralelizadas - -A pesar de que las abstracciones de las colecciones paralelizadas se parecen mucho a las colecciones secuenciales normales, es importante notar que su semántica difiere, especialmente con relación a efectos secundarios (o colaterales, según algunas traducciones) y operaciones no asociativas. - -Para entender un poco más esto, primero analizaremos _cómo_ son realizadas las operaciones paralelas. Conceptualmente, el framework de colecciones paralelizadas de Scala paraleliza una operación al "dividir" recursivamente una colección dada, aplicando una operación en cada partición de la colección en paralelo y recombinando todos los resultados que fueron completados en paralelo. - -Esta ejecución concurrente y fuera de orden de las colecciones paralelizadas llevan a dos implicancias que es importante notar: - -1. **Las operaciones con efectos secundarios pueden llegar a resultados no deterministas** -2. **Operaciones no asociativas generan resultados no deterministas** - -### Operaciones con efectos secundarios - -Given the _concurrent_ execution semantics of the parallel collections -framework, operations performed on a collection which cause side-effects -should generally be avoided, in order to maintain determinism. A simple -example is by using an accessor method, like `foreach` to increment a `var` -declared outside of the closure which is passed to `foreach`. - -Dada la ejecución _concurrente_ del framework de colecciones paralelizadas, las operaciones que generen efectos secundarios generalmente deben ser evitadas, de manera de mantener el "determinismo". - -Veamos un ejemplo: - - scala> var sum = 0 - sum: Int = 0 - - scala> val list = (1 to 1000).toList.par - list: scala.collection.parallel.immutable.ParSeq[Int] = ParVector(1, 2, 3,… - - scala> list.foreach(sum += _); sum - res01: Int = 467766 - - scala> var sum = 0 - sum: Int = 0 - - scala> list.foreach(sum += _); sum - res02: Int = 457073 - - scala> var sum = 0 - sum: Int = 0 - - scala> list.foreach(sum += _); sum - res03: Int = 468520 - -Acá podemos ver que cada vez que `sum` es reinicializado a 0, e invocamos el método `foreach` en nuestro objeto `list`, el valor de `sum` resulta ser distinto. La razón de este no-determinismo es una _condición de carrera_ -- lecturas/escrituras concurrentes a la misma variable mutable. - -En el ejemplo anterior, es posible para dos hilos leer el _mismo_ valor de `sum`, demorarse un tiempo realizando la operación que tienen que hacer sobre `sum`, y después volver a escribir ese nuevo valor a `sum`, lo que probablemente resulte en una sobreescritura (y por lo tanto pérdida) de un valor anterior que generó otro hilo. Veamos otro ejemplo: - - HiloA: lee el valor en sum, sum = 0 valor de sum: 0 - HiloB: lee el valor en sum, sum = 0 valor de sum: 0 - HiloA: incrementa el valor de sum a 760, graba sum = 760 valor de sum: 760 - HiloA: incrementa el valor de sum a 12, graba sum = 12 valor de sum: 12 - -Este ejemplo ilustra un escenario donde dos hilos leen el mismo valor, `0`, antes que el otro pueda sumar su parte de la ejecución sobre la colección paralela. En este caso el `HiloA` lee `0` y le suma el valor de su cómputo, `0+760`, y en el caso del `HiloB`, le suma al valor leido `0` su resultado, quedando `0+12`. Después de computar sus respectivas sumas, ambos escriben el valor en `sum`. Ya que el `HiloA` llega a escribir antes que el `HiloB` (por nada en particular, solamente coincidencia que en este caso llegue primero el `HiloA`), su valor se pierde, porque seguidamente llega a escribir el `HiloB` y borra el valor previamente guardado. Esto se llama _condición de carrera_ porque el valor termina resultando una cuestión de suerte, o aleatoria, de quién llega antes o después a escribir el valor final. - -### Operaciones no asociativas - -Dado este funcionamiento "fuera de orden", también se debe ser cuidadoso de realizar solo operaciones asociativas para evitar comportamientos no esperados. Es decir, dada una colección paralelizada `par_col`, uno debe saber que cuando invoca una función de orden superior sobre `par_col`, tal como `par_col.reduce(func)`, el orden en que la función `func` es invocada sobre los elementos de `par_col` puede ser arbitrario (de hecho, es el caso más probable). Un ejemplo simple y pero no tan obvio de una operación no asociativa es es una substracción: - - scala> val list = (1 to 1000).toList.par - list: scala.collection.parallel.immutable.ParSeq[Int] = ParVector(1, 2, 3,… - - scala> list.reduce(_-_) - res01: Int = -228888 - - scala> list.reduce(_-_) - res02: Int = -61000 - - scala> list.reduce(_-_) - res03: Int = -331818 - -En el ejemplo anterior invocamos reduce sobre un `ParVector[Int]` pasándole `_-_`. Lo que hace esto es simplemente tomar dos elementos y resta el primero al segundo. Dado que el framework de colecciones paralelizadas crea varios hilos que realizan `reduce(_-_)` independientemente en varias secciones de la colección, el resultado de correr dos veces el método `reduce(_-_)` en la misma colección puede no ser el mismo. - -_Nota:_ Generalmente se piensa que, al igual que las operaciones no asociativas, las operaciones no conmutativas pasadas a un función de orden superior también generan resultados extraños (no deterministas). En realidad esto no es así, un simple ejemplo es la concatenación de Strings (cadenas de caracteres). -- una operación asociativa, pero no conmutativa: - - scala> val strings = List("abc","def","ghi","jk","lmnop","qrs","tuv","wx","yz").par - strings: scala.collection.parallel.immutable.ParSeq[java.lang.String] = ParVector(abc, def, ghi, jk, lmnop, qrs, tuv, wx, yz) - - scala> val alfabeto = strings.reduce(_++_) - alfabeto: java.lang.String = abcdefghijklmnopqrstuvwxyz - -Lo que implica el "fuera de orden" en las colecciones paralelizadas es solamente que la operación será ejecutada fuera de orden (en un sentido _temporal_, es decir no secuencial, no significa que el resultado va a ser re-"*combinado*" fuera de orden (en un sentido de _espacio_). Al contrario, en general los resultados siempre serán reensamblados en roden, es decir una colección paralelizada que se divide en las siguientes particiones A, B, C, en ese orden, será reensamblada nuevamente en el orden A, B, C. No en otro orden arbitrario como B, C, A. - -Para más información de cómo se dividen y se combinan los diferentes tipos de colecciones paralelizadas véase el artículo sobre [Arquitectura]({{ site.baseurl }}/es/overviews -/parallel-collections/architecture.html) de esta misma serie. diff --git a/es/overviews/parallel-collections/performance.md b/es/overviews/parallel-collections/performance.md deleted file mode 100644 index 6c4fd9c173..0000000000 --- a/es/overviews/parallel-collections/performance.md +++ /dev/null @@ -1,276 +0,0 @@ ---- -layout: overview-large -title: Midiendo el rendimiento - -discourse: false - -partof: parallel-collections -num: 8 -outof: 8 -language: es ---- - -## Performance on the JVM - -Algunas veces el modelo de rendimiento de la JVM se complica debido a comentarios -sobre el mismo, y como resultado de los mismos, se tienen concepciones equívocas del mismo. -Por diferentes motivos, determinado código podría ofrecer un rendimiento o escalabilidad -inferior a la esperada. A continuación ofrecemos algunos ejemplos. - -Uno de los principales motivos es que el proceso de compilación de una aplicación que se -ejecuta sobre la JVM no es el mismo que el de un lenguaje compilado de manera estática -(véase \[[2][2]\]). Los compiladores de Java y Scala traducen el código fuente en *bytecode* y -el conjunto de optimizaciones que llevan a cabo es muy reducido. En la mayoría de las JVM -modernas, una vez el bytecode es ejecutado, se convierte en código máquina dependiente de la -arquitectura de la máquina subyacente. Este proceso es conocido como compilación "just-int-time". -Sin embargo, el nivel de optimización del código es muy bajo puesto que dicho proceso deber ser -lo más rápido posible. Con el objetivo de evitar el proceso de recompilación, el llamado -compilador HotSpot optimiza únicamente aquellas partes del código que son ejecutadas de manera -frecuente. Esto supone que los desarrolladores de "benchmarks" deberán ser conscientes que los -programas podrían presentar rendimientos dispares en diferentes ejecuciones. Múltiples ejecuciones -de un mismo fragmento de código (por ejemplo un método) podrían ofrecer rendimientos dispares en función de -si se ha llevado a cabo un proceso de optimización del código entre dichas ejecuciones. Adicionalmente, -la medición de los tiempos de ejecución de un fragmento de código podría incluir el tiempo en el que -el propio compilador JIT lleva a cabo el proceso de optimizacion, falseando los resultados. - -Otro elemento "oculto" que forma parte de la JVM es la gestión automática de la memoria. De vez en cuando, -la ejecución de un programa es detenida para que el recolector de basura entre en funcionamiento. Si el -programa que estamos analizando realiza alguna reserva de memoria (algo que la mayoría de programas hacen), -el recolector de basura podría entrar en acción, posiblemente distorsionando los resultados. Con el objetivo -de disminuir los efectos de la recolección de basura, el programa bajo estudio deberá ser ejecutado en -múltiples ocasiones para disparar numerosas recolecciones de basura. - -Una causa muy común que afecta de manera notable al rendimiento son las conversiones implícitas que se -llevan a cabo cuando se pasa un tipo primitivo a un método que espera un argumento genérico. En tiempo -de ejecución, los tipos primitivos con convertidos en los objetos que los representan, de manera que puedan -ser pasados como argumentos en los métodos que presentan parámetros genéricos. Este proceso implica un conjunto -extra de reservas de memoria y es más lento, ocasionando nueva basura en el heap. - -Cuando nos referimos al rendimiento en colecciones paralelas la contención de la memoria es un problema muy -común, dado que el desarrollador no dispone de un control explícito sobre la asignación de los objetos. -De hecho, debido a los efectos ocasionados por la recolección de basura, la contención puede producirse en -un estado posterior del ciclo de vida de la aplicación, una vez los objetos hayan ido circulando por la -memoria. Estos efectos deberán ser tenidos en cuenta en el momento en que se esté desarrollando un benchmark. - -## Ejemplo de microbenchmarking - -Numerosos enfoques permiten evitar los anteriores efectos durante el periodo de medición. -En primer lugar, el microbenchmark debe ser ejecutado el número de veces necesario que -permita asegurar que el compilador just-in-time ha compilado a código máquina y que -ha optimizado el código resultante. Esto es lo que comunmente se conoce como fase de -calentamiento. - -El microbenchmark debe ser ejecutado en una instancia independiente de la máquina virtual -con el objetivo de reducir la cantidad de ruido proveniente de la recolección de basura -de los objetos alocados por el propio benchmark o de compilaciones just-in-time que no -están relacionadas con el proceso que estamos midiendo. - -Deberá ser ejecutado utilizando la versión servidora de la máquina virtual, la cual lleva a -cabo un conjunto de optimizaciones mucho más agresivas. - -Finalmente, con el objetivo de reducir la posibilidad de que una recolección de basura ocurra -durante la ejecución del benchmark, idealmente, debería producirse un ciclo de recolección de basura antes -de la ejecución del benchmark, retrasando el siguiente ciclo tanto como sea posible. - -El trait `scala.testing.Benchmark` se predefine en la librería estándar de Scala y ha sido diseñado con -el punto anterior en mente. A continuación se muestra un ejemplo del benchmarking de un operación map -sobre un "trie" concurrente: - - import collection.parallel.mutable.ParTrieMap - import collection.parallel.ForkJoinTaskSupport - - object Map extends testing.Benchmark { - val length = sys.props("length").toInt - val par = sys.props("par").toInt - val partrie = ParTrieMap((0 until length) zip (0 until length): _*) - - partrie.tasksupport = new ForkJoinTaskSupport(new scala.concurrent.forkjoin.ForkJoinPool(par)) - - def run = { - partrie map { - kv => kv - } - } - } - -El método `run` encapsula el código del microbenchmark que será ejecutado de -manera repetitiva y cuyos tiempos de ejecución serán medidos. El anterior objeto `Map` extiende -el trait `scala.testing.Benchmark` y parsea los parámetros `par` (nivel de paralelismo) y -`length` (número de elementos en el trie). Ambos parámetros son especificados a través de -propiedades del sistema. - -Tras compilar el programa anterior, podríamos ejecutarlo tal y como se muestra a continuación: - - java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=1 -Dlength=300000 Map 10 - -El flag `server` indica que la máquina virtual debe ser utiliada en modo servidor. `cp` especifica -el classpath e incluye todos los archivos __.class__ en el directorio actual así como el jar de -la librería de Scala. Los argumentos `-Dpar` y `-Dlength` representan el nivel de paralelismo y -el número de elementos respectivamente. Por último, `10` indica el número de veces que el benchmark -debería ser ejecutado en una misma máquina virtual. - -Los tiempos de ejecución obtenidos estableciendo `par` a los valores `1`, `2`, `4` y `8` sobre un -procesador quad-core i7 con hyperthreading habilitado son los siguientes: - - Map$ 126 57 56 57 54 54 54 53 53 53 - Map$ 90 99 28 28 26 26 26 26 26 26 - Map$ 201 17 17 16 15 15 16 14 18 15 - Map$ 182 12 13 17 16 14 14 12 12 12 - -Podemos observar en la tabla anterior que el tiempo de ejecución es mayor durante las -ejecuciones iniciales, reduciéndose a medida que el código va siendo optimizado. Además, -podemos ver que el beneficio del hyperthreading no es demasiado alto en este ejemplo -concreto, puesto que el incremento de `4` a `8` hilos produce un incremento mínimo en -el rendimiento. - -## ¿Cómo de grande debe ser una colección para utilizar la versión paralela? - -Esta es pregunta muy común y la respuesta es algo complicada. - -El tamaño de la colección a partir de la cual la paralelización merece la pena -depende de numerosos factores. Algunos de ellos, aunque no todos, son: - -- Arquitectura de la máquina. Diferentes tipos de CPU ofrecen diferente características - de rendimiento y escalabilidad. Por ejemplo, si la máquina es multicore o presenta - múltiples procesadores comunicados mediante la placa base. - -- Versión y proveedor de la JVM. Diferentes máquinas virtuales llevan a cabo - diferentes optimizaciones sobre el código en tiempo de ejecución. Implementan - diferente gestion de memoria y técnicas de sincronización. Algunas de ellas no - soportan el `ForkJoinPool`, volviendo a `ThreadPoolExecutor`, lo cual provoca - una sobrecarga mucho mayor. - -- Carga de trabajo por elemento. Una función o un predicado para una colección - paralela determina cómo de grande es la carga de trabajo por elemento. Cuanto - menor sea la carga de trabajo, mayor será el número de elementos requeridos para - obtener acelaraciones cuando se está ejecutando en paralelo. - -- Uso de colecciones específicas. Por ejemplo, `ParArray` y - `ParTrieMap` tienen "splitters" que recorren la colección a diferentes - velocidades, lo cual implica que existe más trabajo por elemento en el - propio recorrido. - -- Operación específica. Por ejemplo, `ParVector` es mucho más lenta para los métodos - de transformación (cómo `filter`) que para métodos de acceso (como `foreach`). - -- Efectos colaterales. Cuando se modifica un area de memoria de manera concurrente o - se utiliza la sincronización en el cuerpo de un `foreach`, `map`, etc se puede - producir contención. - -- Gestión de memoria. Cuando se reserva espacio para muchos objectos es posible - que se dispare un ciclo de recolección de basura. Dependiendo de cómo se - distribuyan las referencias de los objetos el ciclo de recolección puede llevar - más o menos tiempo. - -Incluso de manera independiente, no es sencillo razonar sobre el conjunto de situaciones -anteriores y determinar una respuesta precisa sobre cuál debería ser el tamaño de la -colección. Para ilustrar de manera aproximada cuál debería ser el valor de dicho tamaño, -a continuación, se presenta un ejemplo de una sencilla operación de reducción, __sum__ en este caso, -libre de efectos colaterales sobre un vector en un procesador i7 quad-core (hyperthreading -deshabilitado) sobre JDK7 - - import collection.parallel.immutable.ParVector - - object Reduce extends testing.Benchmark { - val length = sys.props("length").toInt - val par = sys.props("par").toInt - val parvector = ParVector((0 until length): _*) - - parvector.tasksupport = new collection.parallel.ForkJoinTaskSupport(new scala.concurrent.forkjoin.ForkJoinPool(par)) - - def run = { - parvector reduce { - (a, b) => a + b - } - } - } - - object ReduceSeq extends testing.Benchmark { - val length = sys.props("length").toInt - val vector = collection.immutable.Vector((0 until length): _*) - - def run = { - vector reduce { - (a, b) => a + b - } - } - } - -La primera ejecución del benchmark utiliza `250000` elementos y obtiene los siguientes resultados para `1`, `2` y `4` hilos: - - java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=1 -Dlength=250000 Reduce 10 10 - Reduce$ 54 24 18 18 18 19 19 18 19 19 - java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=2 -Dlength=250000 Reduce 10 10 - Reduce$ 60 19 17 13 13 13 13 14 12 13 - java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=4 -Dlength=250000 Reduce 10 10 - Reduce$ 62 17 15 14 13 11 11 11 11 9 - -Posteriormente se decrementa en número de elementos hasta `120000` y se utilizan `4` hilos para comparar -el tiempo con la operación de reducción sobre un vector secuencial: - - java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=4 -Dlength=120000 Reduce 10 10 - Reduce$ 54 10 8 8 8 7 8 7 6 5 - java -server -cp .:../../build/pack/lib/scala-library.jar -Dlength=120000 ReduceSeq 10 10 - ReduceSeq$ 31 7 8 8 7 7 7 8 7 8 - -En este caso, `120000` elementos parece estar en torno al umbral. - -En un ejemplo diferente, utilizamos `mutable.ParHashMap` y el método `map` (un método de transformación) -y ejecutamos el siguiente benchmark en el mismo entorno: - - import collection.parallel.mutable.ParHashMap - - object Map extends testing.Benchmark { - val length = sys.props("length").toInt - val par = sys.props("par").toInt - val phm = ParHashMap((0 until length) zip (0 until length): _*) - - phm.tasksupport = new collection.parallel.ForkJoinTaskSupport(new scala.concurrent.forkjoin.ForkJoinPool(par)) - - def run = { - phm map { - kv => kv - } - } - } - - object MapSeq extends testing.Benchmark { - val length = sys.props("length").toInt - val hm = collection.mutable.HashMap((0 until length) zip (0 until length): _*) - - def run = { - hm map { - kv => kv - } - } - } - -Para `120000` elementos obtenemos los siguientes tiempos cuando el número de hilos oscila de `1` a `4`: - - java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=1 -Dlength=120000 Map 10 10 - Map$ 187 108 97 96 96 95 95 95 96 95 - java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=2 -Dlength=120000 Map 10 10 - Map$ 138 68 57 56 57 56 56 55 54 55 - java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=4 -Dlength=120000 Map 10 10 - Map$ 124 54 42 40 38 41 40 40 39 39 - -Ahora, si reducimos el número de elementos a `15000` y comparamos con el hashmap secuencial: - - java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=1 -Dlength=15000 Map 10 10 - Map$ 41 13 10 10 10 9 9 9 10 9 - java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=2 -Dlength=15000 Map 10 10 - Map$ 48 15 9 8 7 7 6 7 8 6 - java -server -cp .:../../build/pack/lib/scala-library.jar -Dlength=15000 MapSeq 10 10 - MapSeq$ 39 9 9 9 8 9 9 9 9 9 - -Para esta colección y esta operacion tiene sentido utilizar la versión paralela cuando existen más -de `15000` elementos (en general, es factible paralelizar hashmaps y hashsets con menos elementos de -los que serían requeridos por arrays o vectores). - -## Referencias - -1. [Anatomy of a flawed microbenchmark, Brian Goetz][1] -2. [Dynamic compilation and performance measurement, Brian Goetz][2] - - [1]: http://www.ibm.com/developerworks/java/library/j-jtp02225/index.html "flawed-benchmark" - [2]: http://www.ibm.com/developerworks/library/j-jtp12214/ "dynamic-compilation" \ No newline at end of file diff --git a/es/tutorials/scala-for-java-programmers.md b/es/tutorials/scala-for-java-programmers.md deleted file mode 100644 index d833018565..0000000000 --- a/es/tutorials/scala-for-java-programmers.md +++ /dev/null @@ -1,411 +0,0 @@ ---- -layout: overview -title: Tutorial de Scala para programadores Java -overview: scala-for-java-programmers - -discourse: true -language: es ---- - -Por Michel Schinz y Philipp Haller. -Traducción y arreglos Santiago Basulto. - -## Introducción - -Este documento provee una rápida introducción al lenguaje Scala como también a su compilador. Está pensado para personas que ya poseen cierta experiencia en programación y quieren una vista rápida de lo que pueden hacer con Scala. Se asume como un conocimiento básico de programación orientada a objetos, especialmente en Java. - -## Un primer ejemplo - -Como primer ejemplo, usaremos el programa *Hola mundo* estándar. No es muy fascinante, pero de esta manera resulta fácil demostrar el uso de herramientas de Scala sin saber demasiado acerca del lenguaje. Veamos como luce: - - object HolaMundo { - def main(args: Array[String]) { - println("¡Hola, mundo!") - } - } - -La estructura de este programa debería ser familiar para programadores Java: consiste de un método llamado `main` que toma los argumentos de la línea de comando (un array de objetos String) como parámetro; el cuerpo de este método consiste en una sola llamada al método predefinido `println` con el saludo amistoso como argumento. El método `main` no retorna un valor (se puede entender como un procedimiento). Por lo tanto, no es necesario que se declare un tipo retorno. - -Lo que es menos familiar a los programadores Java es la declaración de `object` que contiene al método `main`. Esa declaración introduce lo que es comúnmente conocido como *objeto singleton*, que es una clase con una sola instancia. Por lo tanto, dicha construcción declara tanto una clase llamada `HolaMundo` como una instancia de esa clase también llamada `HolaMundo`. Esta instancia es creada bajo demanda, es decir, la primera vez que es utilizada. - -El lector astuto notará que el método `main` no es declarado como `static`. Esto es así porque los miembros estáticos (métodos o campos) no existen en Scala. En vez de definir miembros estáticos, el programador de Scala declara estos miembros en un objeto singleton. - -### Compilando el ejemplo - -Para compilar el ejemplo utilizaremos `scalac`, el compilador de Scala. `scalac` funciona como la mayoría de los compiladores. Toma un archivo fuente como argumento, algunas opciones y produce uno o varios archivos objeto. Los archivos objeto que produce son archivos class de Java estándar. - -Si guardamos el programa anterior en un archivo llamado `HolaMundo.scala`, podemos compilarlo ejecutando el siguiente comando (el símbolo mayor `>` representa el prompt del shell y no debe ser escrita): - - > scalac HolaMundo.scala - -Esto generará algunos archivos class en el directorio actual. Uno de ellos se llamará `HolaMundo.class` y contiene una clase que puede ser directamente ejecutada utilizando el comando `scala`, como mostramos en la siguiente sección. - -### Ejecutando el ejemplo - -Una vez compilado, un programa Scala puede ser ejecutado utilizando el comando `scala`. Su uso es muy similar al comando `java` utilizado para ejecutar programas Java, y acepta las mismas opciones. El ejemplo de arriba puede ser ejecutado utilizando el siguiente comando, que produce la salida esperada: - - > scala -classpath . HolaMundo - - ¡Hola, mundo! - -## Interacción con Java - -Una de las fortalezas de Scala es que hace muy fácil interactuar con código Java. Todas las clases del paquete `java.lang` son importadas por defecto, mientras otras necesitan ser importadas explícitamente. - -Veamos un ejemplo que demuestra esto. Queremos obtener y formatear la fecha actual de acuerdo a convenciones utilizadas en un país específico, por ejemplo Francia. - -Las librerías de clases de Java definen clases de utilería poderosas, como `Date` y `DateFormat`. Ya que Scala interacciona fácilmente con Java, no es necesario implementar estas clases equivalentes en las librerías de Scala --podemos simplemente importar las clases de los correspondientes paquetes de Java: - - import java.util.{Date, Locale} - import java.text.DateFormat - import java.text.DateFormat._ - - object FrenchDate { - def main(args: Array[String]) { - val ahora = new Date - val df = getDateInstance(LONG, Locale.FRANCE) - println(df format ahora) - } - } - -Las declaraciones de importación de Scala lucen muy similares a las de Java, sin embargo, las primeras son bastante más poderosas. Múltiples clases pueden ser importadas desde el mismo paquete al encerrarlas en llaves como se muestra en la primer línea. Otra diferencia es que podemos importar todos los nombres de un paquete o clase, utilizando el carácter guión bajo (`_`) en vez del asterisco (`*`). Eso es porque el asterisco es un identificador válido en Scala (quiere decir que por ejemplo podemos nombrar a un método `*`), como veremos más adelante. - -La declaración `import` en la tercer línea por lo tanto importa todos los miembros de la clase `DateFormat`. Esto hace que el método estático `getDateInstance` y el campo estático `LONG` sean directamente visibles. - -Dentro del método `main` primero creamos una instancia de la clase `Date` la cual por defecto contiene la fecha actual. A continuación definimos un formateador de fechas utilizando el método estático `getDateInstance` que importamos previamente. Finalmente, imprimimos la fecha actual formateada de acuerdo a la instancia de `DateFormat` que fue "localizada". Esta última línea muestra una propiedad interesante de la sintaxis de Scala. Los métodos que toman un solo argumento pueden ser usados con una sintaxis de infijo Es decir, la expresión - - df format ahora - -es solamente otra manera más corta de escribir la expresión: - - df.format(ahora) - -Esto parece tener como un detalle sintáctico menor, pero tiene importantes consecuencias, una de ellas la exploraremos en la próxima sección. - -Para concluir esta sección sobre la interacción con Java, es importante notar que es también posible heredar de clases Java e implementar interfaces Java directamente en Scala. - -## Todo es un objeto - -Scala es un lenguaje puramente orientado a objetos en el sentido de que *todo* es un objeto, incluyendo números o funciones. Difiere de Java en este aspecto, ya que Java distingue tipos primitivos (como `boolean` e `int`) de tipos referenciales, y no nos permite manipular las funciones como valores. - -### Los números son objetos - -Ya que los números son objetos, estos también tienen métodos. De hecho, una expresión aritmética como la siguiente: - - 1 + 2 * 3 / x - -Consiste exclusivamente de llamadas a métodos, porque es equivalente a la siguiente expresión, como vimos en la sección anterior: - - (1).+(((2).*(3))./(x)) - -Esto también indica que `+`, `*`, etc. son identificadores válidos en Scala. - -Los paréntesis alrededor de los números en la segunda versión son necesarios porque el analizador léxico de Scala usa la regla de "mayor coincidencia". Por lo tanto partiría la siguiente expresión: - - 1.+(2) - -En estas partes: `1.`, `+`, y `2`. La razón que esta regla es elegida es porque `1.` es una coincidencia válida y es mayor que `1`, haciendo a este un `Double` en vez de un `Int`. Al escribir la expresión así: - - (1).+(2) - -previene que el `1` sea tomado como un `Double`. - -### Las funciones son objetos - -Tal vez suene más sorprendente para los programadores Java, las funciones en Scala también son objetos. Por lo tanto es posible pasar funciones como argumentos, almacenarlas en variables, y retornarlas desde otras funciones. Esta habilidad de manipular funciones como valores es una de las valores fundamentales de un paradigma de programación muy interesante llamado *programación funcional*. - -Como un ejemplo muy simple de por qué puede ser útil usar funciones como valores consideremos una función *temporizador* (o timer, en inglés) cuyo propósito es realizar alguna acción cada un segundo. ¿Cómo pasamos al temporizador la acción a realizar? Bastante lógico, como una función. Este simple concepto de pasar funciones debería ser familiar para muchos programadores: es generalmente utilizado en código relacionado con Interfaces gráficas de usuario (GUIs) para registrar "retrollamadas" (call-back en inglés) que son invocadas cuando un evento ocurre. - -En el siguiente programa, la función del temporizador se llama `unaVezPorSegundo` y recibe una función call-back como argumento. El tipo de esta función es escrito de la siguiente manera: `() => Unit` y es el tipo de todas las funciones que no toman argumentos ni retornan valores (el tipo `Unit` es similar a `void` en Java/C/C++). La función principal de este programa simplemente invoca esta función temporizador con una call-back que imprime una sentencia en la terminal. En otras palabras, este programa imprime interminablemente la sentencia "El tiempo vuela como una flecha" cada segundo. - - object Temporizador { - def unaVezPorSegundo(callback: () => Unit) { - while (true) { callback(); Thread sleep 1000 } - } - def tiempoVuela() { - println("El tiempo vuela como una flecha...") - } - def main(args: Array[String]) { - unaVezPorSegundo(tiempoVuela) - } - } - -_Nota: si nunca tuviste experiencias previas con programación funcional te recomiendo que te tomes unos segundos para analizar cuando se utilizan paréntesis y cuando no en los lugares donde aparece *callback*. Por ejemplo, dentro de la declaración de `unaVezPorSegundo` no aparece, ya que se trata de la función como un "valor", a diferencia de cómo aparece dentro del método, ya que en ese caso se la está invocando (por eso los paréntesis)._ -Note that in order to print the string, we used the predefined method -`println` instead of using the one from `System.out`. - -#### Funciones anónimas - -El programa anterior es fácil de entender, pero puede ser refinado aún más. Primero que nada es interesante notar que la función `tiempoVuela` está definida solamente para ser pasada posteriormente a la función `unaVezPorSegundo`. Tener que nombrar esa función, que es utilizada solamente una vez parece un poco innecesario y sería bueno poder construirla justo cuando sea pasada a `unaVezPorSegundo`. Esto es posible en Scala utilizando *funciones anónimas*, que son exactamente eso: funciones sin nombre. La versión revisada de nuestro temporizador utilizando una función anónima luce así: - - object TemporizadorAnonimo { - def unaVezPorSegundo(callback: () => Unit) { - while (true) { callback(); Thread sleep 1000 } - } - def main(args: Array[String]) { - unaVezPorSegundo( - () => println("El tiempo vuela como una flecha...") - ) - } - } - -La presencia de una función anónima en este ejemplo es revelada por la flecha a la derecha `=>` que separa los argumentos de la función del cuerpo de esta. En este ejemplo, la lista de argumentos está vacía, como se ve por el par de paréntesis vacíos a la izquierda de la flecha. El cuerpo de la función es el mismo que en `tiempoVuela` del programa anterior. - -## Clases - -Como hemos visto anteriormente, Scala es un lenguaje orientado a objetos, y como tal tiene el concepto de Clase (en realidad existen lenguajes orientados a objetos que no cuentan con el concepto de clases, pero Scala no es uno de ellos). Las clases en Scala son declaradas utilizando una sintaxis que es cercana a la de Java. Una diferencia importante es que las clases en Scala pueden tener parámetros. Ilustramos esto en el siguiente ejemplo, la definición de un número complejo: - - class Complejo(real: Double, imaginaria: Double) { - def re() = real - def im() = imaginaria - } - -Esta clase compleja toma dos argumentos, que son las partes real e imaginarias de un número complejo. Estos argumentos deben ser pasados cuando se crea una instancia de la clase `Complejo`, de la siguiente manera: - - new Complejo(1.5, 2.3) - -La clase contiene dos métodos llamados `re` e `im`, que proveen acceso a las dos partes del número. - -Debe notarse que el tipo de retorno de estos dos métodos no está expresado explícitamente. Será inferido automáticamente por el compilador, que primero mira la parte derecha de estos métodos y puede deducir que ambos retornan un valor de tipo `Double`. - -El compilador no es siempre capaz de inferir los tipos como lo hace aquí, y desafortunadamente no existe una regla simple para saber cuándo será y cuándo no. En la práctica, esto generalmente no es un problema ya que el compilador se queja cuando no es capaz de inferir un tipo que no fue explícitamente fijado. Como regla simple, los programadores de Scala novatos deberían tratar de omitir las declaraciones de tipos que parecen ser simples de deducir del contexto y ver si el compilador no lanza errores. Después de algún tiempo, el programador debería tener una buena idea de cuando omitir tipos y cuando explicitarlos. - -### Métodos sin argumentos - -Un pequeño problema de los métodos `re` e `im` es que para poder llamarlos es necesario agregar un par de paréntesis vacíos después de sus nombres, como muestra el siguiente ejemplo: - - object NumerosComplejos { - def main(args: Array[String]) { - val c = new Complejo(1.2, 3.4) - println("Parte imaginaria: " + c.im()) - } - } - -Sería mejor poder acceder las partes imaginarias y reales como si fueran campos, sin poner los paréntesis vacíos. Esto es perfectamente realizable en Scala, simplemente al definirlos como *métodos sin argumentos*. Tales métodos difieren de los métodos con cero o más argumentos en que no tienen paréntesis después de su nombre, tanto en la definición como en el uso. Nuestra clase `Complejo` puede ser reescrita así: - - class Complejo(real: Double, imaginaria: Double) { - def re = real - def im = imaginaria - } - - -### Herencia y sobreescritura - -Todas las clases en Scala heredan de una superclase. Cuando ninguna superclase es especificada, como es el caso de `Complejo` se utiliza implícitamente `scala.AnyRef`. - -Es posible sobreescribir métodos heredados de una superclase en Scala. Aunque es necesario explicitar específicamente que un método sobreescribe otro utilizando el modificador `override`, de manera de evitar sobreescrituras accidentales. Como ejemplo, nuestra clase `Complejo` puede ser aumentada con la redefinición del método `toString` heredado de `Object`. - - class Complejo(real: Double, imaginaria: Double) { - def re = real - def im = imaginaria - override def toString() = - "" + re + (if (im < 0) "" else "+") + im + "i" - } - -## Clases Case y Reconocimiento de patrones - -Un tipo de estructura de datos que aparece seguido en programas es el Árbol. Por ejemplo, los intérpretes y compiladores usualmente representan los programas internamente como árboles; los documentos XML son árboles; y muchos otros tipos de contenedores están basados en árboles, como los árboles rojo y negro. - -Ahora examinaremos cómo estos árboles son representados y manipulados en Scala mediante un pequeño programa que oficie de calculadora. El objetivo de este programa es manipular expresiones aritméticas simples compuestas de sumas de enteros y variables. Dos ejemplos de estas expresiones pueden ser: `1+2` y `(x+x)+(7+y)`. - -Primero tenemos que decidir una representación para tales expresiones. La más natural es un árbol, donde los nodos son las operaciones (la adición en este caso) y las hojas son valores (constantes o variables). - -En Java, un árbol así sería representado utilizando una superclase abstracta para los árboles, y una subclase concreta por nodo u hoja. En un lenguaje de programación funcional uno utilizaría un tipo de dato algebraico para el mismo propósito. Scala provee el concepto de *clases case* que está en el medio de los dos conceptos anteriores. Aquí mostramos como pueden ser usadas para definir el tipo de los árboles en nuestro ejemplo: - - abstract class Arbol - case class Sum(l: Arbol, r: Arbol) extends Arbol - case class Var(n: String) extends Arbol - case class Const(v: Int) extends Arbol - -El hecho de que las clases `Sum`, `Var` y `Const` sean declaradas como clases case significa que dififieren de las clases normales en varios aspectos: - -- no es obligatorio utilizar la palabra clave `new` para crear - instancias de estas clases (es decir, se puede escribir `Const(5)` - en lugar de `new Const(5)`), -- se crea automáticamente un "getter" (un método para obtener el valor) - para los parámetros utilizados en el constructor (por ejemplo es posible - obtener el valor de `v` de una instancia `c` de la clase `Const` de la - siguiente manera: `c.v`), -- se proveen definiciones por defecto de los métodos `equals` y `hashCode`, - que trabajan sobre la estructura de las instancias y no sobre su identidad, -- se crea una definición por defecto del método `toString` que - imprime el valor de una forma "tipo código) (ej: la expresión - del árbol `x+1` se imprimiría `Sum(Var(x),Const(1))`), -- las instancias de estas clases pueden ser descompuestas - mediante *reconocimiento de patrones* (pattern matching) - como veremos más abajo. - -Ahora que hemos definido el tipo de datos para representar nuestra expresión aritmética podemos empezar definiendo operaciones para manipularlas. Empezaremos con una función para evaluar una expresión en un *entorno*. El objetivo del entorno es darle valores a las variables. Por ejemplo, la expresión `x+1` evaluada en un entorno que asocia el valor `5` a la variable `x`, escrito `{ x -> 5 }`, da como resultado `6`. - -Por lo tanto tenemos que encontrar una manera de representar entornos. Podríamos por supuesto utilizar alguna estructura de datos asociativa como una tabla hash, pero podemos directamente utilizar funciones! Un entorno realmente no es nada más que una función la cual asocia valores a variables. El entorno `{ x -> 5 }` mostrado anteriormente puede ser fácilmente escrito de la siguiente manera en Scala: - - { case "x" => 5 } - -Esta notación define una función la cual, dado un string `"x"` como argumento retorna el entero `5`, y falla con una excepción si no fuera así. - -Antes de escribir la función evaluadora, démosle un nombre al tipo de los entornos. Podríamos por supuesto simplemente utilizar `String => Int` para los entornos, pero simplifica el programa introducir un nombre para este tipo, y hace que los futuros cambios sean más fáciles. Esto lo realizamos de la siguiente manera: - - type Entorno = String => Int - -De ahora en más, el tipo `Entorno` puede ser usado como un alias del tipo de funciones definidas de `String` a `Int`. - -Ahora podemos dar la definición de la función evaluadora. Conceptualmente, es muy sencillo: el valor de una suma de dos expresiones es simplemente la suma de los valores de estas expresiones; el valor de una variable es obtenido directamente del entorno; y el valor de una constante es la constante en sí misma. Expresar esto en Scala no resulta para nada difícil: - - def eval(a: Arbol, ent: Entorno): Int = a match { - case Sum(i, d) => eval(i, ent) + eval(d, env) - case Var(n) => ent(n) - case Const(v) => v - } - -Esta función evaluadora función realizando un *reconocimiento de patrones* (pattern matching) en el árbol `a`. Intuitivamente, el significado de la definición de arriba debería estar claro: - -1. Primero comprueba si el árbol `t`es una `Sum`, y si lo es, asocia el sub-arbol izquierdo a una nueva variable llamada `i` y el sub-arbol derecho a la variable `r`, y después procede con la evaluación de la expresión que sigue a la flecha (`=>`); esta expresión puede (y hace) uso de las variables asociadas por el patrón que aparece del lado izquierdo de la flecha. -2. si la primer comprobación (la de `Sum`) no prospera, es decir que el árbol no es una `Sum`, sigue de largo y comprueba si `a` es un `Var`; si lo es, asocia el nombre contenido en el nodo `Var` a la variable `n` y procede con la parte derecha de la expresión. -3. si la segunda comprobación también falla, resulta que `a` no es un `Sum` ni un `Var`, por lo tanto comprueba que sea un `Const`, y si lo es, asocia el valor contenido en el nodo `Const` a la variable `v`y procede con el lado derecho. -4. finalmente, si todos las comprobaciones fallan, una excepción es lanzada para dar cuenta el fallo de la expresión; esto puede pasar solo si existen más subclases de `Arbol`. - -Hemos visto que la idea básica del reconocimiento de patrones es intentar coincidir un valor con una serie de patrones, y tan pronto como un patrón coincida, extraer y nombrar las varias partes del valor para finalmente evaluar algo de código que típicamente hace uso de esas partes nombradas. - -Un programador con experiencia en orientación a objetos puede preguntarse por qué no definimos `eval` como un método de la clase `Arbol` y sus subclases. En realidad podríamos haberlo hecho, ya que Scala permite la definición de métodos en clases case tal como en clases normales. Por lo tanto decidir en usar reconocimiento de patrones o métodos es una cuestión de gustos, pero también tiene grandes implicancias en cuanto a la extensibilidad: - -- cuando usamos métodos, es fácil añadir un nuevo tipo de nodo ya que esto puede ser realizado simplemente al definir una nueva subclase de `Arbol`; por otro lado, añadir una nueva operación para manipular el árbol es tedioso, ya que requiere la modificación en todas las subclases. - -- cuando utilizamos reconocimiento de patrones esta situación es inversa: agregar un nuevo tipo de nodo requiere la modificación de todas las funciones que hacen reconocimiento de patrones sobre el árbol, para tomar en cuenta un nuevo nodo; pero por otro lado agregar una nueva operación fácil, solamente definiendolo como una función independiente. - -Para explorar un poco más esto de pattern matching definamos otra operación aritmética: derivación simbólica. El lector recordará las siguientes reglas sobre esta operación: - -1. la derivada de una suma es la suma de las derivadas, -2. la derivada de una variable `v` es uno (1) si `v` es la variable relativa a la cual la derivada toma lugar, y cero (0)de otra manera, -3. la derivada de una constante es cero (0). - -Estas reglas pueden ser traducidas casi literalmente en código Sclaa, para obtener la siguiente definición. - - def derivada(a: Arbol, v: String): Arbol = a match { - case Sum(l, r) => Sum(derivada(l, v), derivada(r, v)) - case Var(n) if (v == n) => Const(1) - case _ => Const(0) - } - -Esta función introduce dos nuevos conceptos relacionados al pattern matching. Primero que nada la expresión `case` para variables tienen una *guarda*, una expresión siguiendo la palabra clave `if`. Esta guarda previene que el patrón concuerde al menos que la expresión sea verdadera. Aquí es usada para asegurarse que retornamos la constante 1 solo si el nombre de la variable siendo derivada es el mismo que la variable derivada `v`. El segundo concepto nuevo usado aquí es el *comodín*, escrito con el guión bajo `_`, que coincide con cualquier valor que aparezca, sin darle un nombre. - -No hemos explorado el completo poder del pattern matching aún, pero nos detendremos aquí para mantener este documento corto. Todavía nos queda pendiente ver cómo funcionan las dos funciones de arriba en un ejemplo real. Para ese propósito, escribamos una función main simple que realice algunas operaciones sobre la expresión `(x+x)+(7+y)`: primero computa su valor en el entorno `{ x -> 5, y -> 7 }` y después computa su derivada con respecto a `x` y después a `y`. - - def main(args: Array[String]) { - val exp: Arbol = Sum(Sum(Var("x"),Var("x")),Sum(Const(7),Var("y"))) - val ent: Entonrno = { case "x" => 5 case "y" => 7 } - println("Expresión: " + exp) - println("Evaluación con x=5, y=7: " + eval(exp, ent)) - println("Derivada con respecto a x:\n " + derivada(exp, "x")) - println("Derivada con respecto a y:\n " + derivada(exp, "y")) - } - -Al ejecutar este programa obtenemos el siguiente resultado: - - Expresión: Sum(Sum(Var(x),Var(x)),Sum(Const(7),Var(y))) - Evaluación con x=5, y=7: 24 - Derivada con respecto a x: - Sum(Sum(Const(1),Const(1)),Sum(Const(0),Const(0))) - Derivada con respecto a y: - Sum(Sum(Const(0),Const(0)),Sum(Const(0),Const(1))) - -Al examinar la salida vemos que el resultado de la derivada debería ser simplificado antes de ser presentado al usuario. Definir una función de simplificación básica utilizando reconocimiento de patrones es un problema interesante (y, por no decir complejo, que necesita una solución astuta), lo dejamos para un ejercicio para el lector. - -## Traits - -_Nota: La palabra Trait(/treɪt/, pronunciado Treit) puede ser traducida literalmente como "Rasgo". De todas maneras decido utilizar la notación original por ser un concepto muy arraigado a Scala_ - -Aparte de poder heredar código de una super clase, una clase en Scala puede también importar código de uno o varios *traits*. - -Tal vez la forma más fácil para un programador Java de entender qué son los traits es verlos como interfaces que también pueden contener código. En Scala, cuando una clase hereda de un trait, implementa la interface de ese trait, y hereda todo el código contenido en el trait. - -Para ver la utilidad de los traits, veamos un ejemplo clásico: objetos ordenados. Generalmente es útil tener la posibilidad de comparar objetos de una clase dada entre ellos, por ejemplo, para ordenarlos. En Java, los objetos que son comparables implementan la interfaz `Comparable`. En Scala, podemos hacer algo un poco mejor que en Java al definir un trait equivalente `Comparable` que invocará a `Ord`. - -Cuando comparamos objetos podemos utilizar seis predicados distintos: menor, menor o igual, igual, distinto, mayor o igual y mayor. De todas maneras, definir todos estos es fastidioso, especialmente que cuatro de estos pueden ser expresados en base a los otros dos. Esto es, dados los predicados "igual" y "menor" (por ejemplo), uno puede expresar los otros. En Scala, todas estas observaciones pueden ser fácilmente capturadas mediante la siguiente declaración de un Trait: - - trait Ord { - def < (that: Any): Boolean - def <=(that: Any): Boolean = (this < that) || (this == that) - def > (that: Any): Boolean = !(this <= that) - def >=(that: Any): Boolean = !(this < that) - } - -Esta definición crea un nuevo tipo llamado `Ord` el cual juega el mismo rol que la interfaz `Comparable`, como también provee implementaciones de tres predicados en términos de un cuarto, abstracto. Los predicados para igualidad y su inverso (distinto, no igual) no aparecen aquí ya que por defecto están presenten en todos los objetos. - -El tipo `Any` el cual es usado arriba es el supertipo de todos los otros tipos en Scala. Puede ser visto como una versión más general del tipo `Object` en Java, ya que `Any` también es supertipo de `Int`, `Float`, etc. cosa que no se cumple en Java (`int` por ejemplo es un tipo primitivo). - -Para hacer a un objeto de la clase comparable es suficiente definir los predicados que comprueban la igualdad y la inferioridad y mezclar la clase `Ord` de arriba. Como un ejemplo, definamos una clase `Fecha` que representa fechas en el calendario gregoriano. - - class Fecha(d: Int, m: Int, a: Int) extends Ord { - def anno = a - def mes = m - def dia = d - override def toString(): String = anno + "-" + mes + "-" + dia - -La parte importante aquí es la declaración `extends Ord` la cual sigue al nombre de la clase y los parámetros. Declara que la clase `Fecha` hereda del trait `Ord`. - -Después redefinimos el método `equals`, heredado de `Object`, para comparar correctamente fechas mediante sus campos individuales. La implementación por defecto de `equals` no es utilizable, porque como en Java, compara los objetos físicamente. Por lo tanto llegamos a esto: - - override def equals(that: Any): Boolean = - that.isInstanceOf[Fecha] && { - val o = that.asInstanceOf[Fecha] - o.dia== dia && o.mes == mes && o.anno== anno - } - -Este método utiliza el método predefinido `isInstanceOf` ("es instancia de") y `asInstanceOf` ("como instancia de"). El primero `isInstanceOf` se corresponde con el operador java `instanceOf` y retorna `true` si y solo si el objeto en el cual es aplicado es una instancia del tipo dado. El segundo, `asInstanceOf`, corresponde al operador de casteo en Java: si el objeto es una instancia de un tipo dado, esta es vista como tal, de otra manera se lanza una excepción `ClassCastException`. - -Finalmente el último método para definir es el predicado que comprueba la inferioridad. Este hace uso de otro método predefinido, `error` que lanza una excepción con el mensaje de error provisto. - - def <(that: Any): Boolean = { - if (!that.isInstanceOf[Fecha]) - error("no se puede comparar" + that + " y una fecha") - - val o = that.asInstanceOf[Fecha] - (anno < o.anno) || - (anno== o.anno && (mes < o.mes || - (mes == o.mes && dia < o.dia))) - } - -Esto completa la definición de la clase `Fecha`. Las instancias de esta clase pueden ser vistas tanto como fechas o como objetos comparables. Además, todas ellas definen los seis predicados de comparación mencionados arriba: `equals` y `<` porque aparecen directamente en la definición de la clase `Fecha` y los otros porque son heredados del trait `Ord`. - -Los traits son útiles en muchas otras más situaciones que las aquí mostrada, pero discutir sus aplicaciones está fuera del alcance de este documento. - -## Tipos Genéricos - -_Nota: El diseñador de los tipos genéricos en Java fue nada más ni nada menos que Martin Odersky, el diseñador de Scala._ - -La última característica de Scala que exploraremos en este tutorial es la de los tipos genéricos. Los programadores de Java deben estar bien al tanto de los problemas que genera la falta de genéricos en su lenguaje, lo cual es solucionado en Java 1.5. - -Los tipos genéricos proveen al programador la habilidad de escribir código parametrizado por tipos. Por ejemplo, escribir una librería para listas enlazadas se enfrenta al problema de decidir qué tipo darle a los elementos de la lista. Ya que esta lista está pensada para ser usada en diferentes contextos, no es posible decidir que el tipo de elementos sea, digamos, `Int`. Esto sería completamente arbitrario y muy restrictivo. - -Los programadores Java cuentan como último recurso con `Object`, que es el supertipo de todos los objetos. Esta solución de todas maneras está lejos de ser ideal, ya que no funciona con tipos primitivos (`int`, `long`, `float`, etc.) e implica que el programador tenga que realizar muchos casteos de tipos en su programa. - -Scala hace posible definir clases genéricas (y métodos) para resolver este problema. Examinemos esto con un ejemplo del contenedor más simple posible: una referencia, que puede estar tanto vacía como apuntar a un objeto de algún tipo. - - class Referencia[T] { - private var contenido: T = _ - def set(valor: T) { contenido = valor } - def get: T = contenido - } - -La clase `Referencia` es parametrizada por un tipo llamado `T`, que es el tipo de sus elementos. Este tipo es usado en el cuerpo de la clase como el tipo de la variable `contenido`, el argumento del método `set` y el tipo de retorno del método `get`. - -El ejemplo anterior introduce a las variables en Scala, que no deberían requerir mayor explicación. Es interesante notar que el valor inicial dado a la variable `contenido` es `_`, que representa un valor por defecto. Este valor por defecto es 0 para tipos numéricos, `false` para tipos `Boolean`, `()` para el tipo `Unit` y `null` para el resto de los objetos. - -Para utilizar esta clase `Referencia`, uno necesita especificar qué tipo utilizar por el parámetro `T`, es decir, el tipo del elemento contenido por la referencia. Por ejemplo, para crear y utilizar una referencia que contenga un entero, podríamos escribir lo siguiente: - - object ReferenciaEntero { - def main(args: Array[String]) { - val ref = new Referencia[Int] - ref.set(13) - println("La referncia tiene la mitad de " + (ref.get * 2)) - } - } - -Como puede verse en el ejemplo, no es necesario castear el valor retornado por el método `get` antes de usarlo como un entero. Tampoco es posible almacenar otra cosa que no sea un entero en esa referencia en particular, ya que fue declarada como contenedora de un entero. - -## Conclusión - -Scala es un lenguaje tremendamente poderoso que ha sabido heredar las mejores cosas de cada uno de los lenguajes más exitosos que se han conocido. Java no es la excepción, y comparte muchas cosas con este. La diferencia que vemos es que para cada uno de los conceptos de Java, Scala los aumenta, refina y mejora. Poder aprender todas las características de Scala nos equipa con más y mejores herramientas a la hora de escribir nuestros programas. -Si bien la programación funcional no ha sido una característica de Java, el programador experimentado puede notar la falta de soporte de este paradigma en múltiples ocasiones. El solo pensar en el código necesario para proveer a un `JButton` con el código que debe ejecutar al ser presionado nos muestra lo necesario que sería contar con herramientas funcionales. Recomendamos entonces tratar de ir incorporando estas características, por más que sea difícil para el programador Java al estar tan acostumbrado al paradigma imperativo de este lenguaje. - -Este documento dio una rápida introducción al lenguaje Scala y presento algunos ejemplos básicos. El lector interesado puede seguir, por ejemplo, leyendo el *Tutorial de Scala* que figura en el sitio de documentación, o *Scala by Example* (en inglés). También puede consultar la especificación del lenguaje cuando lo desee. diff --git a/es/tutorials/tour/_posts/2017-02-13-abstract-types.md b/es/tutorials/tour/_posts/2017-02-13-abstract-types.md deleted file mode 100644 index e08037dd33..0000000000 --- a/es/tutorials/tour/_posts/2017-02-13-abstract-types.md +++ /dev/null @@ -1,72 +0,0 @@ ---- -layout: tutorial -title: Tipos Abstractos - -discourse: false - -tutorial: scala-tour -categories: tour -num: 2 -outof: 33 -language: es - -next-page: annotations -previous-page: tour-of-scala ---- - -En Scala, las cases son parametrizadas con valores (los parámetros de construcción) y con tipos (si las clases son [genéricas](generic-classes.html)). Por razones de consistencia, no es posible tener solo valores como miembros de objetos; tanto los tipos como los valores son miembros de objetos. Además, ambos tipos de miembros pueden ser concretos y abstractos. -A continuación un ejemplo el cual define de forma conjunta una asignación de valor tardía y un tipo abstracto como miembros del [trait](traits.html) `Buffer`. - - trait Buffer { - type T - val element: T - } - -Los *tipos abstractos* son tipos los cuales su identidad no es precisamente conocida. En el ejemplo anterior, lo único que sabemos es que cada objeto de la clase `Buffer` tiene un miembro de tipo `T`, pero la definición de la clase `Buffer` no revela qué tipo concreto se corresponde con el tipo `T`. Tal como las definiciones de valores, es posible sobrescribir las definiciones de tipos en subclases. Esto permite revelar más información acerca de un tipo abstracto al acotar el tipo ligado (el cual describe las posibles instancias concretas del tipo abstracto). - -En el siguiente programa derivamos la clase `SeqBuffer` la cual nos permite almacenar solamente sequencias en el buffer al estipular que el tipo `T` tiene que ser un subtipo de `Seq[U]` para un nuevo tipo abstracto `U`: - - abstract class SeqBuffer extends Buffer { - type U - type T <: Seq[U] - def length = element.length - } - -Traits o [clases](classes.html) con miembros de tipos abstractos son generalmente usados en combinación con instancias de clases anónimas. Para ilustrar este concepto veremos un programa el cual trata con un buffer de sequencia que se remite a una lista de enteros. - - 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) - } - -El tipo retornado por el método `newIntSeqBuf` está ligado a la especialización del trait `Buffer` en el cual el tipo `U` es ahora equivalente a `Int`. Existe un tipo alias similar en la instancia de la clase anónima dentro del cuerpo del método `newIntSeqBuf`. En ese lugar se crea una nueva instancia de `IntSeqBuffer` en la cual el tipo `T` está ligado a `List[Int]`. - -Es necesario notar que generalmente es posible transformar un tipo abstracto en un tipo paramétrico de una clase y viceversa. A continuación se muestra una versión del código anterior el cual solo usa tipos paramétricos. - - 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) - } - -Nótese que es necesario usar [variance annotations](variances.html) aquí; de otra manera no sería posible ocultar el tipo implementado por la secuencia concreta del objeto retornado por `newIntSeqBuf`. Además, existen casos en los cuales no es posible remplazar tipos abstractos con tipos parametrizados. diff --git a/es/tutorials/tour/_posts/2017-02-13-annotations.md b/es/tutorials/tour/_posts/2017-02-13-annotations.md deleted file mode 100644 index b9e56407e8..0000000000 --- a/es/tutorials/tour/_posts/2017-02-13-annotations.md +++ /dev/null @@ -1,126 +0,0 @@ ---- -layout: tutorial -title: Anotaciones - -discourse: false - -tutorial: scala-tour -categories: tour -num: 3 -language: es - -next-page: classes -previous-page: abstract-types ---- - -Las anotaciones sirven para asociar meta-información con definiciones. - -Una anotación simple tiene la forma `@C` o `@C(a1, .., an)`. Aquí, `C` es un constructor de la clase `C`, que debe extender de la clase `scala.Annotation`. Todos los argumentos de construcción dados `a1, .., an` deben ser expresiones constantes (es decir, expresiones de números literales, strings, clases, enumeraciones de Java y arrays de una dimensión de estos valores). - -Una anotación se aplica a la primer definición o declaración que la sigue. Más de una anotación puede preceder una definición o declaración. El orden en que es dado estas anotaciones no importa. - -El significado de las anotaciones _depende de la implementación_. En la plataforma de Java, las siguientes anotaciones de Scala tienen un significado estandar. - -| Scala | Java | -| ------ | ------ | -| [`scala.SerialVersionUID`](https://www.scala-lang.org/api/current/scala/SerialVersionUID.html) | [`serialVersionUID`](http://java.sun.com/j2se/1.5.0/docs/api/java/io/Serializable.html#navbar_bottom) (campo, variable) | -| [`scala.deprecated`](https://www.scala-lang.org/api/current/scala/deprecated.html) | [`java.lang.Deprecated`](http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Deprecated.html) | -| [`scala.inline`](https://www.scala-lang.org/api/current/scala/inline.html) (desde 2.6.0) | sin equivalente | -| [`scala.native`](https://www.scala-lang.org/api/current/scala/native.html) (desde 2.6.0) | [`native`](http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html) (palabra clave) | -| [`scala.remote`](https://www.scala-lang.org/api/current/scala/remote.html) | [`java.rmi.Remote`](http://java.sun.com/j2se/1.5.0/docs/api/java/rmi/Remote.html) | -| [`scala.throws`](https://www.scala-lang.org/api/current/scala/throws.html) | [`throws`](http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html) (palabra clave) | -| [`scala.transient`](https://www.scala-lang.org/api/current/scala/transient.html) | [`transient`](http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html) (palabra clave) | -| [`scala.unchecked`](https://www.scala-lang.org/api/current/scala/unchecked.html) (desde 2.4.0) | sin equivalente | -| [`scala.volatile`](https://www.scala-lang.org/api/current/scala/volatile.html) | [`volatile`](http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html) (palabra clave) | -| [`scala.beans.BeanProperty`](https://www.scala-lang.org/api/current/scala/beans/BeanProperty.html) | [`Design pattern`](http://docs.oracle.com/javase/tutorial/javabeans/writing/properties.html) | - -En el siguiente ejemplo agregamos la anotación `throws` a la definición del método `read` de manera de capturar la excepción lanzada en el programa principal de Java. - -> El compilador de Java comprueba que un programa contenga manejadores para [excepciones comprobadas](http://docs.oracle.com/javase/specs/jls/se5.0/html/exceptions.html) al analizar cuales de esas excepciones comprobadas pueden llegar a lanzarse en la ejecución de un método o un constructor. Por cada excepción comprobada que sea un posible resultado, la cláusula **throws** debe para ese método o constructor debe ser mencionada en la clase de esa excepción o una de las superclases. -> Ya que Scala no tiene excepciones comprobadas, los métodos en Scala deben ser anotados con una o más anotaciones `throws` para que el código Java pueda capturar las excepciones lanzadas por un método de Scala. - - package examples - import java.io._ - class Reader(fname: String) { - private val in = new BufferedReader(new FileReader(fname)) - @throws(classOf[IOException]) - def read() = in.read() - } - -El siguiente programa de Java imprime en consola los contenidos del archivo cuyo nombre es pasado como primer argumento al método `main`. - - package test; - import examples.Reader; // Scala class !! - 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()); - } - } - } - -Si comentamos la anotación `throws` en la clase `Reader` se produce el siguiente error cuando se intenta compilar el programa principal de Java: - - Main.java:11: exception java.io.IOException is never thrown in body of - corresponding try statement - } catch (java.io.IOException e) { - ^ - 1 error - -### Anotaciones en Java ### - -**Nota:** Asegurate de usar la opción `-target:jvm-1.5` con anotaciones de Java. - -Java 1.5 introdujo metadata definida por el usuario en la forma de [anotaciones](http://java.sun.com/j2se/1.5.0/docs/guide/language/annotations.html). Una característica fundamental de las anotaciones es que se basan en pares nombre-valor específicos para inicializar sus elementos. Por ejemplo, si necesitamos una anotación para rastrear el código de alguna clase debemos definirlo así: - - @interface Source { - public String URL(); - public String mail(); - } - -Y después utilizarlo de la siguiente manera - - @Source(URL = "http://coders.com/", - mail = "support@coders.com") - public class MyClass extends HisClass ... - -Una anotación en Scala se asemeja a una invocación a un constructor. Para instanciar una anotación de Java es necesario usar los argumentos nombrados: - - @Source(URL = "http://coders.com/", - mail = "support@coders.com") - class MyScalaClass ... - -Esta sintaxis es bastante tediosa si la anotación contiene solo un elemento (sin un valor por defecto) por lo tanto, por convención, si el nombre es especificado como `value` puede ser utilizado en Java usando una sintaxis similar a la de los constructores: - - @interface SourceURL { - public String value(); - public String mail() default ""; - } - -Y podemos aplicarlo así: - - @SourceURL("http://coders.com/") - public class MyClass extends HisClass ... - -En este caso, Scala provee la misma posibilidad: - - @SourceURL("http://coders.com/") - class MyScalaClass ... - -El elemento `mail` fue especificado con un valor por defecto (mediante la cláusula `default`) por lo tanto no necesitamos proveer explicitamente un valor para este. De todas maneras, si necesitamos pasarle un valor no podemos mezclar los dos estilos en Java: - - @SourceURL(value = "http://coders.com/", - mail = "support@coders.com") - public class MyClass extends HisClass ... - -Scala provee más flexibilidad en este caso: - - @SourceURL("http://coders.com/", - mail = "support@coders.com") - class MyScalaClass ... diff --git a/es/tutorials/tour/_posts/2017-02-13-anonymous-function-syntax.md b/es/tutorials/tour/_posts/2017-02-13-anonymous-function-syntax.md deleted file mode 100644 index 46028d622b..0000000000 --- a/es/tutorials/tour/_posts/2017-02-13-anonymous-function-syntax.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -layout: tutorial -title: Sintaxis de funciones anónimas - -discourse: false - -tutorial: scala-tour -categories: tour -num: 14 -language: es - -next-page: currying -previous-page: nested-functions ---- - -Scala provee una sintaxis relativamente livana para definir funciones anónimas. La siguiente expresión crea una función incrementadora para números enteros: - - (x: Int) => x + 1 - -El código anterior es una forma compacta para la definición de la siguiente clase anónima: - - new Function1[Int, Int] { - def apply(x: Int): Int = x + 1 - } - -También es posible definir funciones con múltiples parámetros: - - (x: Int, y: Int) => "(" + x + ", " + y + ")" - -o sin parámetros: - - () => { System.getProperty("user.dir") } - -Existe también una forma simple para escribir los tipos de las funciones. A continuación se muestran los tipos de las trés funciones escritas anteriormente: - - Int => Int - (Int, Int) => String - () => String - -La sintaxis anterior es la forma sintética de escribir los siguientes tipos: - - Function1[Int, Int] - Function2[Int, Int, String] - Function0[String] diff --git a/es/tutorials/tour/_posts/2017-02-13-automatic-closures.md b/es/tutorials/tour/_posts/2017-02-13-automatic-closures.md deleted file mode 100644 index 3138eef2fe..0000000000 --- a/es/tutorials/tour/_posts/2017-02-13-automatic-closures.md +++ /dev/null @@ -1,68 +0,0 @@ ---- -layout: tutorial -title: Construcción de closures automáticas - -discourse: false - -tutorial: scala-tour -categories: tour -num: 16 -language: es - -next-page: operators -previous-page: currying ---- - -Scala permite pasar funciones sin parámetros como parámetros de un método. Cuando un método así es invocado, los parámetros reales de la función enviada sin parámetros no son evaluados y una función "nularia" (de aridad cero, 0-aria, o sin parámetros) es pasada en su lugar. Esta función encapsula el comportamiento del parámetro correspondiente (comunmente conocido como "llamada por nombre"). - -Para aclarar un poco esto aquí se muestra un ejemplo: - - 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 - } - } - -La función `whileLoop` recibe dos parámetros `cond` y `body`. Cuando la función es llamada, los parámetros reales no son evaluados en ese momento. Pero cuando los parámetros son utilizados en el cuerpo de la función `whileLoop`, las funciones nularias creadas implícitamente serán evaluadas en su lugar. Así, nuestro método `whileLoop` implementa un bucle tipo Java mediante una implementación recursiva. - -Es posible combinar el uso de [operadores de infijo y postfijo (infix/postfix)](operators.html) con este mecanismo para crear declaraciones más complejas (con una sintaxis agradadable). - -Aquí mostramos la implementación de una declaración tipo repetir-a-menos-que (repetir el bucle a no ser que se cumpla X condición): - - 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) - } - -La función `loop` solo acepta el cuerpo de un bucle y retorna una instancia de la clase `LoopUnlessCond` (la cual encapsula el cuerpo del objeto). Es importante notar que en este punto el cuerpo del bucle no ha sido evaluado aún. La clase `LoopUnlessCond` tiene un método `unless` el cual puede ser usado como un *operador de infijo (infix)*. De esta manera podemos lograr una sintaxis muy natural para nuestro nuevo bucle `repetir { a_menos_que ( )`. - -A continuación se expone el resultado de la ejecución de `TargetTest2`: - - i = 10 - i = 9 - i = 8 - i = 7 - i = 6 - i = 5 - i = 4 - i = 3 - i = 2 - i = 1 diff --git a/es/tutorials/tour/_posts/2017-02-13-case-classes.md b/es/tutorials/tour/_posts/2017-02-13-case-classes.md deleted file mode 100644 index f8d22f3b74..0000000000 --- a/es/tutorials/tour/_posts/2017-02-13-case-classes.md +++ /dev/null @@ -1,81 +0,0 @@ ---- -layout: tutorial -title: Clases Case - -discourse: false - -tutorial: scala-tour -categories: tour -num: 5 -language: es - -next-page: compound-types -previous-page: classes ---- - -Scala da soporte a la noción de _clases caso_ (en inglés _case classes_, desde ahora _clases Case_). Las clases Case son clases regulares las cuales exportan sus parámetros constructores y a su vez proveen una descomposición recursiva de sí mismas a través de [reconocimiento de patrones](pattern-matching.html). - -A continuación se muestra un ejemplo para una jerarquía de clases la cual consiste de una super clase abstracta llamada `Term` y tres clases concretas: `Var`, `Fun` y `App`. - - abstract class Term - case class Var(name: String) extends Term - case class Fun(arg: String, body: Term) extends Term - case class App(f: Term, v: Term) extends Term - -Esta jerarquía de clases puede ser usada para representar términos de [cálculo lambda no tipado](http://www.ezresult.com/article/Lambda_calculus). Para facilitar la construcción de instancias de clases Case, Scala no requiere que se utilice la primitiva `new`. Simplemente es posible utilizar el nombre de la clase como una llamada a una función. - -Aquí un ejemplo: - - Fun("x", Fun("y", App(Var("x"), Var("y")))) - -Los parámetros constructores de las clases Case son tratados como valores públicos y pueden ser accedidos directamente. - - val x = Var("x") - println(x.name) - -Para cada una de las clases Case el compilador de Scala genera el método `equals` el cual implementa la igualdad estructural y un método `toString`. Por ejemplo: - - val x1 = Var("x") - val x2 = Var("x") - val y1 = Var("y") - println("" + x1 + " == " + x2 + " => " + (x1 == x2)) - println("" + x1 + " == " + y1 + " => " + (x1 == y1)) - -imprime - - Var(x) == Var(x) => true - Var(x) == Var(y) => false - -Solo tiene sentido definir una clase Case si el reconocimiento de patrones es usado para descomponer la estructura de los datos de la clase. El siguiente objeto define define una función de impresión `elegante` (en inglés `pretty`) que imprime en pantalla nuestra representación del cálculo lambda: - - object TermTest extends scala.App { - def printTerm(term: Term) { - term match { - case Var(n) => - print(n) - case Fun(x, b) => - print("^" + x + ".") - printTerm(b) - case App(f, v) => - print("(") - printTerm(f) - print(" ") - printTerm(v) - print(")") - } - } - def isIdentityFun(term: Term): Boolean = term match { - case Fun(x, Var(y)) if x == y => true - case _ => false - } - val id = Fun("x", Var("x")) - val t = Fun("x", Fun("y", App(Var("x"), Var("y")))) - printTerm(t) - println - println(isIdentityFun(id)) - println(isIdentityFun(t)) - } - -En nuestro ejemplo, la función `printTerm` es expresada como una sentencia basada en reconocimiento de patrones, la cual comienza con la palabra reservada `match` y consiste en secuencias de sentencias tipo `case PatronBuscado => Código que se ejecuta`. - -El programa de arriba también define una función `isIdentityFun` la cual comprueba si un término dado se corresponde con una función identidad simple. Ese ejemplo utiliza patrones y guardas más avanzados (obsrvese la guarda `if x==y`). Tras reconocer un patrón con un valor dado, la guarda (definida después de la palabra clave `if`) es evaluada. Si retorna `true` (verdadero), el reconocimiento es exitoso; de no ser así, falla y se intenta con el siguiente patrón. diff --git a/es/tutorials/tour/_posts/2017-02-13-classes.md b/es/tutorials/tour/_posts/2017-02-13-classes.md deleted file mode 100644 index f6b9ca10ca..0000000000 --- a/es/tutorials/tour/_posts/2017-02-13-classes.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -layout: tutorial -title: Clases - -discourse: false - -tutorial: scala-tour -categories: tour -num: 4 -language: es - -next-page: case-classes -previous-page: annotations ---- -En Scala, las clases son plantillas estáticas que pueden ser instanciadas por muchos objetos en tiempo de ejecución. -Aquí se presenta una clase la cual define la clase `Point`: - - class Point(xc: Int, yc: Int) { - var x: Int = xc - var y: Int = yc - def move(dx: Int, dy: Int) { - x = x + dx - y = y + dy - } - override def toString(): String = "(" + x + ", " + y + ")"; - } - -Esta clase define dos variables `x` e `y`, y dos métodos: `move` y `toString`. El método `move` recibe dos argumentos de tipo entero, pero no retorna ningún valor (implícitamente se retorna el tipo `Unit`, el cual se corresponde a `void` en lenguajes tipo Java). `toString`, por otro lado, no recibe ningún parámetro pero retorna un valor tipo `String`. Ya que `toString` sobreescribe el método `toString` predefinido en una superclase, tiene que ser anotado con `override`. - -Las clases en Scala son parametrizadas con argumentos constructores (inicializadores). En el código anterior se definen dos argumentos contructores, `xc` y `yc`; ambos son visibles en toda la clase. En nuestro ejemplo son utilizados para inicializar las variables `x` e `y`. - -Para instanciar una clase es necesario usar la primitiva `new`, como se muestra en el siguiente ejemplo: - - object Classes { - def main(args: Array[String]) { - val pt = new Point(1, 2) - println(pt) - pt.move(10, 10) - println(pt) - } - } - -El programa define una aplicación ejecutable a través del método `main` del objeto singleton `Classes`. El método `main` crea un nuevo `Point` y lo almacena en `pt`. _Note que valores definidos con la signatura `val` son distintos de los definidos con `var` (véase la clase `Point` arriba) ya que los primeros (`val`) no permiten reasignaciones; es decir, que el valor es una constante._ - -Aquí se muestra la salida del programa: - - (1, 2) - (11, 12) diff --git a/es/tutorials/tour/_posts/2017-02-13-compound-types.md b/es/tutorials/tour/_posts/2017-02-13-compound-types.md deleted file mode 100644 index fcde64118e..0000000000 --- a/es/tutorials/tour/_posts/2017-02-13-compound-types.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -layout: tutorial -title: Tipos Compuestos - -discourse: false - -tutorial: scala-tour -categories: tour -num: 6 -language: es - -next-page: sequence-comprehensions -previous-page: case-classes ---- - -Algunas veces es necesario expresar que el tipo de un objeto es un subtipo de varios otros tipos. En Scala esto puede ser expresado con la ayuda de *tipos compuestos*, los cuales pueden entenderse como la intersección de otros tipos. - -Suponga que tenemos dos traits `Cloneable` y `Resetable`: - - trait Cloneable extends java.lang.Cloneable { - override def clone(): Cloneable = { - super.clone().asInstanceOf[Cloneable] - } - } - trait Resetable { - def reset: Unit - } - -Ahora suponga que queremos escribir una función `cloneAndReset` la cual recibe un objeto, lo clona y resetea el objeto original: - - def cloneAndReset(obj: ?): Cloneable = { - val cloned = obj.clone() - obj.reset - cloned - } - -La pregunta que surge es cuál es el tipo del parámetro `obj`. Si este fuera `Cloneable` entonces el objeto puede ser clonado mediante el método `clone`, pero no puede usarse el método `reset`; Si fuera `Resetable` podríamos resetearlo mediante el método `reset`, pero no sería posible clonarlo. Para evitar casteos (refundiciones, en inglés `casting`) de tipos en situaciones como la descrita, podemos especificar que el tipo del objeto `obj` sea tanto `Clonable` como `Resetable`. En tal caso estaríamos creando un tipo compuesto; de la siguiente manera: - - def cloneAndReset(obj: Cloneable with Resetable): Cloneable = { - //... - } - -Los tipos compuestos pueden crearse a partir de varios tipos de objeto y pueden tener un refinamiento el cual puede ser usado para acotar la signatura los miembros del objeto existente. - -La forma general es: `A with B with C ... { refinamiento }` - -Un ejemplo del uso de los refinamientos se muestra en la página sobre [tipos abstractos](abstract-types.html). diff --git a/es/tutorials/tour/_posts/2017-02-13-currying.md b/es/tutorials/tour/_posts/2017-02-13-currying.md deleted file mode 100644 index 611e2698a2..0000000000 --- a/es/tutorials/tour/_posts/2017-02-13-currying.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -layout: tutorial -title: Currying - -discourse: false - -tutorial: scala-tour -categories: tour -num: 15 -language: es - -next-page: automatic-closures -previous-page: anonymous-function-syntax ---- - -_Nota de traducción: Currying es una técnica de programación funcional nombrada en honor al matemático y lógico Haskell Curry. Es por eso que no se intentará hacer ninguna traducción sobre el término Currying. Entiendase este como una acción, técnica base de PF. Como una nota al paso, el lenguaje de programación Haskell debe su nombre a este eximio matemático._ - -Los métodos pueden definir múltiples listas de parámetros. Cuando un método es invocado con un número menor de listas de parámetros, en su lugar se devolverá una función que toma las listas faltantes como sus argumentos. - -Aquí se muestra un ejemplo: - - 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))) - } - -_Nota: el método `modN` está parcialmente aplicado en las dos llamadas a `filter`; esto significa que solo su primer argumento es realmente aplicado. El término `modN(2)` devuelve una función de tipo `Int => Boolean` y es por eso un posible candidato para el segundo argumento de la función `filter`_ - -Aquí se muestra la salida del programa anterior: - - List(2,4,6,8) - List(3,6) diff --git a/es/tutorials/tour/_posts/2017-02-13-default-parameter-values.md b/es/tutorials/tour/_posts/2017-02-13-default-parameter-values.md deleted file mode 100644 index 89915dcbb0..0000000000 --- a/es/tutorials/tour/_posts/2017-02-13-default-parameter-values.md +++ /dev/null @@ -1,69 +0,0 @@ ---- -layout: tutorial -title: Valores de parámetros por defecto - -discourse: false - -tutorial: scala-tour -categories: tour -num: 34 -language: es - -next-page: named-parameters -previous-page: implicit-conversions ---- - -Scala tiene la capacidad de dar a los parámetros valores por defecto que pueden ser usados para permitir a quien invoca el método o función que omita dichos parámetros. - -En Java, uno tiende a ver muchos métodos sobrecargados que solamente sirven para proveer valores por defecto para ciertos parámetros de un método largo. En especial se ve este comportamiento en constructores: - - public class HashMap { - public HashMap(Map m); - /** Create a new HashMap with default capacity (16) - * and loadFactor (0.75) - */ - public HashMap(); - /** Create a new HashMap with default loadFactor (0.75) */ - public HashMap(int initialCapacity); - public HashMap(int initialCapacity, float loadFactor); - } - -Existen realmente dos constructores aquí; uno que toma otro mapa y uno que toma una capacidad y un factor de carga. Los constructores tercero y cuarto están ahí para premitir a los usuarios de la clase HashMap crear instancias con el valor por defecto que probablemente sea el mejor para ambos, el factor de carga y la capacidad. - -Más problemático es que los valores usados para ser por defecto están tanto en la documentación (Javadoc) como en el código. Mantener ambos actualizado es dificil. Un patrón típico utilizado para no cometer estos errores es agregar constantes públicas cuyo valor será mostrado en el Javadoc: - - public class HashMap { - public static final int DEFAULT_CAPACITY = 16; - public static final float DEFAULT_LOAD_FACTOR = 0.75; - - public HashMap(Map m); - /** Create a new HashMap with default capacity (16) - * and loadFactor (0.75) - */ - public HashMap(); - /** Create a new HashMap with default loadFactor (0.75) */ - public HashMap(int initialCapacity); - public HashMap(int initialCapacity, float loadFactor); - } - -Mientras esto evita repetirnos una y otra vez, es menos que expresivo. - -Scala cuenta con soporte directo para esto: - - class HashMap[K,V](initialCapacity:Int = 16, loadFactor:Float = 0.75) { - } - - // Usa los parametros por defecto - val m1 = new HashMap[String,Int] - - // initialCapacity 20, default loadFactor - val m2= new HashMap[String,Int](20) - - // sobreescribe ambos - val m3 = new HashMap[String,Int](20,0.8) - - // sobreescribe solamente loadFactor - // mediante parametros nombrados - val m4 = new HashMap[String,Int](loadFactor = 0.8) - -Nótese cómo podemos sacar ventaja de cualquier valor por defecto al utilizar [parámetros nombrados]({{ site.baseurl }}/tutorials/tour/named-parameters.html). diff --git a/es/tutorials/tour/_posts/2017-02-13-explicitly-typed-self-references.md b/es/tutorials/tour/_posts/2017-02-13-explicitly-typed-self-references.md deleted file mode 100644 index 0392db84fe..0000000000 --- a/es/tutorials/tour/_posts/2017-02-13-explicitly-typed-self-references.md +++ /dev/null @@ -1,106 +0,0 @@ ---- -layout: tutorial -title: Autorefrencias explicitamente tipadas - -discourse: false - -tutorial: scala-tour -categories: tour -num: 27 -language: es - -next-page: local-type-inference -previous-page: lower-type-bounds ---- - -Cuando se está construyendo software extensible, algunas veces resulta útil declarar el tipo de la variable `this` explícitamente. Para motivar esto, realizaremos una pequeña representación de una estructura de datos Grafo, en Scala. - -Aquí hay una definición que sirve para describir un grafo: - - abstract class Grafo { - type Vertice - type Nodo <: NodoIntf - abstract class NodoIntf { - def conectarCon(nodo: Nodo): Vertice - } - def nodos: List[Nodo] - def vertices: List[Vertice] - def agregarNodo: Nodo - } - -Los grafos consisten de una lista de nodos y vértices (o aristas en alguna bibliografía) donde tanto el tipo nodo, como el vértice fueron declarados abstractos. El uso de [tipos abstractos](abstract-types.html) permite las implementaciones del trait `Grafo` proveer sus propias clases concretas para nodos y vértices. Además, existe un método `agregarNodo` para agregar nuevos nodos al grafo. Los nodos se conectan entre sí utilizando el método `conectarCon`. - -Una posible implementación de la clase `Grafo`es dada en el siguiente programa: - - abstract class GrafoDirigido extends Grafo { - type Vertice <: VerticeImpl - class VerticeImpl(origen: Nodo, dest: Nodo) { - def desde = origen - def hasta = dest - } - class NodoImpl extends NodoIntf { - def conectarCon(nodo: Nodo): Vertice = { - val vertice = nuevoVertice(this, nodo) - vertices = vertice :: vertices - vertice - } - } - protected def nuevoNodo: Nodo - protected def nuevoVertice(desde: Nodo, hasta: Nodo): Vertice - var nodos: List[Nodo] = Nil - var vertices: List[Vertice] = Nil - def agregarNodo: Nodo = { - val nodo = nuevoNodo - nodos = nodo :: nodos - nodo - } - } - -La clase `GrafoDirigido` especializa la clase `Grafo` al proveer una implementación parcial. La implementación es solamente parcial, porque queremos que sea posible extender `GrafoDirigido` aun más. Por lo tanto, esta clase deja todos los detalles de implementación abiertos y así tanto los tipos vértice como nodo son abstractos. De todas maneras, la clase `GrafoDirigido` revela algunos detalles adicionales sobre la implementación del tipo vértice al acotar el límite a la clase `VerticeImpl`. Además, tenemos algunas implementaciones preliminares de vértices y nodos representados por las clases `VerticeImpl` y `NodoImpl`. - -Ya que es necesario crear nuevos objetos nodo y vértice con nuestra implementación parcial del grafo, también debimos agregar los métodos constructores `nuevoNodo` y `nuevoVertice`. Los métodos `agregarNodo` y `conectarCon` están ambos definidos en términos de estos métodos constructores. Una mirada más cercana a la implementación del método `conectarCon` revela que para crear un vértice es necesario pasar la auto-referencia `this` al método constructor `newEdge`. Pero a `this` en ese contexto le es asignado el tipo `NodoImpl`, por lo tanto no es compatible con el tipo `Nodo` el cual es requerido por el correspondiente método constructor. Como consecuencia, el programa superior no está bien definido y compilador mostrará un mensaje de error. - -En Scala es posible atar a una clase otro tipo (que será implementado en el futuro) al darle su propia auto-referencia `this` el otro tipo explicitamente. Podemos usar este mecanismo para arreglar nuestro código de arriba. El tipo the `this` explícito es especificado dentro del cuerpo de la clase `GrafoDirigido`. - -Este es el progama arreglado: - - abstract class GrafoDirigido extends Grafo { - ... - class NodoImpl extends NodoIntf { - self: Nodo => - def conectarCon(nodo: Nodo): Vertice = { - val vertice = nuevoVertice(this, nodo) // ahora legal - vertices = vertice :: vertices - vertice - } - } - ... - } - -En esta nueva definición de la clase `NodoImpl`, `this` tiene el tipo `Nodo`. Ya que `Nodo` es abstracta y por lo tanto todavía no sabemos si `NodoImpl` es realmente un subtipo de `Nodo`, el sistema de tipado de Scala no permitirá instanciar esta clase. Pero de todas maneras, estipulamos con esta anotación explicita de tipo que en algún momento en el tiempo, una subclase de `NodeImpl` tiene que denotar un subtipo del tipo `Nodo` de forma de ser instanciable. - -Aquí presentamos una especialización concreta de `GrafoDirigido` donde todos los miembros abstractos son definidos: - - class GrafoDirigidoConcreto extends GrafoDirigido { - type Vertice = VerticeImpl - type Nodo = NodoImpl - protected def nuevoNodo: Nodo = new NodoImpl - protected def nuevoVertice(d: Nodo, h: Node): Vertice = - new VerticeImpl(d, h) - } - - -Por favor nótese que en esta clase nos es posible instanciar `NodoImpl` porque ahora sabemos que `NodoImpl` denota a un subtipo de `Nodo` (que es simplemente un alias para `NodoImpl`). - -Aquí hay un ejemplo de uso de la clase `GrafoDirigidoConcreto`: - - object GraphTest extends App { - val g: Grafo = new GrafoDirigidoConcreto - val n1 = g.agregarNodo - val n2 = g.agregarNodo - val n3 = g.agregarNodo - n1.conectarCon(n2) - n2.conectarCon(n3) - n1.conectarCon(n3) - } - diff --git a/es/tutorials/tour/_posts/2017-02-13-extractor-objects.md b/es/tutorials/tour/_posts/2017-02-13-extractor-objects.md deleted file mode 100644 index 9facdeee5f..0000000000 --- a/es/tutorials/tour/_posts/2017-02-13-extractor-objects.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -layout: tutorial -title: Objetos Extractores - -discourse: false - -tutorial: scala-tour -categories: tour -num: 8 -language: es - -next-page: generic-classes -previous-page: sequence-comprehensions ---- - -En Scala pueden ser definidos patrones independientemente de las clases Caso (en inglés case classes, desde ahora clases Case). Para este fin exite un método llamado `unapply` que proveera el ya dicho extractor. Por ejemplo, en el código siguiente se define el objeto extractor `Twice` - - 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) } // imprime 21 - } - -Hay dos convenciones sintácticas que entran en juego aquí: - -El patrón `case Twice(n)` causará la invocación del método `Twice.unapply`, el cual es usado para reconocer cualquier número par; el valor de retorno de `unapply` indica si el argumento produjo una coincidencia o no, y cualquier otro sub valor que pueda ser usado para un siguiente reconocimiento. Aquí, el sub-valor es `z/2`. - -El método `apply` no es necesario para reconocimiento de patrones. Solamente es usado para proveer un constructor. `val x = Twice(21)` se puede expandir como `val x = Twice.apply(21)`. - -El tipo de retorno de un método `unapply` debería ser elegido de la siguiente manera: -* Si es solamente una comprobación, retornar un `Boolean`. Por ejemplo, `case esPar()` -* Si retorna un único sub valor del tipo T, retornar un `Option[T]` -* Si quiere retornar varios sub valores `T1,...,Tn`, es necesario agruparlos en una tupla de valores opcionales `Option[(T1,...,Tn)]`. - -Algunas veces, el número de sub valores es fijo y nos gustaría retornar una secuencia. Por esta razón, siempre es posible definir patrones a través de `unapplySeq`. El último sub valor de tipo `Tn` tiene que ser `Seq[S]`. Este mecanismo es usado por ejemplo en el patrón `case List(x1, ..., xn)`. - -Los objetos extractores pueden hacer el código más mantenible. Para más detalles lea el paper ["Matching Objects with Patterns (Reconociendo objetos con patrones)"](https://infoscience.epfl.ch/record/98468/files/MatchingObjectsWithPatterns-TR.pdf) (ver sección 4) por Emir, Odersky y Williams (Enero de 2007). diff --git a/es/tutorials/tour/_posts/2017-02-13-generic-classes.md b/es/tutorials/tour/_posts/2017-02-13-generic-classes.md deleted file mode 100644 index 7b294679a3..0000000000 --- a/es/tutorials/tour/_posts/2017-02-13-generic-classes.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -layout: tutorial -title: Clases genéricas - -discourse: false - -tutorial: scala-tour -categories: tour -num: 9 -language: es - -next-page: implicit-parameters -previous-page: extractor-objects ---- - -Tal como en Java 5 ([JDK 1.5](http://java.sun.com/j2se/1.5/)), Scala provee soporte nativo para clases parametrizados con tipos. Eso es llamado clases genéricas y son especialmente importantes para el desarrollo de clases tipo colección. - -A continuación se muestra un ejemplo: - - 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 } - } - -La clase `Stack` modela una pila mutable que contiene elementos de un tipo arbitrario `T` (se dice, "una pila de elementos `T`). Los parámetros de tipos nos aseguran que solo elementos legales (o sea, del tipo `T`) sean insertados en la pila (apilados). De forma similar, con los parámetros de tipo podemos expresar que el método `top` solo devolverá elementos de un tipo dado (en este caso `T`). - -Aquí se muestra un ejemplo del uso de dicha pila: - - object GenericsTest extends App { - val stack = new Stack[Int] - stack.push(1) - stack.push('a') - println(stack.top) - stack.pop() - println(stack.top) - } - -La salida del programa sería: - - 97 - 1 - -_Nota: los subtipos de tipos genéricos es *invariante*. Esto significa que si tenemos una pila de caracteres del tipo `Stack[Char]`, esta no puede ser usada como una pila de enteros tipo `Stack[Int]`. Esto no sería razonable ya que nos permitiría introducir elementos enteros en la pila de caracteres. Para concluir, `Stack[T]` es solamente un subtipo de `Stack[S]` si y solo si `S = T`. Ya que esto puede llegar a ser bastante restrictivo, Scala ofrece un [mecanismo de anotación de parámetros de tipo](variances.html) para controlar el comportamiento de subtipos de tipos genéricos._ diff --git a/es/tutorials/tour/_posts/2017-02-13-higher-order-functions.md b/es/tutorials/tour/_posts/2017-02-13-higher-order-functions.md deleted file mode 100644 index d84c663a9e..0000000000 --- a/es/tutorials/tour/_posts/2017-02-13-higher-order-functions.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -layout: tutorial -title: Funciones de orden superior - -discourse: false - -tutorial: scala-tour -categories: tour -num: 18 -language: es - -next-page: pattern-matching -previous-page: operators ---- - -Scala permite la definición de funciones de orden superior. Estas funciones son las que _toman otras funciones como parámetros_, o las cuales _el resultado es una función_. Aquí mostramos una función `apply` la cual toma otra función `f` y un valor `v` como parámetros y aplica la función `f` a `v`: - - def apply(f: Int => String, v: Int) = f(v) - -_Nota: los métodos son automáticamente tomados como funciones si el contexto lo requiere._ - -Otro ejemplo: - - 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)) - } - -La ejecución da como valor el siguiente resultado: - - [7] - -En este ejemplo, el método `decorator.layout` es coaccionado automáticamente a un valor del tipo `Int => String` como es requerido por el método `apply`. Por favor note que el método `decorator.layout` es un _método polimórfico_ (esto es, se abstrae de algunos de sus tipos) y el compilador de Scala primero tiene que instanciar correctamente el tipo del método. diff --git a/es/tutorials/tour/_posts/2017-02-13-implicit-conversions.md b/es/tutorials/tour/_posts/2017-02-13-implicit-conversions.md deleted file mode 100644 index 9368a641a8..0000000000 --- a/es/tutorials/tour/_posts/2017-02-13-implicit-conversions.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -layout: tutorial -title: Implicit Conversions - -discourse: false - -tutorial: scala-tour -categories: tour -num: 32 -language: es - -next-page: default-parameter-values -previous-page: variances ---- - -(this page has not been translated into Spanish) diff --git a/es/tutorials/tour/_posts/2017-02-13-implicit-parameters.md b/es/tutorials/tour/_posts/2017-02-13-implicit-parameters.md deleted file mode 100644 index 16097037c6..0000000000 --- a/es/tutorials/tour/_posts/2017-02-13-implicit-parameters.md +++ /dev/null @@ -1,50 +0,0 @@ ---- -layout: tutorial -title: Parámetros implícitos - -discourse: false - -tutorial: scala-tour -categories: tour -num: 10 -language: es - -next-page: inner-classes -previous-page: generic-classes ---- - -Un método con _parámetros implícitos_ puede ser aplicado a argumentos tal como un método normal. En este caso la etiqueta `implicit` no tiene efecto. De todas maneras, si a un método le faltan argumentos para sus parámetros implícitos, tales argumentos serán automáticamente provistos. - -Los argumentos reales que son elegibles para ser pasados a un parámetro implícito están contenidos en dos categorías: -* Primera, son elegibles todos los identificadores x que puedan ser accedidos en el momento de la llamada al método sin ningún prefijo y que denotan una definición implícita o un parámetro implícito. -* Segunda, además son elegibles todos los miembros de modulos `companion` (ver [objetos companion] (singleton-objects.html) ) del tipo de parámetro implicito que tienen la etiqueta `implicit`. - -En el siguiente ejemplo definimos un método `sum` el cual computa la suma de una lista de elementos usando las operaciones `add` y `unit` de `Monoid`. Note que los valores implícitos no pueden ser de nivel superior (top-level), deben ser miembros de una plantilla. - - abstract class SemiGroup[A] { - def add(x: A, y: A): A - } - abstract class Monoid[A] extends SemiGroup[A] { - def unit: A - } - object ImplicitTest extends App { - 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 - } - def sum[A](xs: List[A])(implicit m: Monoid[A]): A = - if (xs.isEmpty) m.unit - else m.add(xs.head, sum(xs.tail)) - - println(sum(List(1, 2, 3))) - println(sum(List("a", "b", "c"))) - } - -Esta es la salida del programa: - - 6 - abc diff --git a/es/tutorials/tour/_posts/2017-02-13-inner-classes.md b/es/tutorials/tour/_posts/2017-02-13-inner-classes.md deleted file mode 100644 index f35b970514..0000000000 --- a/es/tutorials/tour/_posts/2017-02-13-inner-classes.md +++ /dev/null @@ -1,90 +0,0 @@ ---- -layout: tutorial -title: Clases Internas - -discourse: false - -tutorial: scala-tour -categories: tour -num: 11 -language: es - -next-page: mixin-class-composition -previous-page: implicit-parameters ---- - -En Scala es posible que las clases tengan como miembro otras clases. A diferencia de lenguajes similares a Java donde ese tipo de clases internas son miembros de las clases que las envuelven, en Scala esas clases internas están ligadas al objeto externo. Para ilustrar esta diferencia, vamos a mostrar rápidamente una implementación del tipo grafo: - - 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 - } - } - -En nuestro programa, los grafos son representados mediante una lista de nodos. Estos nodos son objetos de la clase interna `Node`. Cada nodo tiene una lista de vecinos que se almacena en la lista `connectedNodes`. Ahora podemos crear un grafo con algunos nodos y conectarlos incrementalmente: - - 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) - } - -Ahora vamos a completar el ejemplo con información relacionada al tipado para definir explicitamente de qué tipo son las entidades anteriormente definidas: - - 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) - } - -El código anterior muestra que al tipo del nodo le es prefijado con la instancia superior (que en nuestro ejemplo es `g`). Si ahora tenemos dos grafos, el sistema de tipado de Scala no nos permite mezclar nodos definidos en un grafo con nodos definidos en otro, ya que los nodos del otro grafo tienen un tipo diferente. - -Aquí está el programa ilegal: - - object IllegalGraphTest extends App { - val g: Graph = new Graph - val n1: g.Node = g.newNode - val n2: g.Node = g.newNode - n1.connectTo(n2) // legal - val h: Graph = new Graph - val n3: h.Node = h.newNode - n1.connectTo(n3) // ilegal! - } - -Por favor note que en Java la última linea del ejemplo anterior hubiese sido correcta. Para los nodos de ambos grafos, Java asignaría el mismo tipo `Graph.Node`; es decir, `Node` es prefijado con la clase `Graph`. En Scala un tipo similar también puede ser definido, pero es escrito `Graph#Node`. Si queremos que sea posible conectar nodos de distintos grafos, es necesario modificar la implementación inicial del grafo de la siguiente manera: - - class Graph { - class Node { - var connectedNodes: List[Graph#Node] = Nil // Graph#Node en lugar de Node - 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 - } - } - -> Por favor note que este programa no nos permite relacionar un nodo con dos grafos diferentes. Si también quisiéramos eliminar esta restricción, sería necesario cambiar el tipo de la variable `nodes` a `Graph#Node`. diff --git a/es/tutorials/tour/_posts/2017-02-13-local-type-inference.md b/es/tutorials/tour/_posts/2017-02-13-local-type-inference.md deleted file mode 100644 index c781bdf6d8..0000000000 --- a/es/tutorials/tour/_posts/2017-02-13-local-type-inference.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -layout: tutorial -title: Inferencia de tipos Local - -discourse: false - -tutorial: scala-tour -categories: tour -num: 29 -language: es - -next-page: unified-types -previous-page: explicitly-typed-self-references ---- - -Scala tiene incorporado un mecanismo de inferencia de tipos el cual permite al programador omitir ciertos tipos de anotaciones. Por ejemplo, generalmente no es necesario especificar el tipo de una variable, ya que el compilador puede deducir el tipo mediante la expresión de inicialización de la variable. También puede generalmente omitirse los tipos de retorno de métodos ya que se corresponden con el tipo del cuerpo, que es inferido por el compilador. - -Aquí hay un ejemplo: - - object InferenceTest1 extends App { - val x = 1 + 2 * 3 // el tipo de x es Int - val y = x.toString() // el tipo de y es String - def succ(x: Int) = x + 1 // el método succ retorna valores Int - } - -Para métodos recursivos, el compilador no es capaz de inferir el tipo resultado. A continuación mostramos un programa el cual falla por esa razón: - - object InferenceTest2 { - def fac(n: Int) = if (n == 0) 1 else n * fac(n - 1) - } - -Tampoco es obligatorio especificar el tipo de los parámetros cuando se trate de [métodos polimórficos](polymorphic-methods.html) o sean instanciadas [clases genéricas](generic-classes.html). El compilador de Scala inferirá esos tipos de parámetros faltantes mediante el contexto y de los tipos de los parámetros reales del método/constructor. - -Aquí se muestra un ejemplo que ilustra esto: - - case class MyPair[A, B](x: A, y: B); - object InferenceTest3 extends App { - def id[T](x: T) = x - val p = MyPair(1, "scala") // tipo: MyPair[Int, String] - val q = id(1) // tipo: Int - } - -Las últimas dos lineas de este programa son equivalentes al siguiente código, donde todos los tipos inferidos son especificados explicitamente: - - val x: MyPair[Int, String] = MyPair[Int, String](1, "scala") - val y: Int = id[Int](1) - -En algunas situaciones puede ser bastante peligroso confiar en el mecanismo de inferencia de tipos de Scala, como se ilustra en el siguiente ejemplo: - - object InferenceTest4 { - var obj = null - obj = new Object() - } - -Este programa no compila porque el tipo inferido para la variable `obj` es `Null`. Ya que el único valor de ese tipo es `null`, es imposible hacer que esta variable refiera a otro valor. diff --git a/es/tutorials/tour/_posts/2017-02-13-lower-type-bounds.md b/es/tutorials/tour/_posts/2017-02-13-lower-type-bounds.md deleted file mode 100644 index 8bddc26862..0000000000 --- a/es/tutorials/tour/_posts/2017-02-13-lower-type-bounds.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -layout: tutorial -title: Límite de tipado inferior - -discourse: false - -tutorial: scala-tour -categories: tour -num: 26 -language: es - -next-page: explicitly-typed-self-references -previous-page: upper-type-bounds ---- - -Mientras que los [límites de tipado superior](upper-type-bounds.html) limitan el tipo de un subtipo de otro tipo, los *límites de tipado inferior* declaran que un tipo sea un supertipo de otro tipo. El término `T >: A` expresa que el parámetro de tipo `T` o el tipo abstracto `T` se refiera a un supertipo del tipo `A` - -Aquí se muestra un ejemplo donde esto es de utilidad: - - 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) - } - -El programa mostrado implementa una lista enlazada con una operación `prepend` (agregar al principio). Desafortunadamente este tipo es invariante en el parámetro de tipo de la clase `ListNode`; esto es, el tipo `ListNode[String]` no es un subtipo de `ListNode[Object]`. Con la ayuda de [anotaciones de varianza](variances.html) es posible expresar tal semantica de subtipos: - - case class ListNode[+T](h: T, t: ListNode[T]) { ... } // No compila - -Desafortunadamente, este programa no compila porque una anotación covariante es solo posible si el tipo de la variable es usado solo en posiciones covariantes. Ya que la variable de tipo `T` aparece como un parámetro de tipo en el método `prepend`, esta regla se rompe. Con la ayuda de un *límite de tipado inferior*, sin embargo, podemos implementar un método `prepend` donde `T` solo aparezca en posiciones covariantes. - -Este es el código correspondiente: - - 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) - } - -_Nota: el nuevo método `prepend` tiene un tipo un poco menos restrictivo. Esto permite, por ejemplo, agregar un objeto de un supertipo a una lista ya creada. La lista resultante será una lista de este supertipo._ - -Este código ilustra el concepto: - - 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/es/tutorials/tour/_posts/2017-02-13-mixin-class-composition.md b/es/tutorials/tour/_posts/2017-02-13-mixin-class-composition.md deleted file mode 100644 index 990fb4a031..0000000000 --- a/es/tutorials/tour/_posts/2017-02-13-mixin-class-composition.md +++ /dev/null @@ -1,50 +0,0 @@ ---- -layout: tutorial -title: Composición de clases mixin - -discourse: false - -tutorial: scala-tour -categories: tour -num: 12 -language: es - -next-page: singleton-objects -previous-page: inner-classes ---- -_Nota de traducción: La palabra `mixin` puede ser traducida como mezcla, dando título a esta sección de: Composición de clases Mezcla, pero es preferible utilizar la notación original_ - -A diferencia de lenguajes que solo soportan _herencia simple_, Scala tiene una notación más general de la reutilización de clases. Scala hace posible reutilizar la _nueva definición de miembros de una clase_ (es decir, el delta en relación a la superclase) en la definición de una nueva clase. Esto es expresado como una _composición de clases mixin_. Considere la siguiente abstracción para iteradores. - - abstract class AbsIterator { - type T - def hasNext: Boolean - def next: T - } - -A continuación, considere una clase mezcla la cual extiende `AbsIterator` con un método `foreach` el cual aplica una función dada a cada elemento retornado por el iterador. Para definir una clase que puede usarse como una clase mezcla usamos la palabra clave `trait`. - - trait RichIterator extends AbsIterator { - def foreach(f: T => Unit) { while (hasNext) f(next) } - } - -Aquí se muestra una clase iterador concreta, la cual retorna caracteres sucesivos de una cadena de caracteres dada: - - 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 } - } - -Nos gustaría combinar la funcionalidad de `StringIterator` y `RichIterator` en una sola clase. Solo con herencia simple e interfaces esto es imposible, ya que ambas clases contienen implementaciones para sus miembros. Scala nos ayuda con sus _compisiciones de clases mezcladas_. Permite a los programadores reutilizar el delta de la definición de una clase, esto es, todas las nuevas definiciones que no son heredadas. Este mecanismo hace posible combinar `StringIterator` con `RichIterator`, como es hecho en el siguiente programa, el cual imprime una columna de todos los caracteres de una cadena de caracteres dada. - - object StringIteratorTest { - def main(args: Array[String]) { - class Iter extends StringIterator(args(0)) with RichIterator - val iter = new Iter - iter foreach println - } - } - -La clase `Iter` en la función `main` es construida de una composición mixin de los padres `StringIterator` y `RichIterator` con la palabra clave `with`. El primera padre es llamado la _superclase_ de `Iter`, mientras el segundo padre (y cualquier otro que exista) es llamada un _mixin_. diff --git a/es/tutorials/tour/_posts/2017-02-13-named-parameters.md b/es/tutorials/tour/_posts/2017-02-13-named-parameters.md deleted file mode 100644 index f5e73b35e8..0000000000 --- a/es/tutorials/tour/_posts/2017-02-13-named-parameters.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -layout: tutorial -title: Parámetros nombrados - -discourse: false - -tutorial: scala-tour -categories: tour -num: 35 -language: es - -previous-page: default-parameter-values ---- - -En la invocación de métodos y funciones se puede usar el nombre de las variables explícitamente en la llamada, de la siguiente manera: - - def imprimirNombre(nombre:String, apellido:String) = { - println(nombre + " " + apellido) - } - - imprimirNombre("John","Smith") - // Imprime "John Smith" - imprimirNombre(first = "John",last = "Smith") - // Imprime "John Smith" - imprimirNombre(last = "Smith",first = "John") - // Imprime "John Smith" - -Note que una vez que se utilizan parámetros nombrados en la llamada, el orden no importa, mientras todos los parámetros sean nombrados. Esta característica funciona bien en conjunción con [valores de parámetros por defecto]({{ site.baseurl }}/tutorials/tour/default_parameter_values.html): - - def imprimirNombre(nombre:String = "John", apellido:String = "Smith") = { - println(nombre + " " + apellido) - } - - printName(apellido = "Jones") - // Imprime "John Jones" - -Ya que es posible colocar los parámetros en cualquier orden que te guste, puedes usar el valor por defecto para parámetros que aparecen primero en la lista de parámetros. diff --git a/es/tutorials/tour/_posts/2017-02-13-nested-functions.md b/es/tutorials/tour/_posts/2017-02-13-nested-functions.md deleted file mode 100644 index 0f54f32d47..0000000000 --- a/es/tutorials/tour/_posts/2017-02-13-nested-functions.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -layout: tutorial -title: Funciones Anidadas - -discourse: false - -tutorial: scala-tour -categories: tour -num: 13 -language: es - -next-page: anonymous-function-syntax -previous-page: singleton-objects ---- - -En scala es posible anidar definiciones de funciones. El siguiente objeto provee una función `filter` para extraer valores de una lista de enteros que están por debajo de un valor determinado: - - 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)) - } - -_Nota: la función anidada `process` utiliza la variable `threshold` definida en el ámbito externo como un parámetro de `filter`._ - -La salida del programa es: - - List(1,2,3,4) diff --git a/es/tutorials/tour/_posts/2017-02-13-operators.md b/es/tutorials/tour/_posts/2017-02-13-operators.md deleted file mode 100644 index af8b78504d..0000000000 --- a/es/tutorials/tour/_posts/2017-02-13-operators.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -layout: tutorial -title: Operadores - -discourse: false - -tutorial: scala-tour -categories: tour -num: 17 -language: es - -next-page: higher-order-functions -previous-page: automatic-closures ---- - -En Scala, cualquier método el cual reciba un solo parámetro puede ser usado como un *operador de infijo (infix)*. Aquí se muestra la definición de la clase `MyBool`, la cual define tres métodos `and`, `or`, y `negate`. - - 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 = new MyBool(!x) - } - -Ahora es posible utilizar `and` y `or` como operadores de infijo: - - def not(x: MyBool) = x negate; // punto y coma necesario aquí - def xor(x: MyBool, y: MyBool) = (x or y) and not(x and y) - -Como muestra la primera linea del código anterior, es también posible utilizar métodos nularios (que no reciban parámetros) como operadores de postfijo. La segunda linea define la función `xor` utilizando los métodos `and`y `or` como también la función `not`. En este ejemplo el uso de los _operadores de postfijo_ ayuda a crear una definición del método `xor` más fácil de leer. - -Para demostrar esto se muestra el código correspondiente a las funciones anteriores pero escritas en una notación orientada a objetos más tradicional: - - def not(x: MyBool) = x.negate; // punto y coma necesario aquí - def xor(x: MyBool, y: MyBool) = x.or(y).and(x.and(y).negate) diff --git a/es/tutorials/tour/_posts/2017-02-13-pattern-matching.md b/es/tutorials/tour/_posts/2017-02-13-pattern-matching.md deleted file mode 100644 index 416a0454e2..0000000000 --- a/es/tutorials/tour/_posts/2017-02-13-pattern-matching.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -layout: tutorial -title: Reconocimiento de patrones - -discourse: false - -tutorial: scala-tour -categories: tour -num: 20 -language: es - -next-page: polymorphic-methods -previous-page: higher-order-functions ---- - -_Nota de traducción: Es dificil encontrar en nuestro idioma una palabra que se relacione directamente con el significado de `match` en inglés. Podemos entender a `match` como "coincidir" o "concordar" con algo. En algunos lugares se utiliza la palabra `machear`, aunque esta no existe en nuestro idioma con el sentido que se le da en este texto, sino que se utiliza como traducción de `match`._ - -Scala tiene incorporado un mecanismo general de reconocimiento de patrones. Este permite identificar cualquier tipo de datos una política primero-encontrado. Aquí se muestra un pequeño ejemplo el cual muestra cómo coincidir un valor entero: - - object MatchTest1 extends App { - def matchTest(x: Int): String = x match { - case 1 => "one" - case 2 => "two" - case _ => "many" - } - println(matchTest(3)) - } - -El bloque con las sentencias `case` define una función la cual mapea enteros a cadenas de caracteres (strings). La palabra reservada `match` provee una manera conveniente de aplicar una función (como la función anterior) a un objeto. - -Aquí se muestra un ejemplo el cual coincide un valor contra un patrón de diferentes tipos: - - 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")) - } - -El primer `case` coincide si `x` se refiere a un valor entero `1`. El segundo `case` coincide si `x` es igual al string `"two"`. El tercero consiste en un patrón tipado (se provee un tipo); se produce una coincidencia contra cualquier entero que se provea y además se liga la variable `y` al valor pasado `x` de tipo entero. - -El reconocimiento de patrones en Scala es más útil para hacer coincidir tipos algebráicos expresados mediante [clases case](case-classes.html). Scala también permite la definición de patrones independientemente de las clases Case, a través del método `unapply` de [objetos extractores](extractor-objects.html). diff --git a/es/tutorials/tour/_posts/2017-02-13-polymorphic-methods.md b/es/tutorials/tour/_posts/2017-02-13-polymorphic-methods.md deleted file mode 100644 index fe4782c43c..0000000000 --- a/es/tutorials/tour/_posts/2017-02-13-polymorphic-methods.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -layout: tutorial -title: Métodos polimórficos - -discourse: false - -tutorial: scala-tour -categories: tour -num: 21 -language: es - -next-page: regular-expression-patterns -previous-page: pattern-matching ---- - -Los métodos en Scala pueden ser parametrizados tanto con valores como con tipos. Como a nivel de clase, parámetros de valores son encerrados en un par de paréntesis, mientras que los parámetros de tipo son declarados dentro de un par de corchetes. - -Aquí hay un ejemplo: - - object PolyTest extends App { - def dup[T](x: T, n: Int): List[T] = - if (n == 0) Nil - else x :: dup(x, n - 1) - println(dup[Int](3, 4)) // linea 5 - println(dup("three", 3)) // linea 6 - } - -El método `dup` en el objeto `PolyTest` es parametrizado con el tipo `T` y con los parámetros `x: T` y `n: Int`. Cuando el método `dup` es llamado, el programador provee los parámetros requeridos _(vea la linea 5 del programa anterior)_, pero como se muestra en la linea 6 no es necesario que se provea el parámetro de tipo `T` explicitamente. El sistema de tipado de Scala puede inferir estos tipos. Esto es realizado a través de la observación del tipo de los parámetros pasados y del contexto donde el método es invocado. - -Por favor note que el trait `App` está diseñado para escribir programas cortos de pruebas. Debe ser evitado en código en producción (para versiones de Scala 2.8.x y anteriores) ya que puede afectar la habilidad de la JVM de optimizar el código resultante; por favor use `def main()` en su lugar. diff --git a/es/tutorials/tour/_posts/2017-02-13-regular-expression-patterns.md b/es/tutorials/tour/_posts/2017-02-13-regular-expression-patterns.md deleted file mode 100644 index 57b7a3c42b..0000000000 --- a/es/tutorials/tour/_posts/2017-02-13-regular-expression-patterns.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -layout: tutorial -title: Patrones basados en expresiones regulares - -discourse: false - -tutorial: scala-tour -categories: tour -num: 22 -language: es - -next-page: traits -previous-page: polymorphic-methods ---- - -## Patrones de secuencias que ignoran a la derecha ## - -Los patrones de secuencias que ignoran a la derecha son una característica útil para separar cualquier dato que sea tanto un subtipo de `Seq[A]` o una clase case con un parámetro iterador formal, como por ejemplo - - Elem(prefix:String, label:String, attrs:MetaData, scp:NamespaceBinding, children:Node*) - -En esos casos, Scala permite a los patrones que utilicen el cómodin `_*` en la posición más a la derecha que tomen lugar para secuencias arbitrariamente largas. El siguiente ejemplo demuestra un reconocimiento de patrones el cual identifica un prefijo de una secuencia y liga el resto a la variable `rest`. - - 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 - } - } - } - - -A diferencia de versiones previas de Scala, ya no está permitido tener expresiones regulares arbitrarias, por las siguientes razones. - -###Patrones generales de expresiones regulares (`RegExp`) temporariamente retirados de Scala### - -Desde que descubrimos un problema en la precisión, esta característica está temporariamente retirada del lenguaje. Si existiese una petición de parte de la comunidad de usuarios, podríamos llegar a reactivarla de una forma mejorada. - -De acuerdo a nuestra opinión los patrones basados en expresiones regulares no resultaron útiles para el procesamiento de XML. En la vida real, las aplicaciones que procesan XML, XPath parece una opción mucho mejor. Cuando descubrimos que nuestra traducción de los patrones para expresiones regulares tenía algunos errores para patrones raros y poco usados, aunque difícil de excluir, decidimos que sería tiempo de simplificar el lenguaje. diff --git a/es/tutorials/tour/_posts/2017-02-13-sequence-comprehensions.md b/es/tutorials/tour/_posts/2017-02-13-sequence-comprehensions.md deleted file mode 100644 index ccbaf9a289..0000000000 --- a/es/tutorials/tour/_posts/2017-02-13-sequence-comprehensions.md +++ /dev/null @@ -1,61 +0,0 @@ ---- -layout: tutorial -title: Sequencias por Comprensión - -discourse: false - -tutorial: scala-tour -categories: tour -num: 7 -language: es - -next-page: extractor-objects -previous-page: compound-types ---- - -Scala cuenta con una notación ligera para expresar *sequencias por comprensión* (*sequence comprehensions*). Las comprensiones tienen la forma `for (enumeradores) yield e`, donde `enumeradores` se refiere a una lista de enumeradores separados por el símbolo punto y coma (;). Un *enumerador* puede ser tanto un generador el cual introduce nuevas variables, o un filtro. La comprensión evalúa el cuerpo `e` por cada paso (o ciclo) generado por los enumeradores y retorna una secuencia de estos valores. - -Aquí hay un ejemplo: - - object ComprehensionTest1 extends App { - def pares(desde: Int, hasta: Int): List[Int] = - for (i <- List.range(desde, hasta) if i % 2 == 0) yield i - Console.println(pares(0, 20)) - } - -La expresión `for` en la función introduce una nueva variable `i` de tipo `Int` la cual es subsecuentemente atada a todos los valores de la lista `List(desde, desde + 1, ..., hasta - 1)`. La guarda `if i % 2 == 0` filtra los números impares por lo que el cuerpo (que solo consiste de la expresión `i`) es solamente evaluado para números pares. Consecuentemente toda la expresión `for` retorna una lista de números pares. - -El programa produce los siguientes valores - - List(0, 2, 4, 6, 8, 10, 12, 14, 16, 18) - -Aquí se muestra un ejemplo más complicado que computa todos los pares de números entre `0` y `n-1` cuya suma es igual a un número dado `v`: - - object ComprehensionTest2 extends App { - def foo(n: Int, v: Int) = - for (i <- 0 until n; - j <- i until n if i + j == v) yield - Pair(i, j); - foo(20, 32) foreach { - case (i, j) => - println("(" + i + ", " + j + ")") - } - } - -Este ejemplo muestra que las comprensiones no están restringidas solo a listas. El programa anterior usa iteradores en su lugar. Cualquier tipo de datos que soporte las operaciones `withFilter`, `map`, y `flatMap` (con los tipos apropiados) puede ser usado en la comprensión de secuencias. - -Esta es la salida del programa: - - (13, 19) - (14, 18) - (15, 17) - (16, 16) - -Existe también una forma especial de comprensión de secuencias la cual retorna `Unit`. En este caso las variables que son creadas por la lista de generadores y filtros son usados para realizar tareas con efectos colaterales (modificaciones de algún tipo). El programador tiene que omitir la palabra reservada `yield` para usar una comprensión de este tipo. - - object ComprehensionTest3 extends App { - for (i <- Iterator.range(0, 20); - j <- Iterator.range(i, 20) if i + j == 32) - println("(" + i + ", " + j + ")") - } - diff --git a/es/tutorials/tour/_posts/2017-02-13-singleton-objects.md b/es/tutorials/tour/_posts/2017-02-13-singleton-objects.md deleted file mode 100644 index 86e019406a..0000000000 --- a/es/tutorials/tour/_posts/2017-02-13-singleton-objects.md +++ /dev/null @@ -1,68 +0,0 @@ ---- -layout: tutorial -title: Singleton Objects - -discourse: false - -tutorial: scala-tour -categories: tour -num: 12 -language: es - -next-page: nested-functions -previous-page: mixin-class-composition ---- - -Métodos y valores que no están asociados con instancias individuales de una [clase](classes.html) se denominan *objetos singleton* y se denotan con la palabra reservada `object` en vez de `class`. - - package test - - object Blah { - def sum(l: List[Int]): Int = l.sum - } - -Este método `sum` está disponible de manera global, y puede ser referenciado, o importado, como `test.Blah.sum`. - -Los objetos singleton son una especie de mezcla entre la definición de una clase de utilización única, la cual no pueden ser instanciada directamente, y un miembro `val`. De hecho, de la misma menera que los `val`, los objetos singleton pueden ser definidos como miembros de un [trait](traits.html) o de una clase, aunque esto no es muy frecuente. - -Un objeto singleton puede extender clases y _traits_. De hecho, una [clase Case](case-classes.html) sin [parámetros de tipo](generic-classes.html) generará por defecto un objeto singleton del mismo nombre, con una [`Función*`](http://www.scala-lang.org/api/current/scala/Function1.html) trait implementada. - -## Acompañantes ## - -La mayoría de los objetos singleton no están solos, sino que en realidad están asociados con clases del mismo nombre. El "objeto singleton del mismo nombre" de una case Case, mencionada anteriormente es un ejemplo de esto. Cuando esto sucede, el objeto singleton es llamado el *objeto acompañante* de la clase, y la clase es a su vez llamada la *clase acompañante* del objeto. - -[Scaladoc](https://wiki.scala-lang.org/display/SW/Introduction) proporciona un soporte especial para ir y venir entre una clase y su acompañante: Si el gran círculo conteniendo la “C” u la “O” tiene su borde inferior doblado hacia adentro, es posible hacer click en el círculo para ir a su acompañante. - -Una clase y su objeto acompañante, si existe, deben estar definidos en el mismo archivo fuente. Como por ejemplo: - - 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)) - } - -Es común ver instancias de clases tipo como [valores implícitos](implicit-parameters.html), (`ipord` en el ejemplo anterior) definida en el acompañante cuando se sigue el patron de clases tipo. Esto es debido a que los miembros del acompañante se incluyen en la búsqueda de implícitos por defecto. - -## Notas para los programadores Java ## -`static` no es una palabra reservada en Scala. En cambio, todos los miembros que serían estáticos, incluso las clases, van en los objetos acompañantes. Estos, pueden ser referenciados usando la misma sintaxis, importados de manera individual o en grupo, etc. - -Frecuentemente, los programadores Java, definen miembros estáticos, incluso definidos como `private`, como ayudas en la implementacion de los miembros de la instancia. Estos elementos también van en el objeto acompañante. Un patrón comúnmente utilizado es de importar los miembros del objeto acompañante en la clase, como por ejemplo: - - class X { - import X._ - - def blah = foo - } - - object X { - private def foo = 42 - } - -Esto permite ilustrar otra característica: en el contexto de un `private`, una clase y su acompañante son amigos. El `objecto X` puede acceder miembros de la `clase X`, y vice versa. Para hacer un miembro *realmente* privado para uno u otro, utilice `private[this]`. - -Para conveniencia de Java, los métodos que incluyen `var` y `val`, definidos directamente en un objeto singleton también tienen un método estático definido en la clase acompañante, llamado *static forwarder*. Otros miembros son accesibles por medio del campo estático `X$.MODULE$` para el `objeto X`. - -Si todos los elementos se mueven al objeto acompanante y se descubre que lo que queda es una clase que no se quiere instanciar, entonces simplemente bórrela. Los *static forwarder* de todas formas van a ser creados. diff --git a/es/tutorials/tour/_posts/2017-02-13-tour-of-scala.md b/es/tutorials/tour/_posts/2017-02-13-tour-of-scala.md deleted file mode 100644 index f553dc7d97..0000000000 --- a/es/tutorials/tour/_posts/2017-02-13-tour-of-scala.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -layout: tutorial -title: Introducción - -discourse: false - -tutorial: scala-tour -categories: tour -num: 1 -language: es - -next-page: abstract-types ---- - -Scala es un lenguaje de programación moderno multi-paradigma diseñado para expresar patrones de programación comunes de una forma concisa, elegante, y de tipado seguro. Integra fácilmente características de lenguajes orientados a objetos y funcionales. - -## Scala es orientado a objetos ## -Scala es un lenguaje puramente orientado a objetos en el sentido de que [todo es un objeto](unified_types.html). Los tipos y comportamientos de objetos son descritos por [clases](classes.html) y [traits](traits.html) (que podría ser traducido como un "rasgo"). Las clases pueden ser extendidas a través de subclases y un mecanismo flexible [de composición mezclada](mixin-class-composition.html) que provee un claro remplazo a la herencia múltiple. - -## Scala es funcional ## -Scala es también un lenguaje funcional en el sentido que [toda función es un valor](unified_types.html). Scala provee una [sintaxis ligera](anonymous-function-syntax.html) para definir funciones anónimas. Soporta [funciones de primer orden](higher-order-functions.html), permite que las funciones sean [anidadas](nested-functions.html), y soporta [currying](currying.html). Las [clases caso](case-classes.html) de Scala y las construcciones incorporadas al lenguaje para [reconocimiento de patrones](pattern-matching.html) modelan tipos algebráicos usados en muchos lenguajes de programación funcionales. - -Además, la noción de reconocimiento de patrones de Scala se puede extender naturalmente al [procesamiento de datos XML](xml-processing.html) con la ayuda de [patrones de expresiones regulares](regular-expression-patterns.html). En este contexto, [seq comprehensions](sequence-comprehensions.html) resultan útiles para formular consultas. Estas características hacen a Scala ideal para desarrollar aplicaciones como Web Services. - -## Scala estáticamente tipado ## -Scala cuenta con un expresivo sistema de tipado que fuerza estáticamente las abstracciones a ser usadas en una manera coherente y segura. En particular, el sistema de tipado soporta: -* [Clases genéricas](generic-classes.html) -* [anotaciones variables](variances.html), -* límites de tipado [superiores](upper-type-bounds.html) e [inferiores](lower-type-bounds.html), -* [clases internas](inner-classes.html) y [tipos abstractos](abstract-types.html) como miembros de objetos, -* [tipos compuestos](compound-types.html) -* [auto-referencias explicitamente tipadas](explicitly-typed-self-references.html) -* [implicit conversions](implicit-conversions.html) -* [métodos polimórficos](polymorphic-methods.html) - -El [mecanismo de inferencia de tipos locales](local-type-inference.html) se encarga de que el usuario no tengan que anotar el programa con información redundante de tipado. Combinadas, estas características proveen una base poderosa para el reuso seguro de abstracciones de programación y para la extensión segura (en cuanto a tipos) de software. - -## Scala es extensible ## - -En la práctica el desarrollo de aplicaciones específicas para un dominio generalmente requiere de "Lenguajes de dominio específico" (DSL). Scala provee una única combinación de mecanismos del lenguaje que simplifican la creación de construcciones propias del lenguaje en forma de librerías: -* cualquier método puede ser usado como un operador de [infijo o postfijo](operators.html) -* [las closures son construidas automáticamente dependiendo del tipo esperado](automatic-closures.html) (tipos objetivo). - -El uso conjunto de ambas características facilita la definición de nuevas sentencias sin tener que extender la sintaxis y sin usar facciones de meta-programación como tipo macros. - -Scala está diseñado para interoperar bien con el popular Entorno de ejecución de Java 2 (JRE). En particular, la interacción con el lenguaje orientado a objetos Java es muy sencillo. Scala tiene el mismo esquema de compilación (compilación separada, carga de clases dinámica) que java y permite acceder a las miles de librerías de gran calidad existentes. - -Por favor continúe a la próxima página para conocer más. diff --git a/es/tutorials/tour/_posts/2017-02-13-traits.md b/es/tutorials/tour/_posts/2017-02-13-traits.md deleted file mode 100644 index e3082c44af..0000000000 --- a/es/tutorials/tour/_posts/2017-02-13-traits.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -layout: tutorial -title: Traits - -discourse: false - -tutorial: scala-tour -categories: tour -num: 24 -language: es - -next-page: upper-type-bounds -previous-page: regular-expression-patterns ---- - -_Nota de traducción: La palabra `trait` en inglés puede traducirse literalmente como `rasgo` o `caracteristica`. Preferimos la designación original trait por ser una característica muy natural de Scala._ - -De forma similar a las interfaces de Java, los traits son usados para definir tipos de objetos al especificar el comportamiento mediante los métodos provistos. A diferencia de Java, Scala permite a los traits ser parcialmente implementados, esto es, es posible definir implementaciones por defecto para algunos métodos. En contraste con las clases, los traits no pueden tener parámetros de constructor. -A continuación se muestra un ejemplo: - - trait Similarity { - def isSimilar(x: Any): Boolean - def isNotSimilar(x: Any): Boolean = !isSimilar(x) - } - -Este trait consiste de dos métodos `isSimilar` y `isNotSimilar`. Mientras `isSimilar` no provee una implementación concreta del método (es abstracto en la terminología Java), el método `isNotSimilar` define una implementación concreta. Consecuentemente, las clases que integren este trait solo tienen que proveer una implementación concreta para `isSimilar`. El comportamiento de `isNotSimilar` es directamente heredado del trait. Los traits típicamente son integrados a una clase (u otros traits) mediante una [Composición de clases mixin](mixin-class-composition.html): - - 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) - println(p1.isNotSimilar(p2)) - println(p1.isNotSimilar(p3)) - println(p1.isNotSimilar(2)) - } - -Esta es la salida del programa: - - false - true - true diff --git a/es/tutorials/tour/_posts/2017-02-13-unified-types.md b/es/tutorials/tour/_posts/2017-02-13-unified-types.md deleted file mode 100644 index d0a654a231..0000000000 --- a/es/tutorials/tour/_posts/2017-02-13-unified-types.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -layout: tutorial -title: Tipos Unificados - -discourse: false - -tutorial: scala-tour -categories: tour -num: 30 -language: es - -next-page: variances -previous-page: local-type-inference ---- - -A diferencia de Java, todos los valores en Scala son objetos (incluyendo valores numéricos y funciones). Dado que Scala está basado en clases, todos los valores son instancias de una clase. El diagrama siguiente ilustra esta jerarquía de clases: - -![Jerarquía de Tipos de Scala]({{ site.baseurl }}/resources/images/classhierarchy.img_assist_custom.png) - -## Jerarquía de clases en Scala ## - -La superclase de todas las clases, `scala.Any`, tiene dos subclases directas, `scala.AnyVal` y `scala.AnyRef` que representan dos mundos de clases muy distintos: clases para valores y clases para referencias. Todas las clases para valores están predefinidas; se corresponden con los tipos primitivos de los lenguajes tipo Java. Todas las otras clases definen tipos referenciables. Las clases definidas por el usuario son definidas como tipos referenciables por defecto, es decir, siempre (indirectamente) extienden de `scala.AnyRef`. Toda clase definida por usuario en Scala extiende implicitamente el trait `scala.ScalaObject`. Clases pertenecientes a la infraestructura en la cual Scala esté corriendo (ejemplo, el ambiente de ejecución de Java) no extienden de `scala.ScalaObject`. Si Scala es usado en el contexto de un ambiente de ejecución de Java, entonces `scala.AnyRef` corresponde a `java.lang.Object`. -Por favor note que el diagrama superior también muestra conversiones implícitas llamadas viestas entre las clases para valores. - -Aquí se muestra un ejemplo que demuestra que tanto valores numéricos, de caracteres, buleanos y funciones son objetos, tal como cualquier otro objeto: - - object UnifiedTypes extends App { - val set = new scala.collection.mutable.LinkedHashSet[Any] - set += "This is a string" // suma un String - set += 732 // suma un número - set += 'c' // suma un caracter - set += true // suma un valor booleano - set += main _ // suma la función main - val iter: Iterator[Any] = set.iterator - while (iter.hasNext) { - println(iter.next.toString()) - } - } - -El programa declara una aplicación `UnifiedTypes` en forma de un objeto singleton de primer nivel con un método `main`. La aplicación define una variable local `set` (un conjunto), la cual se refiere a una instancia de la clase `LinkedHashSet[Any]`. El programa suma varios elementos a este conjunto. Los elementos tienen que cumplir con el tipo declarado para los elementos del conjunto, que es `Any`. Al final, una representación en texto (cadena de caracteres, o string) es impresa en pantalla. - -Aquí se muestra la salida del programa: - - This is a string - 732 - c - true - diff --git a/es/tutorials/tour/_posts/2017-02-13-upper-type-bounds.md b/es/tutorials/tour/_posts/2017-02-13-upper-type-bounds.md deleted file mode 100644 index 74a4bbb616..0000000000 --- a/es/tutorials/tour/_posts/2017-02-13-upper-type-bounds.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -layout: tutorial -title: Límite de tipado superior - -discourse: false - -tutorial: scala-tour -categories: tour -num: 25 -language: es - -next-page: lower-type-bounds -previous-page: traits ---- - -En Scala, los [parámetros de tipo](generic-classes.html) y los [tipos abstractos](abstract-types.html) pueden ser restringidos por un límite de tipado. Tales límites de tipado limitan los valores concretos de las variables de tipo y posiblemente revelan más información acerca de los miembros de tales tipos. Un _límite de tipado superior_ `T <: A` declara que la variable de tipo `T` es un subtipo del tipo `A`. -Aquí se muestra un ejemplo el cual se basa en un límite de tipado superior para la implementación del método polimórfico `findSimilar`: - - trait Similar { - def isSimilar(x: Any): Boolean - } - case class MyInt(x: Int) extends Similar { - def isSimilar(m: Any): Boolean = - m.isInstanceOf[MyInt] && - m.asInstanceOf[MyInt].x == x - } - object UpperBoundTest extends App { - def findSimilar[T <: Similar](e: T, xs: List[T]): Boolean = - if (xs.isEmpty) false - else if (e.isSimilar(xs.head)) true - else findSimilar[T](e, xs.tail) - val list: List[MyInt] = List(MyInt(1), MyInt(2), MyInt(3)) - println(findSimilar[MyInt](MyInt(4), list)) - println(findSimilar[MyInt](MyInt(2), list)) - } - -Sin la anotación del límite de tipado superior no sería posible llamar al método `isSimilar` en el método `findSimilar`. El uso de los límites de tipado inferiores se discute [aquí](lower-type-bounds.html). diff --git a/es/tutorials/tour/_posts/2017-02-13-variances.md b/es/tutorials/tour/_posts/2017-02-13-variances.md deleted file mode 100644 index 5b9832e7e4..0000000000 --- a/es/tutorials/tour/_posts/2017-02-13-variances.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -layout: tutorial -title: Varianzas - -discourse: false - -tutorial: scala-tour -categories: tour -num: 31 -language: es - -next-page: implicit-conversions -previous-page: unified-types ---- - -Scala soporta anotaciones de varianza para parámetros de tipo para [clases genéricas](generic-classes.html). A diferencia de Java 5 ([JDK 1.5](http://java.sun.com/j2se/1.5/)), las anotaciones de varianza pueden ser agregadas cuando una abstracción de clase es definidia, mientras que en Java 5, las anotaciones de varianza son dadas por los clientes cuando una albstracción de clase es usada. - -En el artículo sobre clases genéricas dimos un ejemplo de una pila mutable. Explicamos que el tipo definido por la clase `Stack[T]` es objeto de subtipos invariantes con respecto al parámetro de tipo. Esto puede restringir el reuso de la abstracción (la clase). Ahora derivaremos una implementación funcional (es decir, inmutable) para pilas que no tienen esta restricción. Nótese que este es un ejemplo avanzado que combina el uso de [métodos polimórficos](polymorphic-methods.html), [límites de tipado inferiores](lower-type-bounds.html), y anotaciones de parámetros de tipo covariante de una forma no trivial. Además hacemos uso de [clases internas](inner-classes.html) para encadenar los elementos de la pila sin enlaces explícitos. - -```tut -class Stack[+T] { - def push[S >: T](elem: S): Stack[S] = new Stack[S] { - override def top: S = elem - override def pop: Stack[S] = Stack.this - override def toString: String = - elem.toString + " " + Stack.this.toString - } - def top: T = sys.error("no element on stack") - def pop: Stack[T] = sys.error("no element on stack") - override def toString: String = "" -} - -object VariancesTest extends App { - var s: Stack[Any] = new Stack().push("hello") - s = s.push(new Object()) - s = s.push(7) - println(s) -} -``` - -La anotación `+T` declara que el tipo `T` sea utilizado solamente en posiciones covariantes. De forma similar, `-T` declara que `T` sea usado en posiciones contravariantes. Para parámetros de tipo covariantes obtenemos una relación de subtipo covariante con respecto al parámetro de tipo. Para nuestro ejemplo, esto significa que `Stack[T]` es un subtipo de `Stack[S]` si `T` es un subtipo de `S`. Lo contrario se cumple para parámetros de tipo que son etiquetados con un signo `-`. - -Para el ejemplo de la pila deberíamos haber usado el parámetro de tipo covariante `T` en una posición contravariante para que nos sea posible definir el método `push`. Ya que deseamos que existan subtipos covariantes para las pilas, utilizamos un truco y utilizamos un parámetro de tipo abstracto en el método `push`. De esta forma obtenemos un método polimórfico en el cual utilizamos el tipo del elemento `T` como límite inferior de la variable de tipo de `push`. Esto tiene el efecto de sincronizar la varianza de `T` con su declaración como un parámetro de tipo covariante. Ahora las pilas son covariantes, y nuestra solución permite por ejemplo apilar un String en una pila de enteros (Int). El resultado será una pila de tipo `Stack[Any]`; por lo tanto solo si el resultado es utilizado en un contexto donde se esperan pilas de enteros se detectará un error. De otra forma, simplemente se obtiene una pila con un tipo más general. diff --git a/fr/cheatsheets/index.md b/fr/cheatsheets/index.md deleted file mode 100644 index d6a828a6d6..0000000000 --- a/fr/cheatsheets/index.md +++ /dev/null @@ -1,86 +0,0 @@ ---- -layout: cheatsheet -istranslation: true -title: Scalacheat -by: Brendan O'Connor -about: Grâce à Brendan O'Connor, ce memento vise à être un guide de référence rapide pour les constructions syntaxiques en Scala. Licencié par Brendan O'Connor sous licence CC-BY-SA 3.0. -language: fr ---- - -###### Contribué par {{ page.by }} - -| | | -| ------ | ------ | -| variables | | -| `var x = 5` | variable | -| Good `val x = 5`
        Bad `x=6` | constante | -| `var x: Double = 5` | type explicite | -| fonctions | | -| Good `def f(x: Int) = { x*x }`
        Bad `def f(x: Int) { x*x }` | définition d'une fonction
        erreur cachée : sans le = c'est une procédure qui retourne un Unit ; occasionnant des problèmes incontrôlés. | -| Good `def f(x: Any) = println(x)`
        Bad `def f(x) = println(x)` | définition d'une fonction
        erreur de syntaxe : chaque argument a besoin d'être typé. | -| `type R = Double` | alias de type | -| `def f(x: R)` vs.
        `def f(x: => R)` | appel par valeur
        appel par nom (paramètres paresseux (lazy)) | -| `(x:R) => x*x` | fonction anonyme | -| `(1 to 5).map(_*2)` vs.
        `(1 to 5).reduceLeft( _+_ )` | fonction anonyme : l'underscore est associé à la position du paramètre en argument. | -| `(1 to 5).map( x => x*x )` | fonction anonyme : pour utiliser un argument deux fois, il faut le nommer. | -| Good `(1 to 5).map(2*)`
        Bad `(1 to 5).map(*2)` | fonction anonyme : méthode bornée et infixée. Pour votre santé, préférez la syntaxe `2*_`. | -| `(1 to 5).map { x => val y=x*2; println(y); y }` | fonction anonyme : la dernière expression d'un bloc est celle qui est retournée. | -| `(1 to 5) filter {_%2 == 0} map {_*2}` | fonctions anonymes : style "pipeline". (ou avec des parenthèses). | -| `def compose(g:R=>R, h:R=>R) = (x:R) => g(h(x))`
        `val f = compose({_*2}, {_-1})` | fonctions anonymes : pour passer plusieurs blocs, il faut les entourer par des parenthèses. | -| `val zscore = (mean:R, sd:R) => (x:R) => (x-mean)/sd` | curryfication, syntaxe évidente. | -| `def zscore(mean:R, sd:R) = (x:R) => (x-mean)/sd` | curryfication, syntaxe évidente. | -| `def zscore(mean:R, sd:R)(x:R) = (x-mean)/sd` | curryfication, sucre syntaxique. mais alors : | -| `val normer = zscore(7, 0.4) _` | il faut ajouter l'underscore dans la fonction partielle, mais ceci uniquement pour la version avec le sucre syntaxique. | -| `def mapmake[T](g:T=>T)(seq: List[T]) = seq.map(g)` | type générique. | -| `5.+(3); 5 + 3`
        `(1 to 5) map (_*2)` | sucre syntaxique pour opérateurs infixés. | -| `def sum(args: Int*) = args.reduceLeft(_+_)` | arguments variadiques. | -| paquetages | | -| `import scala.collection._` | import global. | -| `import scala.collection.Vector`
        `import scala.collection.{Vector, Sequence}` | import sélectif. | -| `import scala.collection.{Vector => Vec28}` | renommage d'import. | -| `import java.util.{Date => _, _}` | importe tout de java.util excepté Date. | -| `package pkg` _en début de fichier_
        `package pkg { ... }` | déclare un paquetage. | -| structures de données | | -| `(1,2,3)` | tuple littéral. (`Tuple3`) | -| `var (x,y,z) = (1,2,3)` | liaison déstructurée : le déballage du tuple se fait par le "pattern matching". | -| Bad`var x,y,z = (1,2,3)` | erreur cachée : chaque variable est associée au tuple au complet. | -| `var xs = List(1,2,3)` | liste (immuable). | -| `xs(2)` | indexe un élément par le biais des parenthèses. ([transparents](http://www.slideshare.net/Odersky/fosdem-2009-1013261/27)) | -| `1 :: List(2,3)` | créé une liste par le biais de l'opérateur "cons".| -| `1 to 5` _est équivalent à_ `1 until 6`
        `1 to 10 by 2` | sucre syntaxique pour les plages de valeurs. | -| `()` _(parenthèses vides)_ | l'unique membre de type Unit (à l'instar de void en C/Java). | -| structures de constrôle | | -| `if (check) happy else sad` | test conditionnel. | -| `if (check) happy` _est équivalent à_
        `if (check) happy else ()` | sucre syntaxique pour un test conditionnel. | -| `while (x < 5) { println(x); x += 1}` | boucle while. | -| `do { println(x); x += 1} while (x < 5)` | boucle do while. | -| `import scala.util.control.Breaks._`
        `breakable {`
        ` for (x <- xs) {`
        ` if (Math.random < 0.1) break`
        ` }`
        `}`| break. ([transparents](http://www.slideshare.net/Odersky/fosdem-2009-1013261/21)) | -| `for (x <- xs if x%2 == 0) yield x*10` _est équivalent à_
        `xs.filter(_%2 == 0).map(_*10)` | *for comprehension*: filter/map | -| `for ((x,y) <- xs zip ys) yield x*y` _est équivalent à_
        `(xs zip ys) map { case (x,y) => x*y }` | *for comprehension* : liaison déstructurée | -| `for (x <- xs; y <- ys) yield x*y` _est équivalent à_
        `xs flatMap {x => ys map {y => x*y}}` | *for comprehension* : produit cartésien. | -| `for (x <- xs; y <- ys) {`
        `println("%d/%d = %.1f".format(x, y, x/y.toFloat))`
        `}` | *for comprehension* : à la manière impérative
        [sprintf-style](http://java.sun.com/javase/6/docs/api/java/util/Formatter.html#syntax) | -| `for (i <- 1 to 5) {`
        `println(i)`
        `}` | *for comprehension* : itère jusqu'à la borne supérieure comprise. | -| `for (i <- 1 until 5) {`
        `println(i)`
        `}` | *for comprehension* : itère jusqu'à la borne supérieure non comprise. | -| pattern matching | | -| Good `(xs zip ys) map { case (x,y) => x*y }`
        Bad `(xs zip ys) map( (x,y) => x*y )` | cas d’utilisation d’une fonction utilisée avec un "pattern matching". | -| Bad
        `val v42 = 42`
        `Some(3) match {`
        ` case Some(v42) => println("42")`
        ` case _ => println("Not 42")`
        `}` | "v42" est interprété comme un nom ayant n’importe quelle valeur de type Int, donc "42" est affiché. | -| Good
        `val v42 = 42`
        `Some(3) match {`
        `` case Some(`v42`) => println("42")``
        `case _ => println("Not 42")`
        `}` | "\`v42\`" x les "backticks" est interprété avec la valeur de val `v42`, et "Not 42" est affiché. | -| Good
        `val UppercaseVal = 42`
        `Some(3) match {`
        ` case Some(UppercaseVal) => println("42")`
        ` case _ => println("Not 42")`
        `}` | `UppercaseVal`i est traité avec la valeur contenue dans val, plutôt qu’un nouvelle variable du "pattern", parce que cela commence par une lettre en capitale. Ainsi, la valeur contenue dans `UppercaseVal` est comparée avec `3`, et "Not 42" est affiché. | -| l'orienté objet | | -| `class C(x: R)` _est équivalent à_
        `class C(private val x: R)`
        `var c = new C(4)` | paramètres du constructeur - privé | -| `class C(val x: R)`
        `var c = new C(4)`
        `c.x` | paramètres du constructeur - public | -| `class C(var x: R) {`
        `assert(x > 0, "positive please")`
        `var y = x`
        `val readonly = 5`
        `private var secret = 1`
        `def this = this(42)`
        `}`|
        le constructeur est dans le corps de la classe
        déclare un membre public
        déclare un accesseur
        déclare un membre privé
        constructeur alternatif | -| `new{ ... }` | classe anonyme | -| `abstract class D { ... }` | définition d’une classe abstraite. (qui n’est pas instanciable). | -| `class C extends D { ... }` | définition d’une classe qui hérite d’une autre. | -| `class D(var x: R)`
        `class C(x: R) extends D(x)` | héritage et constructeurs paramétrés. (souhaits : pouvoir passer les paramètres automatiquement par défaut). -| `object O extends D { ... }` | définition d’un singleton. (à la manière d'un module) | -| `trait T { ... }`
        `class C extends T { ... }`
        `class C extends D with T { ... }` | traits.
        interfaces avec implémentation. constructeur sans paramètre. [mixin-able]({{ site.baseurl }}/tutorials/tour/mixin-class-composition.html). -| `trait T1; trait T2`
        `class C extends T1 with T2`
        `class C extends D with T1 with T2` | multiple traits. | -| `class C extends D { override def f = ...}` | doit déclarer une méthode surchargée. | -| `new java.io.File("f")` | créé un objet. | -| Bad `new List[Int]`
        Good `List(1,2,3)` | erreur de typage : type abstrait
        : au contraire, par convention : la fabrique appelée masque le typage.| -| `classOf[String]` | classe littérale. | -| `x.isInstanceOf[String]` | vérification de types (à l’exécution) | -| `x.asInstanceOf[String]` | "casting" de type (à l’exécution) | -| `x: String` | attribution d’un type (à la compilation) | diff --git a/getting-started-intellij-track/building-a-scala-project-with-intellij-and-sbt.md b/getting-started-intellij-track/building-a-scala-project-with-intellij-and-sbt.md new file mode 100644 index 0000000000..031a92dbdd --- /dev/null +++ b/getting-started-intellij-track/building-a-scala-project-with-intellij-and-sbt.md @@ -0,0 +1,106 @@ +--- +title: Building a Scala Project with IntelliJ and sbt +layout: singlepage-overview +disqus: true +previous-page: getting-started-intellij-track/getting-started-with-scala-in-intellij +next-page: testing-scala-in-intellij-with-scalatest +--- + +In this tutorial, we'll see how to build a Scala project using [sbt](http://www.scala-sbt.org/0.13/docs/index.html). sbt is a popular tool for compiling, running, and testing Scala projects of any +size. Using a build tool such as sbt (or Maven/Gradle) becomes essential once you create projects with dependencies +or more than one code file. + We assume you've completed the +[first tutorial](getting-started-with-scala-in-intellij.html). + +## Creating the project +In this section, we'll show you how to create the project in IntelliJ. However, if you're +comfortable with the command line, we recommend you try [Getting +Started with Scala and sbt on the Command Line]({{site.baseurl}}/documentation/getting-started-sbt-track/getting-started-with-scala-and-sbt-on-the-command-line.html) and then come back + here to the section "Writing Scala code". + +1. If you didn't create the project from the command line, open up IntelliJ and select "Create New Project" + * On the left panel, select Scala and on the right panel, select SBT + * Click **Next** + * Name the project "SBTExampleProject" +1. If you already created the project on the command line, open up IntelliJ, select *Import Project* and open the `build.sbt` file for your project +1. Make sure the **JDK Version** is 1.8 and the **SBT Version** is at least 0.13.13 +1. Select **Use auto-import** so dependencies are automatically downloaded when available +1. Select **Finish** + +## Understanding the directory structure +sbt creates many directories which can be useful once you start building +more complex projects. You can ignore most of them for now +but here's a glance at what everything is for: + +``` +- .idea (IntelliJ files) +- project (plugins and additional settings for sbt) +- src (source files) + - main (application code) + - java (Java source files) + - scala (Scala source files) <-- This is all we need for now + - scala-2.12 (Scala 2.12 specific files) + - test (unit tests) +- target (generated files) +- build.sbt (build definition file for sbt) +``` + + +## Writing Scala code +1. On the **Project** panel on the left, expand `SBTExampleProject` => `src` +=> `main` +1. Right-click `scala` and select **New** => **Package** +1. Name the package `example` and click **OK**. +1. Right-click the package `example` and select **New** => **Scala class**. +1. Name the class `Main` and change the **Kind** to `object`. +1. Change the code in the class to the following: + +``` +object Main extends App { + val ages = Seq(42, 75, 29, 64) + println(s"The oldest person is ${ages.max}") +} +``` + +Note: IntelliJ has its own implementation the Scala compiler, and sometimes your +code is correct even though IntelliJ indicates otherwise. You can always check +to see if sbt can run your project on the command line. + +## Running the project +1. From the **Run** menu, select **Edit configurations** +1. Click the **+** button and select **SBT Task**. +1. Name it `Run the program`. +1. In the **Tasks** field, type `~run`. The `~` causes SBT to rebuild and rerun the project +when you save changes to a file in the project. +1. Click **OK**. +1. On the **Run** menu. Click **Run 'Run the program'**. +1. In the code, change `currentYear - 1` to `currentYear - 2` +and look at the updated output in the console. + +## Adding a dependency +Changing gears a bit, let's look at how to use published libraries to add +extra functionality to our apps. +1. Open up `build.sbt` and add the following line: + +``` +libraryDependencies += "org.scala-lang.modules" %% "scala-parser-combinators" % "1.0.5" +``` +Here, `libraryDependencies` is a set of dependencies, and by using `+=`, +we're adding the [scala-parser-combinators]({{site.baseurl}}/scala/scala-parser-combinators) dependency to the set of dependencies that sbt will go +and fetch when it starts up. Now, in any Scala file, you can import classes, +objects, etc, from scala-parser-combinators with a regular import. + +You can find more published libraries on +[Scaladex](https://index.scala-lang.org/), the Scala library index, where you +can also copy the above dependency information for pasting into your `build.sbt` +file. + +## Next steps + +Continue to the next tutorial in the _getting started with IntelliJ_ series, and learn about [testing Scala code in IntelliJ with ScalaTest](testing-scala-in-intellij-with-scalatest.html). + +**or** + +- Continue learning Scala interactively online on + [Scala Exercises](https://www.scala-exercises.org/scala_tutorial). +- Learn about Scala's features in bite-sized pieces by stepping through our [Tour of Scala]({{ site.baseurl }}/tour/tour-of-scala.html }). diff --git a/getting-started-intellij-track/getting-started-with-scala-in-intellij.md b/getting-started-intellij-track/getting-started-with-scala-in-intellij.md new file mode 100644 index 0000000000..cae7e7491a --- /dev/null +++ b/getting-started-intellij-track/getting-started-with-scala-in-intellij.md @@ -0,0 +1,76 @@ +--- +title: Getting Started with Scala in IntelliJ +layout: singlepage-overview +disqus: true +next-page: building-a-scala-project-with-intellij-and-sbt +--- + +In this tutorial, we'll see how to build a minimal Scala project using IntelliJ +IDE with the Scala plugin. In this guide, IntelliJ will download Scala for you. + +## Installation +1. Make sure you have the Java 8 JDK (also known as 1.8) + * Run `javac -version` on the command line and make sure you see + `javac 1.8.___` + * If you don't have version 1.8 or higher, [install the JDK](http://www.oracle.com/technetwork/java/javase/downloads/index.html) +1. Next, download and install [IntelliJ Community Edition](https://www.jetbrains.com/idea/download/) +1. Then, after starting up IntelliJ, you can download and install the Scala plugin by following the instructions on +[how to install IntelliJ plugins](https://www.jetbrains.com/help/idea/installing-updating-and-uninstalling-repository-plugins.html) (search for "Scala" in the plugins menu.) + +When we create the project, we'll install the latest version of Scala. +Note: If you want to open an existing Scala project, you can click **Open** +when you start IntelliJ. + +## Creating the Project +1. Open up IntelliJ and click **File** => **New** => **Project** +1. On the left panel, select Scala. On the right panel, select Scala once again. +1. Name the project **HelloWorld** +1. Assuming this is your first time creating a Scala project with IntelliJ, +you'll need to install a Scala SDK. To the right of the Scala SDK field, +click the **Create** button. +1. Select the highest version number (e.g. {{ site.scala-version }}) and click **Download**. This might +take a few minutes but subsequent projects can use the same SDK. +1. Once the SDK is created and you're back to the "New Project" window click **Finish**. + + +## Writing code + +1. On the **Project** pane on the left, right-click `src` and select +**New** => **Scala class**. +1. Name the class `Hello` and change the **Kind** to `object`. +1. Change the code in the class to the following: + +``` +object Hello extends App { + println("Hello, World!") +} +``` + +## Running it +* Right click on `Hello` in your code and select **Run 'Hello'**. +* You're done! + +## Experimenting with Scala +A good way to try out code samples is with Scala Worksheets + +1. In the project pane on the left, right click +`src` and select **New** => **Scala Worksheet**. +1. Enter the following code into the worksheet: + +``` +def square(x: Int) = x * x + +square(2) +``` + +As you change your code, you'll notice that it gets evaluated +in the right pane. + +## Next Steps + +Now you know how to create a simple Scala project which can be used +for starting to learn the language. In the next tutorial, we'll introduce +an important build tool called sbt which can be used for simple projects +and production apps. + +Up Next: [Building a Scala Project with IntelliJ and sbt](building-a-scala-project-with-intellij-and-sbt.html) diff --git a/getting-started-intellij-track/testing-scala-in-intellij-with-scalatest.md b/getting-started-intellij-track/testing-scala-in-intellij-with-scalatest.md new file mode 100644 index 0000000000..4cbafd5840 --- /dev/null +++ b/getting-started-intellij-track/testing-scala-in-intellij-with-scalatest.md @@ -0,0 +1,78 @@ +--- +title: Testing Scala in IntelliJ with ScalaTest +layout: singlepage-overview +disqus: true +previous-page: building-a-scala-project-with-intellij-and-sbt +--- + +There are multiple libraries and testing methodologies for Scala, +but in this tutorial, we'll demonstrate one popular option from the ScalaTest framework +called [FunSuite](http://www.scalatest.org/getting_started_with_fun_suite). +We assume you know [how to build a project in IntelliJ](building-a-scala-project-with-intellij-and-sbt.html). + +## Setup +1. Create an sbt project in IntelliJ. +* Add the ScalaTest dependency to your build.sbt file: + +``` +libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.1" % "test" +``` + +1. this will cause sbt to pull down the ScalaTest library +1. If you get a notification "build.sbt was changed", select **auto-import**. +1. On the project pane on the left, expand `src` => `main`. +1. Right-click on `scala` and select **New** => **Scala class**. +1. Call it `CubeCalculator`, change the **Kind** to `object`, and click **OK**. +1. Replace the code with the following: + +``` +object CubeCalculator extends App { + def cube(x: Int) = { + x * x * x + } +} +``` + +## Creating a test +1. On the project pane on the left, expand `src` => `test`. +1. Right-click on `scala` and select **New** => **Scala class**. +1. Name the class `CubeCalculatorTest` and click **OK**. +1. Replace the code with the following: + +``` +import org.scalatest.FunSuite + +class CubeCalculatorTest extends FunSuite { + test("CubeCalculator.cube") { + assert(CubeCalculator.cube(3) === 27) + } +} +``` + +1. In the source code, right-click `CubeCalculatorTest` and select **Run +'CubeCalculatorTest'**. + +## Understanding the code + +Let's go over this line by line. + +* `class CubeCalculatorTest` means we are testing the object `CubeCalculator` +* `extends FunSuite` lets us use functionality of ScalaTest's FunSuite class +such as the `test` function +* `test` is function that comes from the FunSuite library that collects +results from assertions within the function body. +* `"CubeCalculator.cube"` is a name for the test. You can call it anything but +one convention is "ClassName.methodName". +* `assert` takes a boolean condition and determines whether the test passes or fails. +* `CubeCalculator.cube(3) === 27` checks whether the output of the `cube` function is +indeed 27. The `===` is part of ScalaTest and provides clean error messages. + +## Adding another test case +1. Add another `assert` statement after the first one that checks for the cube +of `0`. +1. Re-run the test again by right-clicking `CubeCalculatorTest` and selecting +'Run **CubeCalculatorTest**'. + +## Conclusion +You've seen one way to test your Scala code. You can learn more about +ScalaTest's FunSuite on the [official website](http://www.scalatest.org/getting_started_with_fun_suite). diff --git a/getting-started-sbt-track/getting-started-with-scala-and-sbt-on-the-command-line.md b/getting-started-sbt-track/getting-started-with-scala-and-sbt-on-the-command-line.md new file mode 100644 index 0000000000..17b081d7e8 --- /dev/null +++ b/getting-started-sbt-track/getting-started-with-scala-and-sbt-on-the-command-line.md @@ -0,0 +1,88 @@ +--- +title: Getting Started with Scala and sbt on the Command Line +layout: singlepage-overview +disqus: true +next-page: testing-scala-with-sbt-on-the-command-line +--- + +In this tutorial, you'll see how to create a Scala project from +a template. You can use this as a starting point for your own +projects. We'll use [sbt](http://www.scala-sbt.org/0.13/docs/index.html), the de facto build tool for Scala. sbt compiles, +runs, and tests your projects among other related tasks. +We assume you know how to use a terminal. + +## Installation +1. Make sure you have the Java 8 JDK (also known as 1.8) + * Run `javac -version` in the command line and make sure you see + `javac 1.8.___` + * If you don't have version 1.8 or higher, [install the JDK](http://www.oracle.com/technetwork/java/javase/downloads/index.html) +1. Install sbt + * [Mac](http://www.scala-sbt.org/0.13/docs/Installing-sbt-on-Mac.html) + * [Windows](http://www.scala-sbt.org/0.13/docs/Installing-sbt-on-Windows.html) + * [Linux](http://www.scala-sbt.org/0.13/docs/Installing-sbt-on-Linux.html) + +## Create the project +1. `cd` to an empty folder. +1. Run the following command `sbt new scala/hello-world.g8`. +This pulls the 'hello-world' template from GitHub. +It will also create a `target` folder, which you can ignore. +1. When prompted, name the application `hello-world`. This will +create a project called "hello-world". +1. Let's take a look at what just got generated: + +``` +- hello-world + - project (sbt uses this to install manage plugins and dependencies) + - build.properties + - src + - main + - scala (All of your scala code goes here) + -Main.scala (Entry point of program) <-- this is all we need for now + build.sbt (sbt's build definition file) +``` + +After you build your project, sbt will create more `target` directories +for generated files. You can ignore these. + +## Running the project +1. `cd` into `hello-world`. +1. Run `sbt`. This will open up the sbt console. +1. Type `~run`. The `~` is optional and causes sbt to re-run on every file save, +allowing for a fast edit/run/debug cycle. sbt will also generate a `target` directory +which you can ignore. + +## Modifying the code +1. Open the file `src/main/scala/Main.scala` in your favorite text editor. +1. Change "Hello, World!" to "Hello, New York!" +1. If you haven't stopped the sbt command, you should see "Hello, New York!" +printed to the console. +1. You can continue to make changes and see the results in the console. + +## Adding a dependency +Changing gears a bit, let's look at how to use published libraries to add +extra functionality to our apps. + +1. Open up `build.sbt` and add the following line: + +``` +libraryDependencies += "org.scala-lang.modules" %% "scala-parser-combinators" % "1.0.5" +``` +Here, `libraryDependencies` is a set of dependencies, and by using `+=`, +we're adding the [scala-parser-combinators]({{site.baseurl}}/scala/scala-parser-combinators) dependency to the set of dependencies that sbt will go +and fetch when it starts up. Now, in any Scala file, you can import classes, +objects, etc, from scala-parser-combinators with a regular import. + +You can find more published libraries on +[Scaladex](https://index.scala-lang.org/), the Scala library index, where you +can also copy the above dependency information for pasting into your `build.sbt` +file. + +## Next steps + +Continue to the next tutorial in the _getting started with sbt_ series, and learn about [testing Scala code with sbt in the command line](testing-scala-with-sbt-on-the-command-line.html). + +**or** + +- Continue learning Scala interactively online on + [Scala Exercises](https://www.scala-exercises.org/scala_tutorial). +- Learn about Scala's features in bite-sized pieces by stepping through our [Tour of Scala]({{ site.baseurl }}/tour/tour-of-scala.html }). diff --git a/getting-started-sbt-track/testing-scala-with-sbt-on-the-command-line.md b/getting-started-sbt-track/testing-scala-with-sbt-on-the-command-line.md new file mode 100644 index 0000000000..16020bd601 --- /dev/null +++ b/getting-started-sbt-track/testing-scala-with-sbt-on-the-command-line.md @@ -0,0 +1,74 @@ +--- +title: Testing Scala with sbt and ScalaTest on the Command Line +layout: singlepage-overview +disqus: true +previous-page: getting-started-with-scala-and-sbt-on-the-command-line +--- + +There are multiple libraries and testing methodologies for Scala, +but in this tutorial, we'll demonstrate one popular option from the ScalaTest framework +called [FunSuite](http://www.scalatest.org/getting_started_with_fun_suite). +We assume you know [how to create a Scala project with sbt](getting-started-with-scala-and-sbt-on-the-command-line.html). + +## Setup +1. On the command line, create a new directory somewhere. +1. `cd` into the directory and run `sbt new scala/scalatest-example.g8` +1. Name the project `ScalaTestTutorial`. +1. The project comes with ScalaTest as a dependency in the `build.sbt` file. +1. `cd` into the directory and run `sbt test`. This will run the test suite +`CubeCalculatorTest` with a single test called `CubeCalculatorTest.cube`. + +``` +sbt test +[info] Loading global plugins from /Users/travislee/.sbt/0.13/plugins +[info] Loading project definition from /Users/travislee/workspace/sandbox/my-something-project/project +[info] Set current project to scalatest-example (in build file:/Users/travislee/workspace/sandbox/my-something-project/) +[info] CubeCalculatorTest: +[info] - CubeCalculator.cube +[info] Run completed in 267 milliseconds. +[info] Total number of tests run: 1 +[info] Suites: completed 1, aborted 0 +[info] Tests: succeeded 1, failed 0, canceled 0, ignored 0, pending 0 +[info] All tests passed. +[success] Total time: 1 s, completed Feb 2, 2017 7:37:31 PM +``` + +## Understanding tests +1. Open up two files in a text editor: + * `src/main/scala/CubeCalculator.scala` + * `src/test/scala/CubeCalculatorTest.scala` +1. In the file `CubeCalculator.scala`, you'll see how we define the function `cube`. +1. In the file `CubeCalculatorTest.scala`, you'll see that we have a class +named after the object we're testing. + +``` + import org.scalatest.FunSuite + + class CubeCalculatorTest extends FunSuite { + test("CubeCalculator.cube") { + assert(CubeCalculator.cube(3) === 27) + } + } +``` + +Let's go over this line by line. + +* `class CubeCalculatorTest` means we are testing the object `CubeCalculator` +* `extends FunSuite` lets us use functionality of ScalaTest's FunSuite class +such as the `test` function +* `test` is function that comes from FunSuite that collects +results from assertions within the function body. +* `"CubeCalculator.cube"` is a name for the test. You can call it anything but +one convention is "ClassName.methodName". +* `assert` takes a boolean condition and determines whether the test passes or fails. +* `CubeCalculator.cube(3) === 27` checks whether the output of the `cube` function is +indeed 27. The `===` is part of ScalaTest and provides clean error messages. + +## Adding another test case +1. Add another `assert` statement after the first one that checks for the cube +of `0`. +1. Execute `sbt test` again to see the results. + +## Conclusion +You've seen one way to test your Scala code. You can learn more about +ScalaTest's FunSuite on the [official website](http://www.scalatest.org/getting_started_with_fun_suite). You can also check out other testing frameworks such as [ScalaCheck](https://www.scalacheck.org/) and [Specs2](https://etorreborre.github.io/specs2/). diff --git a/getting-started.md b/getting-started.md new file mode 100644 index 0000000000..340f89d8c8 --- /dev/null +++ b/getting-started.md @@ -0,0 +1,61 @@ +--- +layout: singlepage-overview +title: Getting Started +includeTOC: true +--- + +
        There are two main ways people prefer to work in Scala.
        + +
          +
        1. Using an IDE.
        2. +
        3. Using the command line.
        4. +
        + + +The following tutorials will walk you through the set up process for either way +you prefer. + +However, if you just want to jump directly into Scala without installing anything, skip the guides on this page and check out: + + + +## Setting up and getting started with Scala + +### If prefer working in an IDE... + +IntelliJ is the most commonly-used IDE by Scala developers. In this tutorial, +we'll walk you through downloading and setting up IntelliJ with the Scala +plugin, and we'll get you started with your first Scala project, complete with +unit tests! + +* [Getting Started with Scala in IntelliJ](getting-started-intellij-track/getting-started-with-scala-in-intellij.html) +* [Building a Scala Project with IntelliJ and sbt](getting-started-intellij-track/building-a-scala-project-with-intellij-and-sbt.html) +* [Testing Scala in IntelliJ with ScalaTest](getting-started-intellij-track/testing-scala-in-intellij-with-scalatest.html) + + +### If you prefer working on the command line... + +If you prefer using a text editor like emacs, Vim, Atom, or Sublime Text, then +the best way to compile, test, and run Scala code is using _sbt_, Scala's build +tool. + +* [Getting Started with Scala and sbt on the Command Line](getting-started-sbt-track/getting-started-with-scala-and-sbt-on-the-command-line.html) +* [Testing Scala with sbt and ScalaTest on the Command Line](getting-started-sbt-track/testing-scala-with-sbt-on-the-command-line.html) + + + +## Next Steps +Once you've finished these tutorials, check out + +* [The Tour of Scala](http://docs.scala-lang.org/tutorials/tour/tour-of-scala.html) for bite-sized introductions to Scala's features. +* [Learning Resources](learn), which includes online interactive tutorials and courses. +* [Our list of some popular Scala books]({{ site.baseurl }}/books.html). + +## Getting Help +There are a multitude of mailing lists and real-time chat channels in case you want to quickly connect with other Scala users. Check out our [community](https://scala-lang.org/community/) page a list of these resources and where to reach out for help. diff --git a/glossary/index.md b/glossary/index.md index 3b5c1f68bb..6959c4048a 100644 --- a/glossary/index.md +++ b/glossary/index.md @@ -2,10 +2,16 @@ layout: glossary title: Glossary --- + +
        Glossary from the definitive book on Scala, Programming in Scala.
        +
        - Look up a term - - + + + +  
        * #### algebraic data type @@ -385,66 +391,3 @@ A type parameter of a class or trait can be marked with a _variance_ annotation, * #### yield An expression can _yield_ a result. The `yield` keyword designates the result of a [for comprehension](#for-comprehension). - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/guides.md b/guides.md new file mode 100644 index 0000000000..eef2743e4f --- /dev/null +++ b/guides.md @@ -0,0 +1,6 @@ +--- +layout: inner-page-no-masthead +title: Guides and Overviews +languages: [ja, zh-cn, es] + +--- diff --git a/index.md b/index.md index 13ba6fe844..219aba460e 100644 --- a/index.md +++ b/index.md @@ -1,4 +1,69 @@ --- -layout: frontpage -title: Scala Documentation +layout: inner-page-documentation +title: Documentation +#redirect_from: +# - /what-is-scala/ +#includeTOC: true + +# Content masthead links +sections: + + - title: "First Steps..." + links: + - title: "Getting Started" + description: "Install Scala on your computer and start writing some Scala code!" + icon: "fa fa-rocket" + link: getting-started.html + - title: "Tour of Scala" + description: "Bite-sized introductions to some of the core language concepts." + icon: "fa fa-flag" + link: /tour/tour-of-scala.html + - title: "Scala for Java Programmers" + description: "A quick introduction to Scala for those with a Java background." + icon: "fa fa-coffee" + link: /learn.html + more-resources: + - title: Online Courses, Exercises, & Blogs + url: /learn.html + - title: Books + url: /books.html + + - title: "Returning Users" + links: + - title: "API" + description: "API documentation for every version of Scala." + icon: "fa fa-file-text" + link: /api/all.html + - title: "Overviews" + description: "In-depth documentation covering many of Scala's features." + icon: "fa fa-database" + link: /reference.html + - title: "Style Guide" + description: "An in-depth guide on how to write idiomatic Scala code." + icon: "fa fa-bookmark" + link: /style/index.html + - title: "Cheatsheet" + description: "A handy cheatsheet covering the basics of Scala's syntax." + icon: "fa fa-list" + link: /cheatsheets/index.html + - title: "Scala FAQs" + description: "A list of frequently-asked questions about Scala language features and their answers." + icon: "fa fa-question-circle" + link: /reference.html + - title: "Language Spec" + description: "Scala's formal language specification." + icon: "fa fa-book" + link: http://scala-lang.org/files/archive/spec/2.12/ + + - title: "Scala Evolution" + links: + - title: "SIPs" + description: "The Scala Improvement Process. Language & compiler evolution." + icon: "fa fa-cogs" + link: sips/index.html + - title: "SPP" + description: "The Scala Platform Process. Community-driven library evolution." + icon: "fa fa-users" + link: https://scalacenter.github.io/platform-staging/platform.html + --- diff --git a/it/tutorials/scala-for-java-programmers.md b/it/tutorials/scala-for-java-programmers.md deleted file mode 100644 index b13512e1e7..0000000000 --- a/it/tutorials/scala-for-java-programmers.md +++ /dev/null @@ -1,716 +0,0 @@ ---- -layout: overview -title: Un'introduzione a Scala per programmatori Java -overview: scala-for-java-programmers - -discourse: false -multilingual-overview: true -language: it ---- - -Di Michel Schinz e Philipp Haller. -Traduzione italiana a cura di Mirco Veltri. - -## Introduzione - -Lo scopo di questo documento è quello di fornire una rapida -introduzione al linguaggio e al compilatore Scala. È rivolto a -chi ha già qualche esperienza di programmazione e desidera una -panoramica di cosa è possibile fare con Scala. Si assume una -conoscenza di base dei concetti di programmazione orientata -agli oggetti, specialmente in Java. - -## Un Primo Esempio - -Come primo esempio useremo lo standard *Hello world*. Non è sicuramente -un esempio entusiasmante ma rende facile dimostrare l'uso dei tool di -Scala senza richiedere troppe conoscenze del linguaggio stesso. -Ecco come appeare il codice: - - object HelloWorld { - def main(args: Array[String]) { - println("Hello, world!") - } - } - -La struttura di questo programma dovrebbe essere familiare ai -programmatori Java: c'è un metodo chiamato `main` che accetta argomenti, -un array di stringhe, forniti da riga di comando come -parametri; Il corpo del metodo consiste di una singola chiamata al -predefinito `println` che riceve il nostro amichevole saluto -come parametro. Il metodo `main` non ritorna alcun valore -(è un metodo procedura), pertanto non è necessario dichiararne uno -di ritorno. - -Ciò che è meno familiare ai programmatori Java è la -dichiarazione di `object` contenente il metodo `main`. -Tale dichiarazione introduce ciò che è comunemente chiamato -*oggetto singleton*, cioè una classe con una unica istanza. -La dichiarazione precedente infatti crea sia la classe `HelloWorld` -che una istanza di essa, chiamata `HelloWorld`. L'istanza è creata -su richiesta la prima volta che è usata. - - -Il lettore astuto avrà notato che il metodo `main` non è stato -dichiarato come `static`. Questo perchè i membri (metodi o campi) -statici non esistono in Scala. Invece che definire membri statici, -il programmatore Scala li dichiara in oggetti singleton. - -### Compiliamo l'esempio - -Per compilare l'esempio useremo `scalac`, il compilatore Scala. -`scalac` lavora come la maggior parte dei compilatori: prende un file -sorgente come argomento, eventuali opzioni e produce uno o più object -file come output. Gli object file sono gli standard file delle classi -di Java. - -Se salviamo il file precedente come `HelloWorld.scala` e lo compiliamo -con il seguente comando (il segno maggiore `>' rappresenta il prompt -dei comandi e non va digitato): - - > scalac HelloWorld.scala - -sarà generato qualche class file nella directory corrente. Uno di questi -avrà il nome `HelloWorld.class` e conterrà una classe che può essere -direttamente eseguita con il comando `scala`, come mostra la seguente -sezione. - -### Eseguimo l'esempio - -Una volta compilato il programma può esser facilmente eseguito con il -comando scala. L'uso è molto simile al comando java ed accetta le stesse -opzioni. Il precedente esempio può esser eseguito usando il seguente -comando. L'output prodotto è quello atteso: - - > scala -classpath . HelloWorld - - Hello, world! - -## Interazione con Java - -Uno dei punti di forza di Scala è quello di rendere semplice l’interazione con -codice Java. Tutte le classi del package `java.lang` sono importate di -default mentre le altre richiedono l’esplicito import. - -Osserviamo un esempio che lo dimostra. Vogliamo ottenere la data -corrente e formattarla in accordo con la convezione usata in uno -specifico paese del mondo, diciamo la Francia. (Altre regioni, come la parte -di lingua francese della Svizzera, utilizzano le stesse convenzioni.) - -Le librerie delle classi Java definiscono potenti classi di utilità come -`Date` e `DateFormat`. Poiché Scala interagisce direttamente con Java, non -esistono le classi equivalenti nella libreria delle classi di Scala--possiamo -semplicemente importare le classi dei corrispondenti package Java: - - import java.util.{Date, Locale} - import java.text.DateFormat - import java.text.DateFormat._ - - object FrenchDate { - def main(args: Array[String]) { - val now = new Date - val df = getDateInstance(LONG, Locale.FRANCE) - println(df format now) - } - } - -L’istruzione import di Scala è molto simile all’equivalente in Java -tuttavia, risulta essere più potente. Più classi possono essere importate -dallo stesso package includendole in parentesi graffe come nella prima riga -di codice precedentemente riportato. Un’altra differenza è evidente -nell’uso del carattere underscore (`_`) al posto dell’asterisco (`*`) per -importare tutti i nomi di un package o di una classe. Questo perché -l’asterisco è un identificatore valido (e.g. nome di un metodo), come -vedremo più avanti. - -Inoltre, l’istruzione import sulla terza riga importa tutti i membri -della classe `DateFormat`. Questo rende disponibili il metodo statico -`getDateInstance` ed il campo statico `LONG`. - -All’interno del metodo `main` creiamo un’istanza della classe `Date` di -Java che di default contiene la data corrente. Successivamente, definiamo il -formato della data usando il metodo statico `getDateInstance` importato -precedentemente. Infine, stampiamo la data corrente, formattata secondo la -localizzazione scelta, con l’istanza `DateFormat`; quest’ultima linea mostra -un’importante proprietà di Scala.I metodi che prendono un argomento possono -essere usati con una sintassi non fissa. Questa forma dell’espressione - - df format now - -è solo un altro modo meno esteso di scriverla come - - df.format(now) - -Apparentemente sembra un piccolo dettaglio sintattico ma, presenta delle -importanti conseguenze. Una di queste sarà esplorata nella prossima -sezione. - -A questo punto, riguardo l’integrazione con Java, abbiamo notato che è -altresì possibile ereditare dalle classi Java ed implementare le interfacce -direttamente in Scala. - - -## Tutto è un Oggetto - -Scala è un linguaggio orientato agli oggetti (_object-oriented_) puro nel -senso che *ogni cosa* è un oggetto, inclusi i numeri e le funzioni. In questo -differisce da Java che invece distingue tra tipi primitivi (come `boolean` - e `int` ) e tipi referenziati. Inoltre, Java non permette la manipolazione - di funzioni come fossero valori. - -### I numeri sono oggetti - -Poichè i numeri sono oggetti, hanno dei metodi. Di fatti -un’espressione aritmetica come la seguente: - - 1 + 2 * 3 / x - -consiste esclusivamente di chiamate a metodi e risulta equivalente alla -seguente espressione, come visto nella sezione precedente: - - (1).+(((2).*(3))./(x)) - -Questo significa anche che `+`, `*`, etc. sono identificatori validi in -in Scala. - -Le parentesi intorno ai numeri nella seconda versione sono necessarie -perché l’analizzatore lessicale di Scala usa le regole di match più -lunghe per i token quindi, dovrebbe dividere la seguente espressione: - - 1.+(2) - -nei token `1.`, `+`, and `2`. La ragione per cui si è scelto questo tipo -di assegnazione di significato è perché `1.` è un match più lungo e valido -di `1`. Il token `1.` è interpretato come `1.0` rendendolo un `Double` e -non più un `Int`. Scrivendo l’espressione come: - - (1).+(2) - -si evita che `1` sia interpretato come un `Double`. - -### Le funzioni sono oggetti - -Forse per i programmatori Java è più sorprendente scoprire che in Scala -anche le funzioni sono oggetti. È pertanto possibile passare le funzioni -come argomenti, memorizzarle in variabili e ritornarle da altre funzioni. -L’abilità di manipolare le funzioni come valori è uno dei punti -cardini di un interessante paradigma di programmazione chiamato -*programmazione funzionale*. - -Come esempio semplice del perché può risultare utile usare le funzioni -come valori consideriamo una funzione timer che deve eseguire delle -azione ogni secondo. Come specifichiamo l’azione da eseguire? -Logicamente come una funzione. Questo tipo di passaggio di funzione è -familiare a molti programmatori: viene spesso usato nel codice delle -interfacce utente per registrare le funzioni di call-back richiamate -quando un evento si verifica. - -Nel successivo programma la funzione timer è chiamata `oncePerSecond` e -prende come argomento una funzione di call-back. Il tipo di questa -funzione è scritto come `() => Unit` che è il tipo di tutte le funzioni -che non prendono nessun argomento e non restituiscono niente (il tipo - `Unit` è simile al `void` del C/C++). La funzione principale di questo -programma è quella di chiamare la funzione timer con una call-back che -stampa una frase sul terminale. In altre parole questo programma stampa la -frase “time flies like an arrow” ogni secondo. - - object Timer { - def oncePerSecond(callback: () => Unit) { - while (true) { callback(); Thread sleep 1000 } - } - def timeFlies() { - println("time flies like an arrow...") - } - def main(args: Array[String]) { - oncePerSecond(timeFlies) - } - } - -Notare che per stampare la stringa usiamo il metodo `println` predefinito -invece di quelli inclusi in `System.out`. - -#### Funzioni anonime - -Il codice precedente è semplice da capire e possiamo raffinarlo ancora -un po’. Notiamo preliminarmente che la funzione `timeFlies` è definita -solo per esser passata come argomento alla funzione `oncePerSecond`. -Nominare esplicitamente una funzione con queste caratteristiche non è -necessario. È più interessante costruire detta funzione nel momento in -cui viene passata come argomento a `oncePerSecond`. Questo è possibile -in Scala usando le *funzioni anonime*, funzioni cioè senza nome. La -versione rivista del nostro programma timer usa una funzione anonima -invece di *timeFlies* e appare come di seguito: - - object TimerAnonymous { - def oncePerSecond(callback: () => Unit) { - while (true) { callback(); Thread sleep 1000 } - } - def main(args: Array[String]) { - oncePerSecond(() => - println("time flies like an arrow...")) - } - } - -La presenza delle funzioni anonime in questo esempio è rivelata dal -simbolo `=>` che separa la lista degli argomenti della funzione dal -suo corpo. In questo esempio la lista degli argomenti è vuota e di fatti -la coppia di parentesi sulla sinistra della freccia è vuota. Il corpo della -funzione `timeFlies` è lo stesso del precedente. - -## Le Classi - -Come visto precedentemente Scala è un linguaggio orientato agli oggetti e -come tale presenta il concetto di classe. (Per ragioni di completezza -va notato che alcuni linguaggi orientati agli oggetti non hanno il concetto -di classe; Scala non è uno di questi.) Le classi in Scala sono dichiarate -usando una sintassi molto simile a quella usata in Java. Un'importante -differenza è che le classi in Scala possono avere dei parametri. Questo -concetto è mostrato nella seguente definizione dei numeri complessi. - - class Complex(real: Double, imaginary: Double) { - def re() = real - def im() = imaginary - } - -Questa classe per i numeri complessi prende due argomenti, la parte -immaginaria e quella reale del numero complesso. Questi possono esser -passati quando si crea una istanza della classe `Complex` nel seguente -modo: `new Complex(1.5, 2.3)`. La classe ha due metodi, `re` e `im` che -danno l’accesso rispettivamente alla parte reale e a quella immaginaria -del numero complesso. - -Da notare che il tipo di ritorno dei due metodi non è specificato esplicitamante. -Sarà il compilatore che lo dedurrà automaticamente osservando la parte a destra -del segno uguale dei metodi e deducendo che per entrambi si tratta di -valori di tipo `Double`. - -Il compilatore non è sempre capace di dedurre i tipi come nel caso precedente; -purtroppo non c’è una regola semplice capace di dirci quando sarà in grado di -farlo e quando no. Nella pratica questo non è un problema poiché il compilatore -sa quando non è in grado di stabilire il tipo che non è stato definito -esplicitamente. Come semplice regola i programmatori Scala alle prime armi -dovrebbero provare ad omettere la dichiarazione di tipi che sembrano semplici -da dedurre per osservare il comportamento del compilatore. Dopo qualche tempo si -avrà la sensazione di quando è possibile omettere il tipo e quando no. - -### Metodi senza argomenti - -Un piccolo problema dei metodi `re` e `im` è che, per essere invocati, è -necessario far seguire il nome del metodo da una coppia di parentesi tonde -vuote, come mostrato nel codice seguente: - - object ComplexNumbers { - def main(args: Array[String]) { - val c = new Complex(1.2, 3.4) - println("imaginary part: " + c.im()) - } - } - -Sarebbe decisamente meglio riuscire ad accedere alla parte reale ed immaginaria -come se fossero campi senza dover scrivere anche la coppia vuota di parentesi. -Questo è perfettamente fattibile in Scala semplicemente definendo i relativi -metodi *senza argomenti*. Tali metodi differiscono da quelli con zero argomenti -perché non presentano la coppia di parentesi dopo il nome nè nella loro -definizione, nè nel loro utilizzo. La nostra classe `Complex` può essere -riscritta come segue: - - class Complex(real: Double, imaginary: Double) { - def re = real - def im = imaginary - } - - -### Eredità e overriding - -In Scala tutte le classi sono figlie di una super-classe. Quando nessuna -super-classe viene specificata, come nell’esempio della classe `Complex`, -la classe `scala.AnyRef` è implicitamente usata. - -In Scala è possibile eseguire la sovrascrittura (_override_) dei metodi -ereditati dalla super-classe. È pertanto necessario specificare esplicitamente -il metodo che si sta sovrascrivendo usando il modificatore `override` per -evitare sovrascritture accidentali. Come esempio estendiamo la nostra classe - `Complex` ridefinendo il metodo `toString` ereditato da `Object`. - - class Complex(real: Double, imaginary: Double) { - def re = real - def im = imaginary - override def toString() = - "" + re + (if (im < 0) "" else "+") + im + "i" - } - - -## Classi Case e Pattern Matching - -Un tipo di struttura dati che spesso si trova nei programmi è l’albero. -Ad esempio, gli interpreti ed i compilatori abitualmente rappresentano i -programmi internamente come alberi. I documenti XML sono alberi e -diversi tipi di contenitori sono basati sugli alberi, come gli alberi -red-black. - -Esamineremo ora come gli alberi sono rappresentati e manipolati in Scala -attraverso un semplice programma calcolatrice. Lo scopo del programma è -manipolare espressioni aritmetiche molto semplici composte da somme, -costanti intere e variabili intere. Due esempi di tali espressioni sono -`1+2` e `(x+x)+(7+y)`. - -A questo punto è necessario definire il tipo di rappresentazione per -dette espressioni e, a tale proposito, l’albero è la più naturale, con -i nodi che rappresentano le operazioni (nel nostro caso, l’addizione) mentre -le foglie sono i valori (costanti o variabili). - -In Scala questo albero è abitualmente rappresentato usando una super-classe -astratta per gli alberi e una concreta sotto-classe per i nodi o le -foglie. In un linguaggio funzionale useremmo un tipo dati algebrico per -lo stesso scopo. Scala fornisce il concetto di _classi case_ (_case classes_) -che è qualcosa che si trova nel mezzo delle due rappresentazioni. -Mostriamo come può essere usato per definire il tipo di alberi per il nostro -esempio: - - abstract class Tree - case class Sum(l: Tree, r: Tree) extends Tree - case class Var(n: String) extends Tree - case class Const(v: Int) extends Tree - -Il fatto che le classi `Sum`, `Var` e `Const` sono dichiarate come classi case -significa che rispetto alle classi standard differiscono in diversi aspetti: - -- la parola chiave `new` non è necessaria per creare un’istanza di queste - classi (i.e si può scrivere `Const(5)` invece di `new Const(5)`), -- le funzioni getter sono automaticamente definite per i parametri del - costruttore (i.e. è possibile ricavare il valore del parametro `v` del - costruttore di qualche istanza della classe `c` semplicemente - scrivendo `c.v`), -- sono disponibili le definizioni di default dei metodi `equals` e - `hashCode` che lavorano sulle *strutture* delle istanze e non sulle - loro identità, -- è disponibile la definizione di default del metodo `toString` che stampa - il valore in “source form” (e.g. l’albero per l’espressione `x+1` stampa - `Sum(Var(x),Const(1))`), -- le istanze di queste classi possono essere decomposte con il - *pattern matching* come vedremo più avanti. - -Ora che abbiamo definito il tipo dati per rappresentare le nostre -espressioni aritmetiche possiamo iniziare a definire le operazioni per -manipolarle. Iniziamo con una funzione per valutare l’espressione in un -qualche *ambiente* (_environment_) di valutazione. Lo scopo dell’environment -è quello di dare i valori alle variabili. Per esempio, l’espressione -`x+1` valutata nell’environment con associato il valore `5` alla -variabile `x`, scritto `{ x -> 5 }`, restituisce `6` come risultato. - -Inoltre, dobbiamo trovare un modo per rappresentare gli environment. -Potremmo naturalmente usare alcune strutture dati associative come una -hash table ma, possiamo anche usare direttamente delle funzioni! Un -environment in realtà non è altro che una funzione con associato un -valore al nome di una variabile. L’environment `{ x -> 5 }` -mostrato sopra può essere semplicemente scritto in Scala come: - - { case "x" => 5 } - -Questa notazione definisce una funzione che quando riceve la stringa `"x"` -come argomento restituisce l’intero `5` e fallisce con un’eccezione negli -altri casi. - -Prima di scrivere la funzione di valutazione diamo un nome al tipo di -environment. Potremmo usare sempre il tipo `String => Int` per gli environment -ma semplifichiamo il programma se introduciamo un nome per questo tipo -rendendo anche i cambiamenti futuri più facili. Questo è fatto in con la -seguente notazione: - - type Environment = String => Int - -Da ora in avanti il tipo `Environment` può essere usato come un alias per -il tipo delle funzioni da `String` a `Int`. - -Possiamo ora passare alla definizione della funzione di valutazione. -Concettualmente è molto semplice: il valore della somma di due -espressioni è pari alla somma dei valori delle loro espressioni; il -valore di una variabile è ottenuto direttamente dall’environment; il -valore di una costante è la costante stessa. Esprimere quanto appena -detto in Scala non è difficile: - - def eval(t: Tree, env: Environment): Int = t match { - case Sum(l, r) => eval(l, env) + eval(r, env) - case Var(n) => env(n) - case Const(v) => v - } - -Questa funzione di valutazione lavora effettuando un *pattern matching* -sull’albero `t`. Intuitivamente il significato della definizione precendente -dovrebbe esser chiaro: - -1. prima controlla se l’albero `t` è un `Sum`; se lo è, esegue il bind del - sottoalbero sinistro con una nuova variabile chiamata `l` ed il sotto - albero destro con una variabile chiamata `r` e procede con la - valutazione dell’espressione che segue la freccia; questa - espressione può (e lo fa) utilizzare le variabili marcate dal pattern che - appaiono alla sinistra della freccia, i.e. `l` e `r`; -2. se il primo controllo non è andato a buon fine, cioè l’albero non è - un `Sum`, va avanti e controlla se `t` è un `Var`; se lo è, esegue il bind - del nome contenuto nel nodo `Var` con una variabile `n` e procede con la - valutazione dell’espressione sulla destra; -3. se anche il secondo controllo fallisce e quindi `t` non è nè `Sum` nè `Var`, - controlla se si tratta di un `Const` e se lo è, combina il valore contenuto - nel nodo `Const` con una variabile `v` e procede con la valutazione - dell’espressione sulla destra; -4. infine, se tutti i controlli falliscono, viene sollevata - un’eccezione per segnalare il fallimento del pattern matching - dell’espressione; questo caso può accadere qui solo se si - dichiarasse almeno una sotto classe di `Tree`. - -L’idea alla base del pattern matching è quella di eseguire il match di -un valore con una serie di pattern e, non appena il match è trovato, estrarre -e nominare varie parti del valore per valutare il codice che ne fa uso. - -Un programmatore object-oriented esperto potrebbe sorprendersi del fatto -che non abbiamo definito `eval` come *metodo* della classe e delle sue -sottoclassi. Potremmo averlo fatto perchè Scala permette la definizione di -metodi nelle case classes così come nelle classi normali. Decidere quando -usare il pattern matching o i metodi è quindi una questione di gusti ma, -ha anche implicazioni importanti riguardo l’estensibilità: - -- quando si usano i metodi è facile aggiungere un nuovo tipo di nodo - definendo una sotto classe di `Tree` per esso; d’altro canto, aggiungere - una nuova operazione per manipolare l’albero è noioso e richiede la - modifica di tutte le sotto classi `Tree`; -- quando si usa il pattern matching la situazione è ribaltata: - aggiungere un nuovo tipo di nodo richiede la modifica di tutte le - funzioni in cui si fa pattern matching sull’albero per prendere in - esame il nuovo nodo; d’altro canto, aggiungere una nuova operazione - è semplice, basta definirla come una funzione indipendente. - -Per esplorare ulteriormente il pattern matching definiamo un’altra -operazione sulle espressioni aritmetiche: la derivazione simbolica. È -necessario ricordare le seguenti regole che riguardano questa -operazione: - -1. la derivata di una somma è la somma delle derivate, -2. la derivata di una variabile `v` è uno se `v` è la variabile di - derivazione, zero altrimenti, -3. la derivata di una costante è zero. - -Queste regole possono essere tradotte quasi letteralmente in codice Scala e -ottenere la seguente definizione: - - def derive(t: Tree, v: String): Tree = t match { - case Sum(l, r) => Sum(derive(l, v), derive(r, v)) - case Var(n) if (v == n) => Const(1) - case _ => Const(0) - } - -Questa funzione introduce due nuovi concetti relativi al pattern -matching. Prima di tutto l’istruzione `case` per le variabili ha un -*controllo*, un’espressione che segue la parola chiave `if`. Questo -controllo fa si che il pattern matching è eseguito solo se l’espressione -è vera. Qui viene usato per esser sicuri che restituiamo la costante `1` -solo se il nome della variabile da derivare è lo stesso della variabile -di derivazione `v`. La seconda nuova caratteristica del pattern matching qui -introdotta è la *wild-card*, scritta `_`, che corrisponde a qualunque -valore, senza assegnargli un nome. - -Non abbiamo esplorato del tutto la potenza del pattern matching ma ci -fermiamo qui per brevità. Vogliamo ancora osservare come le due -precedenti funzioni lavorano in un esempio reale. A tale scopo -scriviamo una semplice funzione `main` che esegue diverse operazioni -sull’espressione `(x+x)+(7+y)`: prima calcola il suo valore -nell’environment `{ x -> 5, y -> 7 }`, dopo calcola la -derivata relativa ad `x` e poi ad `y`. - - def main(args: Array[String]) { - val exp: Tree = Sum(Sum(Var("x"),Var("x")),Sum(Const(7),Var("y"))) - val env: Environment = { case "x" => 5 case "y" => 7 } - println("Expression: " + exp) - println("Evaluation with x=5, y=7: " + eval(exp, env)) - println("Derivative relative to x:\n " + derive(exp, "x")) - println("Derivative relative to y:\n " + derive(exp, "y")) - } - -Eseguendo questo programma otteniamo l’output atteso: - - Expression: Sum(Sum(Var(x),Var(x)),Sum(Const(7),Var(y))) - Evaluation with x=5, y=7: 24 - Derivative relative a x: - Sum(Sum(Const(1),Const(1)),Sum(Const(0),Const(0))) - Derivative relative to y: - Sum(Sum(Const(0),Const(0)),Sum(Const(0),Const(1))) - -Esaminando l’output notiamo che il risultato della derivata dovrebbe -essere semplificato prima di essere visualizzato all’utente. La -definizione di una funzione di semplificazione usando il pattern -matching rappresenta un interessante (ma sorprendentemente -ingannevole) problema che lasciamo come esercizio per il lettore. - - -## I Trait - -Una classe in Scala oltre che poter ereditare da una super-classe può anche -importare del codice da uno o più *trait*. - -Probabilmente per i programmatori Java il modo più semplice per capire cosa -sono i trait è concepirli come interfacce che possono contenere del codice. -In Scala quando una classe eredita da un trait ne implementa la relativa -interfaccia ed eredita tutto il codice contenuto in essa. - -Per comprendere a pieno l’utilità dei trait osserviamo un classico -esempio: gli oggetti ordinati. Si rivela spesso utile riuscire a confrontare -oggetti di una data classe con se stessi, ad esempio per ordinarli. In Java -gli oggetti confrontabili implementano l’interfaccia `Comparable`. In Scala -possiamo fare qualcosa di meglio che in Java definendo l’equivalente codice -di `Comparable` come un trait, che chiamiamo `Ord`. - -Sei differenti predicati possono essere utili per confrontare gli -oggetti: minore, minore o uguale, uguale, diverso, maggiore e maggiore o uguale. - Tuttavia definirli tutti è noioso, specialmente perché 4 di -essi sono esprimibili con gli altri due. Per esempio, dati i predicati di -uguale e minore, è possibile esprimere gli altri. In Scala tutte queste -osservazioni possono essere piacevolemente inclusi nella seguente -dichiarazione di un trait: - - trait Ord { - def < (that: Any): Boolean - def <=(that: Any): Boolean = (this < that) || (this == that) - def > (that: Any): Boolean = !(this <= that) - def >=(that: Any): Boolean = !(this < that) - } - -Questa definizione crea un nuovo tipo chiamato `Ord` che ha lo stesso -ruolo dell’interfaccia `Comparable` in Java e, fornisce l’implementazione -di default di tre predicati in termini del quarto astraendone uno. -I predicati di uguaglianza e disuguaglianza non sono presenti in questa -dichiarazione poichè sono presenti di default in tutti gli oggetti. - -Il tipo `Any` usato precedentemente è il super-tipo dati di tutti gli -altri tipi in Scala. Può esser visto come una versione generica del -tipo `Object` in Java dato che è altresì il super-tipo dei tipi base come -`Int`, `Float` ecc. - -Per rendere confrontabili gli oggetti di una classe è quindi sufficiente -definire i predicati con cui testare uguaglianza ed minoranza e unire la -precedente classe `Ord`. Come esempio definiamo una classe `Date` che -rappresenta le date nel calendario Gregoriano. Tali date sono composte dal -giorno, dal mese e dall’anno che rappresenteremo tutti con interi. Iniziamo -definendo la classe `Date` come segue: - - class Date(y: Int, m: Int, d: Int) extends Ord { - def year = y - def month = m - def day = d - override def toString(): String = year + "-" + month + "-" + day - -La parte importante qui è la dichiarazione `extends Ord` che segue il nome -della classe e dei parametri. Dichiara che la classe `Date` eredita il -codice dal trait `extends Ord`. - -Successivamente ridefiniamo il metodo `equals`, ereditato da `Object`, -in modo tale che possa confrontare in modo corretto le date confrontando -i singoli campi. L’implementazione di default del metodo `equals` non è -utilizzabile perché, come in Java, confronta fisicamente gli oggetti. -Arriviamo alla seguente definizione: - - override def equals(that: Any): Boolean = - that.isInstanceOf[Date] && { - val o = that.asInstanceOf[Date] - o.day == day && o.month == month && o.year == year - } - -Questo metodo fa uso di due metodi predefiniti `isInstanceOf` e `asInstanceOf`. -Il primo, `isInstanceOf`, corrisponde all’operatore `instanceOf` di Java e -restituisce true se e solo se l’oggetto su cui è applicato è una istanza del -tipo dati. Il secondo, `asInstanceOf`, corrisponde all’operatore di cast in -Java: se l’oggetto è una istanza del tipo dati è visto come tale altrimenti -viene sollevata un’eccezione `ClassCastException`. - -L’ultimo metodo da definire è il predicato che testa la condizione di -minoranza. Fa uso di un altro metodo predefinito, `error`, che solleva -un'eccezione con il messaggio di errore specificato. - - def <(that: Any): Boolean = { - if (!that.isInstanceOf[Date]) - error("cannot compare " + that + " and a Date") - - val o = that.asInstanceOf[Date] - (year < o.year) || - (year == o.year && (month < o.month || - (month == o.month && day < o.day))) - } - -Questo completa la definizione della classe `Date`. Istanze di questa classe -possono esser viste sia come date che come oggetti confrontabili. -Inoltre, tutti e sei i predicati di confronto menzionati precedentemente -sono definiti: `equals` e `<` perché appaiono direttamente nella definizione -della classe `Date` e gli altri perché sono ereditati dal trait `Ord`. - -I trait naturalmente sono utili in molte situazioni più interessanti di quella -qui mostrata, ma la discussione delle loro applicazioni è fuori dallo scopo di -questo documento. - -## Programmazione Generica - -L’ultima caratteristica di Scala che esploreremo in questo tutorial è la -programmazione generica. Gli sviluppatori Java dovrebbero essere bene -informati dei problemi relativi alla mancanza della programmazione -generica nel loro linguaggio, un’imperfezione risolta in Java 1.5. - -La programmazione generica riguarda la capacità di scrivere codice -parametrizzato dai tipi. Per esempio un programmatore che scrive una -libreria per le liste concatenate può incontrare il problema di decidere -quale tipo dare agli elementi della lista. Dato che questa lista è stata -concepita per essere usata in contesti differenti, non è possibile -decidere che il tipo degli elementi deve essere, per esempio, `Int`. -Questo potrebbe essere completamente arbitrario ed eccessivamente -restrittivo. - -I programmatori Java hanno fatto ricorso all’uso di `Object`, che è il -super-tipo di tutti gli oggetti. Questa soluzione è in ogni caso ben lontana -dall’esser ideale perché non funziona per i tipi base (`int`, `long`, `float`, -ecc.) ed implica che molto type casts dinamico deve esser fatto dal -programmatore. - -Scala rende possibile la definizione delle classi generiche (e metodi) per -risolvere tale problema. Esaminiamo ciò con un esempio del più semplice -container di classe possibile: un riferimento, che può essere o vuoto o -un puntamento ad un oggetto di qualche tipo. - - class Reference[T] { - private var contents: T = _ - def set(value: T) { contents = value } - def get: T = contents - } - -La classe `Reference` è parametrizzata da un tipo, chiamato `T`, che è il tipo -del suo elemento. Questo tipo è usato nel corpo della classe come il tipo della -variabile `contents`, l’argomento del metodo , ed il tipo restituito dal metodo -`get`. - -Il precedente codice d’esempio introduce le variabili in Scala che non -dovrebbero richiedere ulteriori spiegazioni. È tuttavia interessante -notare che il valore iniziale dato a quella variabile è `_`, che -rappresenta un valore di default. Questo valore di default è 0 per i -tipi numerici, `false` per il tipo `Boolean`, `())`per il tipo `Unit` -e `null` per tutti i tipi oggetto. - -Per usare la classe `Reference` è necessario specificare quale tipo usare per -il tipo parametro `T`, il tipo di elemento contenuto dalla cella. Ad esempio, -per creare ed usare una cella che contiene un intero si potrebbe scrivere il -seguente codice: - - object IntegerReference { - def main(args: Array[String]) { - val cell = new Reference[Int] - cell.set(13) - println("Reference contains the half of " + (cell.get * 2)) - } - } - -Come si può vedere in questo esempio non è necessario il cast del -tipo ritornato dal metodo `get` prima di usarlo come intero. Non risulta -possibile memorizzare niente di diverso da un intero nella varibile -poiché è stata dichiarata per memorizzare un intero. - -## Conclusioni - -Questo documento ha fornito una veloce introduzione del linguaggio Scala e -presentato alcuni esempi di base. Il lettore interessato può continuare, per -esempio, leggendo il documento *Scala By Example* che contiene esempi molti più -avanzati e consultare al bisogno la documentazione -*Scala Language Specification*. diff --git a/ja/cheatsheets/index.md b/ja/cheatsheets/index.md deleted file mode 100644 index 605ea191b1..0000000000 --- a/ja/cheatsheets/index.md +++ /dev/null @@ -1,86 +0,0 @@ ---- -layout: cheatsheet -istranslation: true -title: Scalacheat -by: Kenji Ohtsuka -about: Thanks to Brendan O'Connor. このチートシートは Scala 構文 のクイックリファレンスとして作成されました。 Licensed by Brendan O'Connor under a CC-BY-SA 3.0 license. -language: ja ---- - -###### Contributed by {{ page.by }} - -| | | -| ------ | ------ | -| 変数 | | -| `var x = 5` | 変数 | -| Good `val x = 5`
        Bad `x=6` | 定数 | -| `var x: Double = 5` | 明示的な型 | -| 関数 | | -| Good `def f(x: Int) = { x*x }`
        Bad `def f(x: Int) { x*x }` | 関数定義
        落とし穴: = を書かないと Unit を返す手続きになり、大惨事の原因になります。 | -| Good `def f(x: Any) = println(x)`
        Bad `def f(x) = println(x)` | 関数定義
        シンタックスエラー: すべての引数に型指定が必要です。 | -| `type R = Double` | 型エイリアス | -| `def f(x: R)` vs.
        `def f(x: => R)` | 値渡し
        名前渡し (遅延評価パラメータ) | -| `(x:R) => x*x` | 無名関数 | -| `(1 to 5).map(_*2)` vs.
        `(1 to 5).reduceLeft( _+_ )` | 無名関数: アンダースコアは位置に応じて引数が代入されます。 | -| `(1 to 5).map( x => x*x )` | 無名関数: 引数を2回使用する場合は名前をつけます。 | -| Good `(1 to 5).map(2*)`
        Bad `(1 to 5).map(*2)` | 無名関数: 片側が束縛された中置演算。 わかりづらいので `2*_` と書くことを推奨します。 | -| `(1 to 5).map { val x=_*2; println(x); x }` | 無名関数: ブロックスタイルでは最後の式の結果が戻り値になります。 | -| `(1 to 5) filter {_%2 == 0} map {_*2}` | 無名関数: パイプラインスタイル (括弧でも同様) 。 | -| `def compose(g:R=>R, h:R=>R) = (x:R) => g(h(x))`
        `val f = compose({_*2}, {_-1})` | 無名関数: 複数のブロックを渡す場合は外側の括弧が必要です。 | -| `val zscore = (mean:R, sd:R) => (x:R) => (x-mean)/sd` | カリー化の明示的記法 | -| `def zscore(mean:R, sd:R) = (x:R) => (x-mean)/sd` | カリー化の明示的記法 | -| `def zscore(mean:R, sd:R)(x:R) = (x-mean)/sd` | カリー化の糖衣構文、ただしこの場合、 | -| `val normer = zscore(7, 0.4)_` | 部分関数を取得するには末尾にアンダースコアが必要です。 | -| `def mapmake[T](g:T=>T)(seq: List[T]) = seq.map(g)` | ジェネリック型 | -| `5.+(3); 5 + 3`
        `(1 to 5) map (_*2)` | 中置記法 | -| `def sum(args: Int*) = args.reduceLeft(_+_)` | 可変長引数 | -| パッケージ | | -| `import scala.collection._` | ワイルドカードでインポートします。 | -| `import scala.collection.Vector`
        `import scala.collection.{Vector, Sequence}` | 個別にインポートします。 | -| `import scala.collection.{Vector => Vec28}` | 別名でインポートします。 | -| `import java.util.{Date => _, _}` | Date を除いて java.util のすべてをインポートします。 | -| _(ファイル先頭の)_ `package pkg`
        `package pkg { ... }` | パッケージ宣言 | -| データ構造 | | -| `(1,2,3)` | タプルリテラル (`Tuple3`) | -| `var (x,y,z) = (1,2,3)` | 構造化代入: パターンマッチによるタプルの展開。 | -| Bad`var x,y,z = (1,2,3)` | 隠れたエラー: 各変数にタプル全体が代入されます。 | -| `var xs = List(1,2,3)` | リスト (イミュータブル) | -| `xs(2)` | 括弧を使って添字を書きます。 ([slides](http://www.slideshare.net/Odersky/fosdem-2009-1013261/27)) | -| `1 :: List(2,3)` | 先頭に要素を追加 | -| `1 to 5` _(_ `1 until 6`
        `1 to 10 by 2` _と同じ)_ | Range の糖衣構文 | -| `()` _(中身のない括弧)_ | Unit 型 の唯一の値(C/Java でいう void) 。 | -| 制御構文 | | -| `if (check) happy else sad` | 条件分岐 | -| `if (check) happy`
        _(_ `if (check) happy else ()` _と同じ)_ | 条件分岐の省略形 | -| `while (x < 5) { println(x); x += 1}` | while ループ | -| `do { println(x); x += 1} while (x < 5)` | do while ループ | -| `import scala.util.control.Breaks._`
        `breakable {`
        ` for (x <- xs) {`
        ` if (Math.random < 0.1) break`
        ` }`
        `}`| break ([slides](http://www.slideshare.net/Odersky/fosdem-2009-1013261/21)) | -| `for (x <- xs if x%2 == 0) yield x*10`
        _(_ `xs.filter(_%2 == 0).map(_*10)` _と同じ)_ | for 内包表記: filter/map | -| `for ((x,y) <- xs zip ys) yield x*y`
        _(_ `(xs zip ys) map { case (x,y) => x*y }` _と同じ)_ | for 内包表記: 構造化代入 | -| `for (x <- xs; y <- ys) yield x*y`
        _(_ `xs flatMap {x => ys map {y => x*y}}` _と同じ)_ | for 内包表記: 直積 | -| `for (x <- xs; y <- ys) {`
        `println("%d/%d = %.1f".format(x, y, x/y.toFloat))`
        `}` | for 内包表記: 命令型の記述
        [sprintf-style](http://java.sun.com/javase/6/docs/api/java/util/Formatter.html#syntax) | -| `for (i <- 1 to 5) {`
        `println(i)`
        `}` | for 内包表記: 上限を含んだ走査 | -| `for (i <- 1 until 5) {`
        `println(i)`
        `}` | for 内包表記: 上限を除いた走査 | -| パターンマッチング | | -| Good `(xs zip ys) map { case (x,y) => x*y }`
        Bad `(xs zip ys) map( (x,y) => x*y )` | case をパターンマッチのために関数の引数で使っています。 | -| Bad
        `val v42 = 42`
        `Some(3) match {`
        ` case Some(v42) => println("42")`
        ` case _ => println("Not 42")`
        `}` | "v42" は任意の Int の値とマッチする変数名として解釈され、 "42" が表示されます。 | -| Good
        `val v42 = 42`
        `Some(3) match {`
        `` case Some(`v42`) => println("42")``
        `case _ => println("Not 42")`
        `}` | バッククオートで囲んだ "\`v42\`" は既に存在する `v42` として解釈され、 "Not 42" が表示されます。 | -| Good
        `val UppercaseVal = 42`
        `Some(3) match {`
        ` case Some(UppercaseVal) => println("42")`
        ` case _ => println("Not 42")`
        `}` | 大文字から始まる `UppercaseVal` は既に存在する定数として解釈され、新しい変数としては扱われません。 これにより `UppercaseVal` は `3` とは異なる値と判断され、 "Not 42" が表示されます。 | -| オブジェクト指向 | | -| `class C(x: R)`
        _(_ `class C(private val x: R)`
        `var c = new C(4)` _と同じ)_ | コンストラクタの引数 - private | -| `class C(val x: R)`
        `var c = new C(4)`
        `c.x` | コンストラクタの引数 - public | -| `class C(var x: R) {`
        `assert(x > 0, "positive please")`
        `var y = x`
        `val readonly = 5`
        `private var secret = 1`
        `def this = this(42)`
        `}`|
        コンストラクタはクラスの body 部分 です。
        public メンバ の宣言
        読取可能・書込不可なメンバの宣言
        private メンバ の宣言
        代替コンストラクタ | -| `new{ ... }` | 無名クラス | -| `abstract class D { ... }` | 抽象クラスの定義 (生成不可) | -| `class C extends D { ... }` | 継承クラスの定義 | -| `class D(var x: R)`
        `class C(x: R) extends D(x)` | 継承とコンストラクタのパラメータ (要望: 自動的にパラメータを引き継げるようになってほしい) -| `object O extends D { ... }` | シングルトンオブジェクトの定義 (モジュールに似ている) | -| `trait T { ... }`
        `class C extends T { ... }`
        `class C extends D with T { ... }` | トレイト
        実装を持ったインターフェースで、コンストラクタのパラメータを持つことができません。 [mixin-able]({{ site.baseurl }}/tutorials/tour/mixin-class-composition.html). -| `trait T1; trait T2`
        `class C extends T1 with T2`
        `class C extends D with T1 with T2` | 複数のトレイトを組み合わせられます。 | -| `class C extends D { override def f = ...}` | メソッドの override は明示する必要があります。 | -| `new java.io.File("f")` | オブジェクトの生成 | -| Bad `new List[Int]`
        Good `List(1,2,3)` | 型のエラー: 抽象型のオブジェクトは生成できません。
        代わりに、習慣として、型を隠蔽するファクトリを使います。 | -| `classOf[String]` | クラスの情報取得 | -| `x.isInstanceOf[String]` | 型のチェック (実行時) | -| `x.asInstanceOf[String]` | 型のキャスト (実行時) | -| `x: String` | 型帰属 (コンパイル時) | diff --git a/ja/overviews/collections/arrays.md b/ja/overviews/collections/arrays.md deleted file mode 100644 index 2975a23f06..0000000000 --- a/ja/overviews/collections/arrays.md +++ /dev/null @@ -1,122 +0,0 @@ ---- -layout: overview-large -title: 配列 - -discourse: false - -partof: collections -num: 10 -language: ja ---- - -配列 ([`Array`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/Array.html)) は Scala のコレクションの中でも特殊なものだ。Scala 配列は、Java の配列と一対一で対応する。どういう事かと言うと、Scala の配列 `Array[Int]` は Java の `int[]` で実装されており、`Array[Double]` は Java の `double[]`、`Array[String]` は Java の `String[]` で実装されている。その一方で、Scala の配列は Java のそれに比べて多くの機能を提供する。まず、Scala の配列は**ジェネリック**であることができる。 つまり、型パラメータか抽象型の `T` に対する `Array[T]` を定義することができる。次に、Scala の配列は Scala の列と互換性があり、`Seq[T]` が期待されている所に `Array[T]` を渡すことができる。さらに、Scala の配列は列の演算の全てをサポートする。以下に具体例で説明する: - - scala> val a1 = Array(1, 2, 3) - a1: Array[Int] = Array(1, 2, 3) - scala> val a2 = a1 map (_ * 3) - a2: Array[Int] = Array(3, 6, 9) - scala> val a3 = a2 filter (_ % 2 != 0) - a3: Array[Int] = Array(3, 9) - scala> a3.reverse - res0: Array[Int] = Array(9, 3) - -Scala の配列は Java の配列で実装されているのに、どのようにして新たな機能をサポートしてるのだろうか。実は、Scala 2.8 とその前のバージョンではその問に対する答が変わってくる。以前は Scala のコンパイラが、ボックス化 (boxing) とボックス化解除 (unboxing) と呼ばれる「魔法」により配列と `Seq` オブジェクトの間を変換していた。この詳細は、特にジェネリック型の `Array[T]` が作成された場合、非常に複雑なものとなる。不可解な特殊ケースなどもあり、配列演算の性能特性は予想不可能なものとなった。 - -Scala 2.8 の設計はより単純なものだ。ほぼ全てのコンパイラの魔法は無くなった。代わりに、Scala 2.8 配列の実装は全体的に暗黙の変換 (implicit conversion) を利用する。Scala 2.8 では、配列はあたかも列であるようなふりをしない。 そもそもネイティブな配列のデータ型の実装は `Seq` の子クラスではないため、それは不可能というものだ。代わりに、配列を `Seq` の子クラスである `scala.collection.mutable.WrappedArray` クラスで「ラッピング」する暗黙の変換が行われる。以下に具体例で説明する: - - scala> val seq: Seq[Int] = a1 - seq: Seq[Int] = WrappedArray(1, 2, 3) - scala> val a4: Array[Int] = s.toArray - a4: Array[Int] = Array(1, 2, 3) - scala> a1 eq a4 - res1: Boolean = true - -上記のやりとりは、配列から `WrappedArray` への暗黙の変換があるため、配列と列に互換性があることを示す。`WrappedArray` から `Array` へ逆の方向に変換するには、`Traversable` に定義されている `toArray` メソッドを使うことで実現できる。上記の REPL の最後の行は、ラッピングした後、`toArray` でそれを解除したときに、同一の配列が得られることを示す。 - -配列に適用されるもう一つの暗黙の変換がある。この変換は単に列メソッドの全てを配列に「追加」するだけで、配列自身を列には変換しない。この「追加」は、配列が全ての列メソッドをサポートする `ArrayOps` 型のオブジェクトにラッピングされることを意味している。典型的には、この `ArrayOps` は短命で、列メソッドを呼び出し終えた後にはアクセス不可能となり、そのメモリ領域はリサイクルされる。現代的な仮想機械 (VM) は、しばしばこのようなオブジェクトの生成そのものを省略できる。 - -以下の REPL のやりとりで、二つの暗黙の変換の違いを示す: - - scala> val seq: Seq[Int] = a1 - seq: Seq[Int] = WrappedArray(1, 2, 3) - scala> seq.reverse - res2: Seq[Int] = WrappedArray(3, 2, 1) - scala> val ops: collection.mutable.ArrayOps[Int] = a1 - ops: scala.collection.mutable.ArrayOps[Int] = [I(1, 2, 3) - scala> ops.reverse - res3: Array[Int] = Array(3, 2, 1) - -実際には `WrappedArray` である `seq` に対して `reverse` を呼ぶと再び `WrappedArray` が返っているのが分かる。`WrappedArray` は `Seq` であり、`Seq` に対して `reverse` を呼ぶと再び `Seq` が返るため、この結果は論理的だ。一方、`ArrayOps` クラスの値 `ops` に対して `reverse` を呼ぶと、`Seq` ではなく、`Array` が返る。 - -上記の `ArrayOps` の例は。`WrappedArray` との違いを示すためだけのかなり恣意的な物で、通常は `ArrayOps` クラスの値を定義することはありえない。単に配列に対して `Seq` メソッドを呼び出すだけでいい: - - scala> a1.reverse - res4: Array[Int] = Array(3, 2, 1) - -`ArrayOps` オブジェクトは暗黙の変換により自動的に導入されるからだ。よって、上の一行は以下に等しく、 - - scala> intArrayOps(a1).reverse - res5: Array[Int] = Array(3, 2, 1) - -`intArrayOps` が先の例で暗黙の変換により導入されたものだ。ここで問題となるのが、先の例でコンパイラがどうやってもう一つの暗黙の変換である `WrappedArray` に対して `intArrayOps` を優先させたのかということだ。結局の所、両方の変換も配列をインプットが指定した `reverse` メソッドをサポートする型へ変換するものだ。二つの暗黙の変換には優先順序が付けられているというのがこの問への答だ。`ArrayOps` 変換には、`WrappedArray` 変換よりも高い優先順位が与えられている。`ArrayOps` 変換は `Predef` オブジェクトで定義されているのに対し、`WrappedArray` 変換は -`Predef` が継承する `scala.LowPriorityImplicits` で定義されている。子クラスや子オブジェクトで定義される暗黙の変換は、親クラスで定義される暗黙の変換に対して優先される。よって、両方の変換が適用可能な場合は、`Predef` -で定義されるものが選ばれる。文字列まわりにも似た仕組みがある。 - -これで、配列において列との互換性および全ての列メソッドのサポートを実現しているかが分かったと思う。ジェネリック性についてはどうだろう。Java では型パラメータ `T` に対して `T[]` と書くことはできない。では、Scala の `Array[T]` はどのように実装されるのだろう。`Array[T]` のようなジェネリックな配列は実行時においては、Java の 8つあるプリミティブ型の -`byte[]`、`short[]`、`char[]`、`int[]`、`long[]`、`float[]`、`double[]`、`boolean[]` のどれか、もしくはオブジェクトの配列である可能性がある。 これらの型に共通の実行時の型は `AnyRef` (もしくは、それと等価な `java.lang.Object`) であるので、Scala のコンパイラは `Array[T]` を `AnyRef` にマップする。実行時に、型が `Array[T]` である配列の要素が読み込まれたり、更新された場合、実際の配列型を決定するための型判定手順があり、その後 Java 配列に対して正しい型演算が実行される。この型判定は配列演算を多少遅くする。ジェネリックな配列へのアクセスはプリミティブ型やオブジェクトの配列に比べて 3〜4倍遅いと思っていい。つまり、最高の性能を必要とするなら、ジェネリック配列ではなく具象配列を使ったほうがいいことを意味する。いくらジェネリック配列を実装しても、それを**作成する**方法がなければ意味が無い。これは更に難しい問題で、あなたにも少し手を借りる必要がある。この問題を説明するのに、配列を作成するジェネリックなメソッドの失敗例を見てほしい。 - - // これは間違っている! - def evenElems[T](xs: Vector[T]): Array[T] = { - val arr = new Array[T]((xs.length + 1) / 2) - for (i <- 0 until xs.length by 2) - arr(i / 2) = xs(i) - arr - } - -`evenElems` メソッドは、引数のベクトル `xs`内の偶数位置にある全ての要素から成る新しい配列を返す。`evenElems` の本文一行目にて、引数と同じ型を持つ戻り値の配列が定義されている。そのため、実際の型パラメータ `T` の実際の型により、これは `Array[Int]`、`Array[Boolean]`、もしくはその他の Java のプリミティブ型の配列か、参照型の配列であるかもしれない。これらの型は実行時に異なる実装を持つため、Scala ランタイムはどのようにして正しいものを選択するのだろう。実際のところ、型パラメータ `T` に対応する実際の型は実行時に消去されてしまうため、与えられた情報だけでは選択することができない。そのため、上記のコードをコンパイルしようとすると以下のエラーが発生する: - - error: cannot find class manifest for element type T - val arr = new Array[T]((arr.length + 1) / 2) - ^ - -あなたがコンパイラを手伝ってあげて、`evenElems` の型パタメータの実際の型が何であるかの実行時のヒントを提供することが必要とされている。この実行時のヒントは `scala.reflect.ClassManifest` -型の**クラスマニフェスト**という形をとる。クラスマニフェストとは、型の最上位クラスが何であるかを記述する型記述オブジェクトだ。型に関するあらゆる事を記述する `scala.reflect.Manifest` 型の完全マニフェストというものもある。配列の作成にはクラスマニフェストで十分だ。 - -Scala コンパイラは、指示を出すだけでクラスマニフェストを自動的に構築する。「指示を出す」とは、クラスマニフェストを以下のように暗黙のパラメータ (implicit parameter) として要求することを意味する: - - def evenElems[T](xs: Vector[T])(implicit m: ClassManifest[T]): Array[T] = ... - -**context bound** という、より短い別の構文を使うことで型がクラスマニフェストを連れてくることを要求できる。これは、型の後にコロン (:) とクラス名 `ClassManifest` を付けることを意味する: - - // これは動作する - def evenElems[T: ClassManifest](xs: Vector[T]): Array[T] = { - val arr = new Array[T]((xs.length + 1) / 2) - for (i <- 0 until xs.length by 2) - arr(i / 2) = xs(i) - arr - } - -2つの `evenElems` の改訂版は全く同じことを意味する。どちらの場合も、`Array[T]` が構築されるときにコンパイラは型パラメータ `T` のクラスマニフェスト、つまり `ClassManifest[T]` 型の暗黙の値 (implicit value)、を検索する。暗黙の値が見つかれば、正しい種類の配列を構築するのにマニフェストが使用される。見つからなければ、その前の例のようにエラーが発生する。 - -以下に `evenElems` を使った REPL のやりとりを示す。 - - scala> evenElems(Vector(1, 2, 3, 4, 5)) - res6: Array[Int] = Array(1, 3, 5) - scala> evenElems(Vector("this", "is", "a", "test", "run")) - res7: Array[java.lang.String] = Array(this, a, run) - -両者の場合とも、Scala コンパイラは要素型 (`Int`、そして `String`) のクラスマニフェストを自動的に構築して、`evenElems` メソッドの暗黙のパラメータに渡した。コンパイラは全ての具象型についてクラスマニフェストを構築できるが、引数そのものがクラスマニフェストを持たない型パラメータである場合はそれができない。以下に失敗例を示す: - - scala> def wrap[U](xs: Vector[U]) = evenElems(xs) - :6: error: No ClassManifest available for U. - def wrap[U](xs: Vector[U]) = evenElems(xs) - ^ - -何が起こったかというと、`evenElems` は型パラメータ `U` に関するクラスマニフェストを要求するが、見つからなかったのだ。当然この場合は、`U` に関する暗黙のクラスマニフェストを要求することで解決するため、以下は成功する: - - scala> def wrap[U: ClassManifest](xs: Vector[U]) = evenElems(xs) - wrap: [U](xs: Vector[U])(implicit evidence$1: ClassManifest[U])Array[U] - -この例から、`U` の定義の context bound 構文は `evidence$1` と呼ばれる `ClassManifest[U]` 型の暗黙のパラメータの略記法であることが分かる。 - -要約すると、ジェネリックな配列の作成はクラスマニフェストを必要とする。型パラメータ `T` の配列を作成する場合、`T` に関する暗黙のクラスマニフェストも提供する必要がある。その最も簡単な方法は、`[T: ClassManifest]` のように、型パラメータを context bound 構文で `ClassManifest` と共に定義することだ。 diff --git a/ja/overviews/collections/concrete-immutable-collection-classes.md b/ja/overviews/collections/concrete-immutable-collection-classes.md deleted file mode 100644 index 8f7895bc82..0000000000 --- a/ja/overviews/collections/concrete-immutable-collection-classes.md +++ /dev/null @@ -1,193 +0,0 @@ ---- -layout: overview-large -title: 具象不変コレクションクラス - -discourse: false - -partof: collections -num: 8 -language: ja ---- - -Scala は様々な具象不変コレクションクラス (concrete immutable collection class) を提供する。これらはどのトレイトを実装するか(マップ、集合、列)、無限を扱えるか、様々な演算の速さなどの違いがある。ここに、Scala で最もよく使われる不変コレクション型を並べる。 - -## リスト - -リスト ([`List`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/List.html)) は有限の不変列だ。リストは最初の要素とリストの残りの部分に定数時間でアクセスでき、また、新たな要素をリストの先頭に追加する定数時間の cons 演算を持つ。他の多くの演算は線形時間で行われる。 - -リストは Scala プログラミングの働き者であり続けてきたので、あえてここで語るべきことは多くない。Scala 2.8 での大きな変更点は `List` クラスはそのサブクラスである `::` とそのサブオブジェクトである `Nil` とともに、論理的にふさわしい `scala.collection.immutable` パッケージで定義されるようになったことだ。`scala` パッケージには `List`、`Nil`、および `::` へのエイリアスがあるため、ユーザの立場から見ると、リストは今まで通り使うことができる。 - -もう一つの変更点は、リストは以前のような特殊扱いではなく、コレクションフレームワークにより緊密に統合されたことだ。例えば、`List` のコンパニオンオブジェクトにあった多くのメソッドは廃止予定になった。代わりに、それらは全てのコレクションが継承する[共通作成メソッド](creating-collections-from-scratch.html)に取って代わられた。 - -## ストリーム - -ストリーム ([`Stream`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Stream.html)) はリストに似ているが、要素は遅延評価される。そのため、ストリームは無限の長さをもつことができる。呼び出された要素のみが計算される。他の点においては、ストリームはリストと同じ性能特性をもつ。 - -リストは `::` 演算子によって構築されるが、ストリームはそれに似た `#::` 演算子によって構築される。以下は、整数の 1, 2, 3 からなる簡単なストリームの例だ: - - scala> val str = 1 #:: 2 #:: 3 #:: Stream.empty - str: scala.collection.immutable.Stream[Int] = Stream(1, ?) - -このストリームの head は 1 で、tail は 2 と 3 だ。上の例では tail が表示されていないが、それはまだ計算されていないからだ。ストリームは遅延評価されるため、`toString` は余計な評価を強いないように慎重に設計されているのだ。 - -以下に、もう少し複雑な例を示す。任意の二つの数から始まるフィボナッチ数列を計算するストリームだ。フィボナッチ数列とは、それぞれの要素がその前二つの要素の和である数列のことだ。 - - scala> def fibFrom(a: Int, b: Int): Stream[Int] = a #:: fibFrom(b, a + b) - fibFrom: (a: Int,b: Int)Stream[Int] - -この関数は嘘のように単純だ。数列の最初の要素は明らかに `a` で、残りは `b` そして `a + b` から始まるフィボナッチ数列だ。無限の再帰呼び出しに陥らずにこの数列を計算するのが難しい所だ。もしこの関数が `#::` の代わりに `::` を使っていたなら、全ての呼び出しはまた別の呼び出しを招くため、無限の再帰呼び出しに陥ってしまう。しかし、`#::` -を使っているため、右辺は呼び出されるまでは評価されないのだ。 - -2つの 1 から始まるフィボナッチ数列の最初の数要素を以下に示す: - - scala> val fibs = fibFrom(1, 1).take(7) - fibs: scala.collection.immutable.Stream[Int] = Stream(1, ?) - scala> fibs.toList - res9: List[Int] = List(1, 1, 2, 3, 5, 8, 11) - -## ベクトル - -リストはアルゴリズムが慎重にリストの先頭要素 (`head`) のみを処理する場合、非常に効率的だ。`head` の読み込み、追加、および削除は一定数時間で行われるのに対して、リストの後続の要素に対する読み込みや変更は、その要素の深さに依存した線形時間で実行される。 - -ベクトル ([`Vector`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Vector.html)) は、ランダムアクセス時の非効率性を解決するために Scala 2.8 から導入された新しいコレクション型だ。ベクトルはどの要素の読み込みも「事実上」定数時間で行う。リストの `head` の読み込みや配列の要素読み込みに比べると大きい定数だが、定数であることには変りない。この結果、ベクトルを使ったアルゴリズムは列の `head` のみを読み込むことに神経質にならなくていい。任意の場所の要素を読み込んだり、変更したりできるため、コードを書くのに便利だ。 - -ベクトルは、他の列と同じように作成され、変更される。 - - scala> val vec = scala.collection.immutable.Vector.empty - vec: scala.collection.immutable.Vector[Nothing] = Vector() - scala> val vec2 = vec :+ 1 :+ 2 - vec2: scala.collection.immutable.Vector[Int] = Vector(1, 2) - scala> val vec3 = 100 +: vec2 - vec3: scala.collection.immutable.Vector[Int] = Vector(100, 1, 2) - scala> vec3(0) - res1: Int = 100 - -ベクトルは分岐度の高い木構造で表される。全てのノードは32以下の要素か、32以下の他のノードを格納する。32個以下の要素を持つベクトルは単一のノードで表すことができる。ベクトルは、たった一つの間接呼び出しで、`32 * 32 = 1024`個までの要素を扱うことができる。木構造の根ノードから末端ノードまで 2ホップで 215個、3ホップで 220個、4ホップで -230個以下までの要素をベクトルは扱うことができる。よって、普通のサイズのベクトルの要素選択は 5回以内の配列選択で行うことができる。要素選択が「事実上定数時間」と言ったのは、こういうことだ。 - -ベクトルは不変であるため、ベクトルの変更無しにベクトル内の要素を変更することはできない。しかし、`updated` メソッドを使うことで一つの要素違いの新たなベクトルを作成することができる: - - scala> val vec = Vector(1, 2, 3) - vec: scala.collection.immutable.Vector[Int] = Vector(1, 2, 3) - scala> vec updated (2, 4) - res0: scala.collection.immutable.Vector[Int] = Vector(1, 2, 4) - scala> vec - res1: scala.collection.immutable.Vector[Int] = Vector(1, 2, 3) - -最後の行が示すように、`updated` の呼び出しは元のベクトル `vec` には一切影響しない。読み込みと同様に、ベクトルの関数型更新も「事実上定数時間」で実行される。ベクトルの真ん中にある要素を更新するには、その要素を格納するノードと、木構造の根ノードからを初めとする全ての親ノードをコピーすることによって行われる。これは関数型更新は、32以内の要素か部分木を格納する 1 〜 5個の ノードを作成することを意味する。これは、可変配列の in-place での上書きに比べると、ずっと時間のかかる計算であるが、ベクトル全体をコピーするよりはずっと安いものだ。 - -ベクトルは高速なランダム読み込みと高速な関数型更新の丁度いいバランスを取れているため、不変添字付き列 ([`immutable.IndexedSeq`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/IndexedSeq.html)) トレイトのデフォルトの実装となっている: - - scala> collection.immutable.IndexedSeq(1, 2, 3) - res2: scala.collection.immutable.IndexedSeq[Int] = Vector(1, 2, 3) - -## 不変スタック - -後入れ先出し (LIFO: last in first out) の列が必要ならば、スタック ([`Stack`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Stack.html)) がある。 `push` メソッドを使ってスタックに要素をプッシュ、`pop` を使ってポップ、そして`top` を使って削除することなく一番上の要素を読み込むことができる。これらの演算は、全て定数時間で行われる。 - -以下はスタックに対して行われる簡単な演算の例だ: - - scala> val stack = scala.collection.immutable.Stack.empty - stack: scala.collection.immutable.Stack[Nothing] = Stack() - scala> val hasOne = stack.push(1) - hasOne: scala.collection.immutable.Stack[Int] = Stack(1) - scala> stack - stack: scala.collection.immutable.Stack[Nothing] = Stack() - scala> hasOne.top - res20: Int = 1 - scala> hasOne.pop - res19: scala.collection.immutable.Stack[Int] = Stack() - -機能的にリストとかぶるため、不変スタックが Scala のプログラムで使われることは稀だ: 不変スタックの `push` はリストの `::` と同じで、`pop` はリストの `tail` と同じだ。 - -## 不変キュー - -キュー ([`Queue`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Queue.html)) はスタックに似ているが、後入れ先出し (LIFO: last in first out) ではなく、先入れ先出し (FIFO: -first in first out) だ。 - -以下に空の不変キューの作り方を示す: - - scala> val empty = scala.collection.immutable.Queue[Int]() - empty: scala.collection.immutable.Queue[Int] = Queue() - -`enqueue` を使って不変キューに要素を追加することができる: - - scala> val has1 = empty.enqueue(1) - has1: scala.collection.immutable.Queue[Int] = Queue(1) - -複数の要素をキューに追加するには、enqueue の引数にコレクションを渡す: - - scala> val has123 = has1.enqueue(List(2, 3)) - has123: scala.collection.immutable.Queue[Int] - = Queue(1, 2, 3) - -キューの先頭から要素を削除するには、`dequeue` を使う: - - scala> val (element, has23) = has123.dequeue - element: Int = 1 - has23: scala.collection.immutable.Queue[Int] = Queue(2, 3) - -`dequeue` は削除された要素と残りのキューのペアを返すことに注意してほしい。 - -## 範囲 - -範囲 ([`Range`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Range.html)) は順序付けされた等間隔の整数の列だ。例えば、「1、2、3」は範囲であり、「5、8、11、14」も範囲だ。Scala で範囲を作成するには、予め定義された `to` メソッドと `by` メソッドを使う。 - - scala> 1 to 3 - res2: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3) - scala> 5 to 14 by 3 - res3: scala.collection.immutable.Range = Range(5, 8, 11, 14) - -上限を含まない範囲を作成したい場合は、`to` の代わりに、便宜上用意された `until` メソッドを使う: - - scala> 1 until 3 - res2: scala.collection.immutable.Range = Range(1, 2) - -範囲は、開始値、終了値、ステップ値という、たった三つの数で定義できため定数空間で表すことができる。そのため、範囲の多くの演算は非常に高速だ。 - -## ハッシュトライ - -ハッシュトライは不変集合と不変マップを効率的に実装する標準的な方法だ。ハッシュトライは、[`immutable.HashMap`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/HashMap.html) クラスによりサポートされている。データ構造は、全てのノードに 32個の要素か 32個の部分木があるという意味でベクトルに似ている。しかし、キーの選択はハッシュコードにより行われる。たとえば、マップから任意のキーを検索する場合、まずキーのハッシュコードを計算する。その後、最初の部分木を選択するのにハッシュコードの下位 5ビットが使われ、次の 5ビットで次の部分木が選択される、という具合だ。ノード内の全ての要素が、その階層までで選ばれているビット範囲内でお互いと異なるハッシュコードを持った時点で選択は終了する。 - -ハッシュトライは、サイズ相応の高速な検索と、相応に効率的な関数型加算 `(+)` と減算 `(-)` の調度良いバランスが取れている。そのため、ハッシュトライは Scala の不変マップと不変集合のデフォルトの実装を支えている。実は、Scala は要素が 5個未満の不変集合と不変マップに関して、更なる最適化をしている。1 〜 4個の要素を持つ集合とセットは、要素 (マップの場合は、キー/値のペア) をフィールドとして持つ単一のオブジェクトとして格納する。空の不変集合と、空の不変マップは、ぞれぞれ単一のオブジェクトである。空の不変集合や不変マップは、空であり続けるため、データ構造を複製する必要はない。 - -## 赤黒木 - -赤黒木は、ノードが「赤」か「黒」に色付けされている平衡二分木の一種だ。他の平衡二分木と同様に演算は木のサイズのログ時間内に確実に完了する。 - -Scala は内部で赤黒木を使った不変集合と不変マップの実装を提供する。[`TreeSet`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/TreeSet.html) と [`TreeMap`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/TreeMap.html) クラスがそれだ。 - - scala> scala.collection.immutable.TreeSet.empty[Int] - res11: scala.collection.immutable.TreeSet[Int] = TreeSet() - scala> res11 + 1 + 3 + 3 - res12: scala.collection.immutable.TreeSet[Int] = TreeSet(1, 3) - -赤黒木は、全ての要素をソートされた順序で返す効率的なイテレータを提供するため、整列済み集合 (`SortedSet`) -の標準実装となっている。 - -## 不変ビット集合 - -ビット集合 ([`BitSet`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/BitSet.html)) は大きい整数のビットを小さな整数のコレクションを使って表す。例えば、3, 2, と 0 を格納するビット集合は二進法で整数の 1101、十進法で 13 を表す。 - -内部では、ビット集合は 64ビットの `Long` の配列を使っている。配列の最初の `Long` は 整数の 0〜63、二番目は 64〜127 という具合だ。そのため、ビット集合は最大値が数百以下の場合は非常にコンパクトだ。 - -ビット集合の演算はとても高速だ。所属判定は一定数時間で行われる。集合への要素の追加は、ビット集合の配列内の `Long` の数に比例するが、普通は小さい数だ。以下にビット集合の使用例を示す: - - scala> val bits = scala.collection.immutable.BitSet.empty - bits: scala.collection.immutable.BitSet = BitSet() - scala> val moreBits = bits + 3 + 4 + 4 - moreBits: scala.collection.immutable.BitSet = BitSet(3, 4) - scala> moreBits(3) - res26: Boolean = true - scala> moreBits(0) - res27: Boolean = false - -## リストマップ - -リストマップ ([`ListMap`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/ListMap.html)) は、キー/値ペアの連結リスト (linked list) により実装されたマップを表す。一般的に、リストマップの演算はリスト全体を総なめする必要がある可能性がある。そのため、リストマップの演算はマップのサイズに対して線形時間をとる。標準の不変マップの方が常に高速なので Scala のリストマップが使われることはほとんど無い。唯一性能の差が出る可能性としては、マップが何らかの理由でリストの最初の要素が他の要素に比べてずっと頻繁に読み込まれるように構築された場合だ。 - - scala> val map = scala.collection.immutable.ListMap(1->"one", 2->"two") - map: scala.collection.immutable.ListMap[Int,java.lang.String] = - Map(1 -> one, 2 -> two) - scala> map(2) - res30: String = "two" diff --git a/ja/overviews/collections/concrete-mutable-collection-classes.md b/ja/overviews/collections/concrete-mutable-collection-classes.md deleted file mode 100644 index 06cb492878..0000000000 --- a/ja/overviews/collections/concrete-mutable-collection-classes.md +++ /dev/null @@ -1,165 +0,0 @@ ---- -layout: overview-large -title: 具象可変コレクションクラス - -discourse: false - -partof: collections -num: 9 -language: ja ---- - -Scala が標準ライブラリで提供する不変コレクションで最もよく使われるものをこれまで見てきた。続いて、具象可変コレクションクラス (concrete mutable collection class) を見ていく。 - -## 配列バッファ - -配列バッファ ([`ArrayBuffer`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/ArrayBuffer.html)) は、配列とサイズを格納するバッファだ。配列バッファの演算のほとんどは、単に内部の配列にアクセスして変更するだけなので、配列の演算と同じ速さで実行される。また、配列バッファは効率的にデータをバッファの最後に追加できる。配列バッファへの要素の追加はならし定数時間 (amortized constant time) で実行される。よって、配列バッファは要素が常に最後に追加される場合、大きいコレクションを効率的に構築するのに便利だ。 - - scala> val buf = scala.collection.mutable.ArrayBuffer.empty[Int] - buf: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer() - scala> buf += 1 - res32: buf.type = ArrayBuffer(1) - scala> buf += 10 - res33: buf.type = ArrayBuffer(1, 10) - scala> buf.toArray - res34: Array[Int] = Array(1, 10) - -## リストバッファ - -リストバッファ ([`ListBuffer`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/ListBuffer.html)) は、内部に配列の代わりに連結リストを使うこと以外は配列バッファに似ている。バッファを構築後にリストに変換する予定なら、配列バッファの代わりにリストバッファを使うべきだ。 - - scala> val buf = scala.collection.mutable.ListBuffer.empty[Int] - buf: scala.collection.mutable.ListBuffer[Int] = ListBuffer() - scala> buf += 1 - res35: buf.type = ListBuffer(1) - scala> buf += 10 - res36: buf.type = ListBuffer(1, 10) - scala> buf.toList - res37: List[Int] = List(1, 10) - -## 文字列ビルダ - -配列バッファが配列を構築するのに便利で、リストバッファがリストを構築するのに便利なように、文字列ビルダ ([`StringBuilder`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/StringBuilder.html)) は文字列を構築するのに便利なものだ。文字列ビルダはあまりに頻繁に使われるため、デフォルトの名前空間に既にインポートされている。以下のように、単に `new StringBuilder` で文字列ビルダを作成することができる: - - scala> val buf = new StringBuilder - buf: StringBuilder = - scala> buf += 'a' - res38: buf.type = a - scala> buf ++= "bcdef" - res39: buf.type = abcdef - scala> buf.toString - res41: String = abcdef - -## 連結リスト - -連結リスト ([`LinkedList`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/LinkedList.html)) は、`next` ポインタにより連結されたノードから成る可変列だ。多くの言語では空の連結リストを表すのに `null` が選ばれるが、空の列も全ての列演算をサポートする必要があるため、Scala のコレクションでは `null` は都合が悪い。特に、`LinkedList.empty.isEmpty` は `true` を返すべきで、`NullPointerException` を発生させるべきではない。 代わりに、空の連結リストは `next` フィールドがノード自身を指すという特殊な方法で表現されている。不変リスト同様、連結リストは順列通りに探索するのに適している。また、連結リストは要素や他の連結リストを簡単に挿入できるように実装されている。 - -## 双方向リスト - -双方向リスト ([`DoubleLinkedList`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/DoubleLinkedList.html)) は、`next` の他に、現ノードの一つ前の要素を指す `prev` -というもう一つの可変フィールドがあることを除けば、単方向の連結リストに似ている。リンクが一つ増えたことで要素の削除が非常に高速になる。 - -## 可変リスト - -可変リスト ([`MutableList`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/MutableList.html)) は単方向連結リストとリスト終端の空ノードを参照するポインタから構成される。これにより、リストの最後への要素の追加が、終端ノードのためにリスト全体を探索しなくてよくなるため、定数時間の演算となる。[`MutableList`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/MutableList.html) は、現在 Scala の [`mutable.LinearSeq`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/LinearSeq.html) トレイトの標準実装だ。 - -## キュー -Scala は不変キューの他に可変キュー ([`mutable.Queue`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/Queue.html)) も提供する。可変キューは不変のものと同じように使えるが、`enqueue` の代わりに `+=` と `++=` 演算子を使って加算する。また、可変キューの `dequeue` は先頭の要素を削除して、それを返す。次に具体例で説明する: - - scala> val queue = new scala.collection.mutable.Queue[String] - queue: scala.collection.mutable.Queue[String] = Queue() - scala> queue += "a" - res10: queue.type = Queue(a) - scala> queue ++= List("b", "c") - res11: queue.type = Queue(a, b, c) - scala> queue - res12: scala.collection.mutable.Queue[String] = Queue(a, b, c) - scala> queue.dequeue - res13: String = a - scala> queue - res14: scala.collection.mutable.Queue[String] = Queue(b, c) - -## 配列シーケンス - -配列シーケンス ([`ArraySeq`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/ArraySeq.html)) は、内部で要素を `Array[Object]` に格納する固定長の可変列だ。 - -典型的には、配列の性能特性が欲しいが、要素の型が特定できず、実行時に `ClassManifest` も無く、ジェネリックな列を作成したい場合に [`ArraySeq`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/ArraySeq.html) を使う。この問題は後ほど[配列の節](arrays.html)で説明する。 - -## スタック - -既に不変スタックについては説明した。スタックには、[`mutable.Stack`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/Stack.html) クラスにより実装される可変バージョンもある。変更が上書き処理されるという違いの他は、不変バージョンと全く同じように動作する。 - - scala> val stack = new scala.collection.mutable.Stack[Int] - stack: scala.collection.mutable.Stack[Int] = Stack() - scala> stack.push(1) - res0: stack.type = Stack(1) - scala> stack - res1: scala.collection.mutable.Stack[Int] = Stack(1) - scala> stack.push(2) - res0: stack.type = Stack(1, 2) - scala> stack - res3: scala.collection.mutable.Stack[Int] = Stack(1, 2) - scala> stack.top - res8: Int = 2 - scala> stack - res9: scala.collection.mutable.Stack[Int] = Stack(1, 2) - scala> stack.pop - res10: Int = 2 - scala> stack - res11: scala.collection.mutable.Stack[Int] = Stack(1) - -## 配列スタック - -配列スタック ([`ArrayStack`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/ArrayStack.html) ) は、内部に必要に応じて大きさを変えた `Array` を使った、可変スタックの代替実装だ。配列スタックは、高速な添字読み込みを提供し、他の演算に関しても普通の可変スタックに比べて少し効率的だ。 - -## ハッシュテーブル - -ハッシュテーブルは、内部で要素を配列に格納し、要素の位置はハッシュコードにより決められる。ハッシュテーブルへの要素の追加は、既に配列の中に同じハッシュコードを持つ他の要素が無い限り、定数時間で行われる。そのため、ハッシュコードの分布が適度である限り非常に高速だ。結果として、Scala の可変マップと可変集合のデフォルトの実装はハッシュテーブルに基づいており、[`mutable.HashSet`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/HashSet.html) クラスと [`mutable.HashMap`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/HashMap.html) クラスから直接アクセスできる。 - -ハッシュ集合とハッシュマップは他の集合やマップと変りなく使うことができる。以下に具体例で説明する: - - scala> val map = scala.collection.mutable.HashMap.empty[Int,String] - map: scala.collection.mutable.HashMap[Int,String] = Map() - scala> map += (1 -> "make a web site") - res42: map.type = Map(1 -> make a web site) - scala> map += (3 -> "profit!") - res43: map.type = Map(1 -> make a web site, 3 -> profit!) - scala> map(1) - res44: String = make a web site - scala> map contains 2 - res46: Boolean = false - -ハッシュテーブルからの要素の取り出しは、特定の順序を保証していない。要素の取り出しは単に内部の配列を通して行われるため、順序はその時の配列の状態により決まる。要素の取り出しの順序を保証したい場合は、普通のハッシュマップではなく**連結**ハッシュマップを使うべきだ。連結ハッシュマップもしくは連結ハッシュ集合は、要素を追加した順序を保った連結リストを含む以外は普通のハッシュマップやハッシュ集合とほとんど同じだ。それにより、コレクションからの要素の取り出しは要素が追加された順序と常に一致する。 - -## ウィークハッシュマップ - -ウィークハッシュマップ([`WeakHashMap`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/WeakHashMap.html)) は、ガーベッジコレクタがマップから「弱キー」への参照を追跡しないという特殊なハッシュマップだ。他にキーを参照するものが無くなると、キーとその関連する値はマップから勝手にいなくなる。ウィークハッシュマップは、キーに対する時間のかかる計算結果を再利用するキャッシュのような用途に向いている。キーとその計算結果が普通のハッシュマップに格納された場合、そのマップは限りなく大きくなり続け、キーはガベージコレクタに永遠に回収されない。ウィークハッシュマップを使うことでこの問題を回避できる。キーオブジェクトが到達不可能になり次第、そのエントリーごとウィークハッシュマップから削除される。Scala のウィークハッシュマップは、Java による実装 `java.util.WeakHashMap` のラッパーである [`WeakHashMap`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/WeakHashMap.html) クラスにより実装されている。 - -## 並行マップ - -並行マップ ([`ConcurrentMap`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/ConcurrentMap.html)) は複数のスレッドから同時にアクセスすることできる。通常の[マップ](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Map.html)の演算の他に、以下のアトミックな演算を提供する: - -### ConcurrentMap クラスの演算 ### - -| 使用例 | 振る舞い| -| ------ | ------ | -| `m putIfAbsent(k, v)` |既に `k` がある場合を除き、キー/値ペア `k -> m` を `m` に追加する。| -| `m remove (k, v)` |`k` に関連付けられた値が `v` である場合、そのエントリを削除する。| -| `m replace (k, old, new)`|`k` に関連付けられた値が `old` である場合、それを `new` で上書きする。| -| `m replace (k, v)` |`k` に任意の関連付けられた値がある場合、それを `v` で上書きする。| - -[`ConcurrentMap`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/ConcurrentMap.html) は Scala コレクションライブラリ内のトレイトだ。現在そのトレイトを実装するのは Java の -`java.util.concurrent.ConcurrentMap` クラスだけで、それは [Java/Scala コレクションの標準変換](conversions-between-java-and-scala-collections.html)を使って Scala のマップに変換することができる。 - -## 可変ビット集合 - -可変ビット集合 ([`mutable.BitSet`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/BitSet.html)) は、変更が上書き処理される他は不変のものとほとんど同じだ。可変ビット集合は、変更しなかった `Long` をコピーしなくても済むぶん不変ビット集合に比べて少し効率的だ。 - - scala> val bits = scala.collection.mutable.BitSet.empty - bits: scala.collection.mutable.BitSet = BitSet() - scala> bits += 1 - res49: bits.type = BitSet(1) - scala> bits += 3 - res50: bits.type = BitSet(1, 3) - scala> bits - res51: scala.collection.mutable.BitSet = BitSet(1, 3) diff --git a/ja/overviews/collections/conversions-between-java-and-scala-collections.md b/ja/overviews/collections/conversions-between-java-and-scala-collections.md deleted file mode 100644 index ff3a6e3a0e..0000000000 --- a/ja/overviews/collections/conversions-between-java-and-scala-collections.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -layout: overview-large -title: Java と Scala 間のコレクションの変換 - -discourse: false - -partof: collections -num: 17 -language: ja ---- - -Scala と同様に、Java -にも豊富なコレクションライブラリがある。両者には多くの共通点がある。例えば、両方のライブラリともイテレータ、`Iterable`、集合、マップ、そして列を提供する。しかし、両者には重要な違いもある。特に、Scala では不変コレクションに要点を置き、コレクションを別のものに変換する演算も多く提供している。 - -時として、コレクションを一方のフレームワークから他方へと渡す必要がある。例えば、既存の Java のコレクションを Scala のコレクションであるかのようにアクセスしたいこともあるだろう。もしくは、Scala のコレクションを Java のコレクションを期待している Java メソッドに渡したいと思うかもしれない。Scala は [`JavaConversions`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/JavaConversions$.html) オブジェクトにより主要なコレクション間の暗黙の変換 (implicit conversion) -を提供するため、簡単に相互運用できる。特に以下の型に関しては、双方向変換を提供する。 - - Iterator <=> java.util.Iterator - Iterator <=> java.util.Enumeration - Iterable <=> java.lang.Iterable - Iterable <=> java.util.Collection - mutable.Buffer <=> java.util.List - mutable.Set <=> java.util.Set - mutable.Map <=> java.util.Map - mutable.ConcurrentMap <=> java.util.concurrent.ConcurrentMap - -このような変換を作動させるには、[`JavaConversions`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/JavaConversions$.html) オブジェクトからインポートするだけでいい: - - scala> import collection.JavaConversions._ - import collection.JavaConversions._ - -これで Scala コレクションとそれに対応する Java コレクションの自動変換が行われる。 - - scala> import collection.mutable._ - import collection.mutable._ - scala> val jul: java.util.List[Int] = ArrayBuffer(1, 2, 3) - jul: java.util.List[Int] = [1, 2, 3] - scala> val buf: Seq[Int] = jul - buf: scala.collection.mutable.Seq[Int] = ArrayBuffer(1, 2, 3) - scala> val m: java.util.Map[String, Int] = HashMap("abc" -> 1, "hello" -> 2) - m: java.util.Map[String,Int] = {hello=2, abc=1} - -内部では、このような変換は全ての演算を委譲する「ラッパー」オブジェクトを作ることで実現されている。そのため、Java と Scala の間でコレクションを変換してもコレクションはコピーされることはない。興味深い特性として、例えば Java 型から対応する Scala 型に変換して再び Java 型に逆変換するといった、ラウンドトリップを実行した場合、始めた時と同一のオブジェクトが返ってくるというものがある。 - -Scala コレクションの中には、Java 型に変換できるが、逆変換はできないというものもある。それらは以下の通り: - - Seq => java.util.List - mutable.Seq => java.utl.List - Set => java.util.Set - Map => java.util.Map - -Java は可変コレクションと不変コレクションを型で区別しないため、例えば `scala.immutable.List` からの変換は、上書き演算を呼び出すと `UnsupportedOperationException` を発生する `java.util.List` を返す。次に具体例で説明する: - - scala> jul = List(1, 2, 3) - jul: java.util.List[Int] = [1, 2, 3] - scala> jul.add(7) - java.lang.UnsupportedOperationException - at java.util.AbstractList.add(AbstractList.java:131) - diff --git a/ja/overviews/collections/creating-collections-from-scratch.md b/ja/overviews/collections/creating-collections-from-scratch.md deleted file mode 100644 index dbc5e4d7f9..0000000000 --- a/ja/overviews/collections/creating-collections-from-scratch.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -layout: overview-large -title: コレクションの作成 - -discourse: false - -partof: collections -num: 16 -language: ja ---- - -`List(1, 2, 3)` 構文によって 3つの整数から成るリストを作成でき、`Map('A' -> 1, 'C' -> 2)` 構文によって 2つの写像から成るマップを作成することができる。これは Scala コレクションの統一された機能だ。どのコレクションを取っても、その名前に括弧付けされた要素のリストを付け加えることができる。結果は渡された要素から成る新しいコレクションだ。以下に具体例で説明する: - - Traversable() // 空の traversable オブジェクト - List() // 空のリスト - List(1.0, 2.0) // 要素 1.0, 2.0 を含むリスト - Vector(1.0, 2.0) // 要素 1.0, 2.0 を含むベクトル - Iterator(1, 2, 3) // 3つの整数を返すイテレータ - Set(dog, cat, bird) // 3種類の動物の集合 - HashSet(dog, cat, bird) // 同じ動物のハッシュ集合 - Map('a' -> 7, 'b' -> 0) // 文字から整数へのマップ - -上の全ての行での呼び出しは内部では何らかのオブジェクトの `apply` メソッドを呼び出している。例えば、3行目は以下のように展開する: - - List.apply(1.0, 2.0) - -つまり、これは `List` クラスのコンパニオンオブジェクトの `apply` メソッドを呼び出している。このメソッドは任意の数の引数を取り、それを使ってリストを構築する。Scala ライブラリの全てのコレクションクラスには、コンパニオンオブジェクトがあり、そのような `apply` メソッドを定義する。コレクションクラスが `List`、`Stream`、や `Vector` のような具象実装を表しているのか、`Seq`、`Set`、や `Traversable` のような抽象基底クラスを表しているのかは関係ない。後者の場合は、`apply` の呼び出しは抽象基底クラスの何らかのデフォルト実装を作成するだけのことだ。用例: - - scala> List(1, 2, 3) - res17: List[Int] = List(1, 2, 3) - scala> Traversable(1, 2, 3) - res18: Traversable[Int] = List(1, 2, 3) - scala> mutable.Traversable(1, 2, 3) - res19: scala.collection.mutable.Traversable[Int] = ArrayBuffer(1, 2, 3) - -`apply` とは別に、全てのコレクションのコンパニオンオブジェクトは、空のコレクションを返す `empty` を定義する。よって、`List()` の代わりに `List.empty` と書いたり、`Map()` の代わりに `Map.empty` と書くことができる。 - -`Seq` を継承するクラスは、コンパニオンオブジェクトにおいて他の factory 演算を提供する。以下の表にこれらの演算をまとめた。要約すると、 - -* `concat` は任意の数の traversable を連結する。 -* `fill` と `tabulate` は単次元か任意の多次元の列を生成して、なんらかの式かテーブル化関数によりその列を初期化する。 -* `range` は一定のステップ値で整数の列を生成する。 -* `iterate` は開始要素に連続して関数を適用することによって得られる列を生成する。 - -### 列の factory 演算 - -| 使用例 | 振る舞い| -| ------ | ------ | -| `S.empty` | 空の列。 | -| `S(x, y, z)` | 要素 `x`, `y`, `z` からなる列。 | -| `S.concat(xs, ys, zs)` | `xs`, `ys`, `zs` の要素を連結することによって得られる列。 | -| `S.fill(n){e}` | 全ての要素が式 `e` によって計算された長さ `n` の列。 | -| `S.fill(m, n){e}` | 全ての要素が式 `e` によって計算された `m × n` の大きさの列の列。(より高次元なものもある) | -| `S.tabulate(n){f}` | 添字 `i` の位置の要素が `f(i)` によって計算された長さ `n` の列。 | -| `S.tabulate(m, n){f}` | 添字 `(i, j)` の位置の要素が `f(i, j)` によって計算された `m×n` の大きさの列の列。(より高次元なものもある)| -| `S.range(start, end)` | `start` ... `end-1` の整数の列。 | -| `S.range(start, end, step)`| `start` より始まり `end` 未満まで `step` づつ増加する整数の列。 | -| `S.iterate(x, n)(f)` | 要素 `x`、`f(x)`、`f(f(x))`、… からなる長さ `n` の列。 | diff --git a/ja/overviews/collections/equality.md b/ja/overviews/collections/equality.md deleted file mode 100644 index a53f0efd5b..0000000000 --- a/ja/overviews/collections/equality.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -layout: overview-large -title: 等価性 - -discourse: false - -partof: collections -num: 13 -language: ja ---- - -コレクションライブラリは等価性 (equality) とハッシング (hashing) に関して統一的な方法を取る。おおまかに言えば、まず、コレクションは集合、マップ、列の三種に大別される。別カテゴリのコレクションは常に不等だと判定される。例えば、`Set(1, 2, 3)` と `List(1, 2, 3)` は同じ要素を格納するが、不等だ。一方、同カテゴリ内ではコレクション内の要素が全く同じ要素から成る (列の場合は、同じ順序の同じ要素) 場合のみ等価だと判定される。例えば、`List(1, 2, 3) == Vector(1, 2, 3)` であり、`HashSet(1, 2) == TreeSet(2, 1)` だ。 - -コレクションが可変であるか不変であるかは、等価性の判定には関わらない。可変コレクションに関しては、等価性判定が実行された時点での要素の状態が用いられる。これは、可変コレクションが追加されたり削除されたりする要素によって、別のコレクションと等価であったり不等であったりすることを意味する。これはハッシュマップのキーとして可変コレクションを使用した場合、落とし穴となりうる。具体例としては: - - scala> import collection.mutable.{HashMap, ArrayBuffer} - import collection.mutable.{HashMap, ArrayBuffer} - scala> val buf = ArrayBuffer(1, 2, 3) - buf: scala.collection.mutable.ArrayBuffer[Int] = - ArrayBuffer(1, 2, 3) - scala> val map = HashMap(buf -> 3) - map: scala.collection.mutable.HashMap[scala.collection. - mutable.ArrayBuffer[Int],Int] = Map((ArrayBuffer(1, 2, 3),3)) - scala> map(buf) - res13: Int = 3 - scala> buf(0) += 1 - scala> map(buf) - java.util.NoSuchElementException: key not found: - ArrayBuffer(2, 2, 3) - -この例では、最後から二番目の行において配列 `xs` のハッシュコードが変わったため、最後の行の選択は恐らく失敗に終わる。ハッシュコードによる検索は `xs` が格納されていた元の位置とは別の場所を探しているからだ。 diff --git a/ja/overviews/collections/introduction.md b/ja/overviews/collections/introduction.md deleted file mode 100644 index 0119b3cfea..0000000000 --- a/ja/overviews/collections/introduction.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -layout: overview-large -title: はじめに - -discourse: false - -partof: collections -num: 1 -language: ja ---- - -**Martin Odersky, Lex Spoon 著**
        -**Eugene Yokota 訳** - -Scala 2.8 の変更点で最も重要なものは新しいコレクションフレームワークだと多くの人が思っている。確かに以前の Scala にもコレクションはあったが、Scala 2.8 になって一貫性があり、包括的な、統一コレクション型のフレームワークを提供することができた。(大部分において新しいフレームワークは旧版と互換性がある) - -コレクションの変更点は、一見すると微細なものだがプログラミングスタイルに重大な変化をもたらすことができる。コレクション内の要素ではなくコレクション全体を、プログラムを組む時の基本的なパーツとすることで、あたかも一つ上の階層でプログラミングをしているかのように感じるだろう。この新しいスタイルには慣れを必要とするが、幸い新しいコレクションの特長のお陰で楽に適合できるようになっている。 -新しいコレクションは簡単に使えて、短く書けて、安全、高速で、統一性がある。 - -**簡単に使える:** 基本的なボキャブラリは、演算 (operation) とよばれる 20〜50 のメソッドから成る。これを身につけてしまえば、二つの演算を組み合わせるだけで、ほとんどのコレクションの問題を解決することができる。複雑なループ構造や再帰に頭を悩ませる必要はない。 -永続的なコレクションと副作用のない演算は、既存のコレクションを間違って新しいデータで壊してしまう心配をする必要がないことを意味する。イテレータとコレクションの更新の間の干渉は完全になくなった。 - -**短く書ける:** 一つまたは複数のループが必要だった作業を単語一つで実現することができる。関数型の演算もライトウェイトな構文で表現でき、簡単に演算を組み合わせることでカスタム代数を扱っているかのように感じるはずだ。 - -**安全である:** これは実際に経験してみないと分からないだろう。 -静的型付きでかつ関数型という Scala のコレクションの特徴は、可能なエラーの圧倒的多数はコンパイル時に捕捉されることを意味する。 -その理由は、 - -
          -
        1. コレクションの演算はが大量に使用されているため、十分にテスト済みである。
        2. -
        3. コレクション演算を使うことで、インプットとアウトプットが関数のパラメータと結果という形で明示的になる。
        4. -
        5. これらの明示的なインプットとアウトプットは静的な型チェックの対象だ。
        6. -
        - -結論としては、誤用の大多数が型エラーとして表出するということだ。数百行のプログラムが初回の一発で実行できることは決して稀ではない。 - -**速い:** コレクション演算はライブラリの中で調整され最適化されている。その結果、コレクションを使用することは一般的にかなり効率的だ。手作業で丁寧に調整されたデータ構造と演算により多少高速化することができるかもしれないが、不適切な実装上の決断を途中でしてしまうとかなり遅くなってしまうということもありえる。さらに、現在コレクションはマルチコア上での並列実行に適応されている途中だ。並列コレクションは順次コレクションと同じ演算をサポートするため、新しい演算を習ったりコードを書き変えたりする必要はない。`par` メソッドを呼ぶだけで順次コレクションを並列に変えることができる。 - -**統一性がある:** コレクションは出来る限りどの型にも同じ演算を提供している。これにより、少ないボキャブラリの演算でも多くのことができる。例えば、文字列は概念的には文字の列 (sequence) だ。その結果、Scalaのコレクションでは、文字列は列の演算の全てをサポートする。配列に関しても同様だ。 - -**用例:** これは Scala のコレクションの多くの利点を示す一行コードだ。 - - val (minors, adults) = people partition (_.age < 18) - -この演算が何をしているかは一目瞭然だ: `people` のコレクションを年齢によって `minors` と `adult` に分割している。`partition` メソッドはコレクションの基底型である `TraversableLike` で定義されているため、このコードは配列を含むどのコレクションでも動作する。これによって生じる `minors` と `adult` のコレクションは、`people`コレクションと同じ型となる。 - -このコードは、従来の 1〜3個のループを用いたコレクション処理に比べて、より簡潔なものになっている (中間結果をどこかにバッファリングする必要があるため、配列を用いた場合はループ3個)。基本的なコレクションのボキャブラリを覚えてしまえば、明示的なループを書くよりも、このようなコードを書く方が簡単かつ安全だと思うようになるだろう。さらに、`partition` 演算は高速であり、将来的にはマルチコア上で並列コレクションにかけると更に速くなるだろう。(並列コレクションは開発ビルドに入っており、Scala 2.9 の一部としてリリースされる予定。) - -このガイドはユーザーの視点から Scala 2.8 のコレクションクラスの API について詳細に説明する。全ての基本的なクラスと、それらのメソッドをみていこう。 diff --git a/ja/overviews/collections/iterators.md b/ja/overviews/collections/iterators.md deleted file mode 100644 index 296150c817..0000000000 --- a/ja/overviews/collections/iterators.md +++ /dev/null @@ -1,177 +0,0 @@ ---- -layout: overview-large -title: イテレータ - -discourse: false - -partof: collections -num: 15 -language: ja ---- - -イテレータ ([`Iterator`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Iterator.html)) はコレクションではなく、コレクションから要素を1つづつアクセスするための方法だ。イテレータ `it` に対する基本的な演算として `next` と `hasNext` の2つがある。 `it.next()` を呼び出すことで、次の要素が返り、イテレータの内部状態が前進する。よって、同じイテレータに対して `next` を再び呼び出すと、前回返したものの次の要素が得られる。返す要素が無くなると、`next` の呼び出しは `NoSuchElementException` を発生させる。返す要素が残っているかは [`Iterator`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Iterator.html) の `hasNext` メソッドを使って調べることができる。 - -イテレータ `it` が返す全ての要素を渡り歩くのに最も率直な方法は while ループを使うことだ: - - while (it.hasNext) - println(it.next()) - -`Traversable`、`Iterable`、および `Seq` クラスのほとんどのメソッドに類似するものを Scala のイテレータは提供している。たとえば、与えられた手順をイテレータが返す全ての要素に対して実行する `foreach` メソッドを提供する。 `foreach` を使うことで、先ほどのループは以下のように短縮できる: - - it foreach println - -例にならって、`foreach`、`map`、`withFilter`、および `flatMap` の代替構文として for 式を使うことができるので、イテレータが返す全ての要素を表示するもう一つの方法として以下のように書ける: - - for (elem <- it) println(elem) - -イテレータの `foreach` メソッドと traversable の同メソッドには重大な違いがある。イテレータのそれを呼び出した場合、`foreach` はイテレータを終端に置いたままで終了するということだ。そのため、`next` を再び呼び出すと `NoSuchElementException` を発生して失敗する。それに比べ、コレクションに対して呼び出した場合、`foreach` -はコレクション内の要素の数を変更しない (渡された関数が要素を追加もしくは削除した場合は別の話だが、これは予想外の結果になることがあるので非推奨だ)。 - -`Iterator` と `Traversable` に共通の他の演算も同じ特性を持つ。例えば、イテレータは新たなイテレータを返す `map` メソッドを提供する: - - scala> val it = Iterator("a", "number", "of", "words") - it: Iterator[java.lang.String] = non-empty iterator - scala> it.map(_.length) - res1: Iterator[Int] = non-empty iterator - scala> res1 foreach println - 1 - 6 - 2 - 5 - scala> it.next() - java.util.NoSuchElementException: next on empty iterator - -上記の通り、`it.map` の呼び出しの後、イテレータ `it` は終端まで前進してしまっている。 - -次の具体例は、ある特性をもつイテレータ内の最初の要素を検索するのに使うことができる `dropWhile` だ。例えば、イテレータ内で二文字以上の最初の語句を検索するのに、このように書くことができる: - - scala> val it = Iterator("a", "number", "of", "words") - it: Iterator[java.lang.String] = non-empty iterator - scala> it dropWhile (_.length < 2) - res4: Iterator[java.lang.String] = non-empty iterator - scala> it.next() - res5: java.lang.String = number - -`dropWhile` を呼び出すことで `it` が変更された事に注意してほしい。イテレータは二番目の語句「number」を指している。実際に、`it` と `dropWhile` の返した戻り値である `res4` 同じ要素の列を返す。 - -同じイテレータを再利用するための標準演算が一つだけある。以下の - - val (it1, it2) = it.duplicate - -への呼び出しはイテレータ `it` と全く同じ要素を返すイテレータを**2つ**返す。この2つのイテレータは独立して作動するため、片方を前進しても他方は影響を受けない。一方、元のイテレータ `it` は `duplicate` により終端まで前進したため、使いものにならない。 - -要約すると、イテレータは**メソッドを呼び出した後、絶対にアクセスしなければ**コレクションのように振る舞う。Scala -コレクションライブラリは、[`Traversable`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Traversable.html) と [`Iterator`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Iterator.html) に共通の親クラスである [`TraversableOnce`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/TraversableOnce.html) を提供することで、明示的にこれを示す。名前が示す通り、 `TraversableOnce` は `foreach` を用いて一度だけ探索することができるが、探索後のそのオブジェクトの状態は指定されていない。`TraversableOnce` オブジェクトが `Iterator` ならば、探索後はその終端にあるし、もし `Traversable` ならば、そのオブジェクトは今まで通り存在する。 `TraversableOnce` のよく使われる事例としては、イテレータか `Traversable` を受け取ることができるメソッドの引数の型だ。その例として、 `Traversable` クラスの追加メソッド `++` がある。`TraversableOnce` パラメータを受け取るため、イテレータか `Traversable` なコレクションの要素を追加することができる。 - -イテレータの全演算は次の表にまとめられている。 - -### Iterator クラスの演算 - -| 使用例 | 振る舞い| -| ------ | ------ | -| **抽象メソッド:** | | -| `it.next()` | イテレータの次の要素を返し、前進させる。 | -| `it.hasNext` | `it` が次の要素を返せる場合、`true` を返す。 | -| **他のイテレータ:** | | -| `it.buffered` | `it` が返す全ての要素を返すバッファ付きイテレータ。 | -| `it grouped size` | `it` が返す要素を固定サイズの「かたまり」にして返すイテレータ。 | -| `xs sliding size` | `it` が返す要素を固定サイズの「窓」をスライドさせて返すイテレータ。 | -| **複製:** | | -| `it.duplicate` | `it` が返す全ての要素を独立して返すイテレータのペア。 | -| **加算:** | | -| `it ++ jt` | イテレータ `it` が返す全ての要素に続いてイテレータ `jt` の全ての要素を返すイテレータ。 | -| `it padTo (len, x)` | 全体で `len`個の要素が返るように、イテレータ `it` の全ての要素に続いて `x` を返すイテレータ。 | -| **map 演算:** | | -| `it map f` | `it` が返す全ての要素に関数 `f` を適用することによって得られるイテレータ。 | -| `it flatMap f` | `it` が返す全ての要素に対してイテレータ値を返す関数 `f` を適用し、その結果を連結したイテレータ。 | -| `it collect f` | `it` が返す全ての要素に対して部分関数 `f` が定義されている場合のみ適用し、その結果を集めたイテレータ。 | -| **変換演算:** | | -| `it.toArray` | `it` が返す要素を配列に集める。 | -| `it.toList` | `it` が返す要素をリストに集める。 | -| `it.toIterable` | `it` が返す要素を iterable に集める。 | -| `it.toSeq` | `it` が返す要素を列に集める。 | -| `it.toIndexedSeq` | `it` が返す要素を添字付き列に集める。 | -| `it.toStream` | `it` が返す要素をストリームに集める。 | -| `it.toSet` | `it` が返す要素を集合に集める。 | -| `it.toMap` | `it` が返すキー/値ペアをマップに集める。 | -| **コピー演算:** | | -| `it copyToBuffer buf` | `it` が返す要素をバッファ `buf` にコピーする。 | -| `it copyToArray(arr, s, n)`| `it` が返す最大 `n` 個の要素を配列 `arr` の添字 `s` より始まる位置にコピーする。最後の2つの引数は省略可能だ。 | -| **サイズ演算:** | | -| `it.isEmpty` | イテレータが空であるかどうかを調べる (`hasNext` の逆)。 | -| `it.nonEmpty` | イテレータに要素が含まれているかを調べる (`hasNext` の別名)。 | -| `it.size` | `it` が返す要素の数。注意: この演算の後、`it` は終端まで前進する! | -| `it.length` | `it.size` に同じ。 | -| `it.hasDefiniteSize` | `it` が有限数の要素を返すことが明らかな場合 true を返す (デフォルトでは `isEmpty` に同じ)。 | -| **要素取得演算・添字検索演算:**| | -| `it find p` | `it` が返す要素の中で条件関数 `p` を満たす最初の要素のオプション値、または条件を満たす要素が無い場合 `None`。注意: イテレータは探しだされた要素の次の要素、それが無い場合は終端まで前進する。 | -| `it indexOf x` | `it` が返す要素の中で `x` と等しい最初の要素の添字。注意: イテレータはこの要素の次の位置まで前進する。 | -| `it indexWhere p` | `it` が返す要素の中で条件関数 `p` を満たす最初の要素の添字、注意: イテレータはこの要素の次の位置まで前進する。 | -| **部分イテレータ演算:** | | -| `it take n` | `it` が返す最初の `n`個の要素を返すイテレータ。注意: `it` は、`n`個目の要素の次の位置、または`n`個以下の要素を含む場合は終端まで前進する。 | -| `it drop n` | `it` の `(n+1)`番目の要素から始まるイテレータ。注意: `it` も同じ位置まで前進する。| -| `it slice (m,n)` | `it` が返す要素の内、`m`番目から始まり `n`番目の一つ前で終わる切片を返すイテレータ。 | -| `it takeWhile p` | `it` が返す要素を最初から次々とみて、条件関数 `p` を満たす限り返していったイテレータ。 | -| `it dropWhile p` | `it` が返す要素を最初から次々とみて、条件関数 `p` を満たす限り飛ばしていき、残りを返すイテレータ。 | -| `it filter p` | `it` が返すの要素で条件関数 `p` を満たすものを返すイテレータ。 | -| `it withFilter p` | `it filter p` に同じ。イテレータが for 式で使えるように用意されている。 | -| `it filterNot p` |`it` が返すの要素で条件関数 `p` を満たさないものを返すイテレータ。 | -| **分割演算:** | | -| `it partition p` | `it` を2つのイテレータから成るペアに分割する。片方のイテレータは `it` が返す要素のうち条件関数 `p` を満たすものを返し、もう一方は `it` が返す要素のうち `p` を満たさないものを返す。 | -| **要素条件演算:** | | -| `it forall p` | `it` が返す全ての要素に条件関数 `p` が当てはまるかを示す boolean 値。 | -| `it exists p` | `it` が返す要素の中に条件関数 `p` を満たすものがあるかどうかを示す boolean 値。 | -| `it count p` | `it` が返す要素の中にで条件関数 `p` 満たすものの数。 | -| **fold 演算:** | | -| `(z /: it)(op)` | `z` から始めて、左から右へと `it` が返す隣接する要素に二項演算 `op` を次々と適用したもの。 | -| `(it :\ z)(op)` | `z` から始めて、右から左へと `it` が返す隣接する要素に二項演算 `op` を次々と適用したもの。 | -| `it.foldLeft(z)(op)` | `(z /: it)(op)` に同じ。 | -| `it.foldRight(z)(op)` | `(it :\ z)(op)` に同じ。 | -| `it reduceLeft op` | 左から右へと、空ではないイテレータ `it` が返す隣接する要素に二項演算 `op` を次々と適用したもの。 | -| `it reduceRight op` | 右から左へと、空ではないイテレータ `it` が返す隣接する要素に二項演算 `op` を次々と適用したもの。 | -| **特定 fold 演算:** | | -| `it.sum` | イテレータ `it` が返す数値要素の値の和。 | -| `it.product` | イテレータ `it` が返す数値要素の値の積。 | -| `it.min` | イテレータ `it` が返す順序付けされたの値の最小値。 | -| `it.max` | イテレータ `it` が返す順序付けされたの値の最大値。 | -| **zip 演算:** | | -| `it zip jt` | イテレータ `it` と `jt` が返す要素から対応したものペアにして返すイテレータ。 | -| `it zipAll (jt, x, y)` | イテレータ `it` と `jt` が返す要素から対応したものペアにして返すイテレータで、もし片方が短い場合は `x` か `y` を使って長いほうに合わせる。 | -| `it.zipWithIndex` | `it` が返す要素とその添字をペアにしたイテレータ。 | -| **更新演算:** | | -| `it patch (i, jt, r)` | `it` の、`i` から始まる `r`個の要素をパッチイテレータ `ji` が返す要素に置換したイテレータ。 | -| **比較演算:** | | -| `it sameElements jt` | イテレータ `it` と `jt` が同じ要素を同じ順序で返すかを調べる。注意: この演算の後、`it` の `jt` 少なくともどちらか一方は終端まで前進している。 | -| **文字列演算:** | | -| `it addString (b, start, sep, end)`| `it` が返す要素を `sep` で区切った後、`start` と `end` で挟んだ文字列を `StringBuilder` `b` に追加する。 `start`、`sep`、`end` は全て省略可能。 | -| `it mkString (start, sep, end)` | `it` が返す要素を `sep` で区切った後、`start` と `end` で挟んだ文字列に変換する。 `start`、`sep`、`end` は全て省略可能。 | - -### バッファ付きイテレータ - -イテレータを前進させずに次に返る要素を検査できるような「先読み」できるイテレータが必要になることがたまにある。例えば、一連の文字列を返すイテレータがあるとして、その最初の空白文字列を飛ばすという作業を考える。以下のように書こうと思うかもしれない。 - - def skipEmptyWordsNOT(it: Iterator[String]) = - while (it.next().isEmpty) {} - -しかし、このコードを慎重に見ると間違っていることが分かるはずだ。コードは確かに先頭の空白文字列の続きを読み飛ばすが、`it` は最初の非空白文字列も追い越してしまっているのだ。 - -この問題はバッファ付きイテレータを使うことで解決できる。[`BufferedIterator`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/BufferedIterator.html) トレイトは、`head` というメソッドを追加で提供する [`Iterator`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Iterator.html) の子トレイトだ。バッファ付きイテレータに対して `head` を呼び出すことで、イテレータを前進させずに最初の要素を返すことができる。バッファ付きイテレータを使うと、空白文字列を読み飛ばすのは以下のように書ける。 - - def skipEmptyWords(it: BufferedIterator[String]) = - while (it.head.isEmpty) { it.next() } - -`buffered` メソッドを呼ぶことで全てのイテレータはバッファ付きイテレータに変換できる。次に具体例で説明する: - - scala> val it = Iterator(1, 2, 3, 4) - it: Iterator[Int] = non-empty iterator - scala> val bit = it.buffered - bit: java.lang.Object with scala.collection. - BufferedIterator[Int] = non-empty iterator - scala> bit.head - res10: Int = 1 - scala> bit.next() - res11: Int = 1 - scala> bit.next() - res11: Int = 2 - -バッファ付きイテレータに対して `head` を呼び出してもイテレータ `bit` は前進しないことに注意してほしい。よって、後続の `bit.next()` の呼び出しは `bit.head` と同じ値を返す。 diff --git a/ja/overviews/collections/maps.md b/ja/overviews/collections/maps.md deleted file mode 100644 index 1ca86626c6..0000000000 --- a/ja/overviews/collections/maps.md +++ /dev/null @@ -1,162 +0,0 @@ ---- -layout: overview-large -title: マップ - -discourse: false - -partof: collections -num: 7 -language: ja ---- - -マップ ([`Map`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Map.html)) はキーと値により構成されるペアの [`Iterable`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Iterable.html) の一種で、**写像** (mapping) や**関連** (association) とも呼ばれる。Scala の [`Predef`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/Predef$.html) クラスは、ペアの `(key, value)` を `key -> value` と書けるような暗黙の変換を提供する。例えば、`Map("x" -> 24, "y" -> 25, "z" -> 26)` は `Map(("x", 24), ("y", 25), ("z", 26))` と全く同じことを意味するがより可読性が高い。 - -マップの基本的な演算は集合のものと似ている。それらは、以下の表にまとめられており、以下のカテゴリーに分類できる: - -* **検索演算** には `apply`、`get`、`getOrElse`、`contains`、および `isDefinedAt` がある。これらはマップをキーから値への部分関数に変える。マップの最も基本的な検索メソッドは `def get(key): Option[Value]` だ。"`m get key`" という演算はマップが `key` に関連する値があるかを調べる。もしあれば、マップはその関連する値を `Some` に包んで返す。`key` がマップ中に定義されていなければ `get` は `None` を返す。マップはまた、任意のキーに関連する値を `Option` に包まずに直接返す `apply` メソッドも定義する。マップにキーが定義されていない場合は、例外が発生する。 -* **加算と更新演算**である `+`、`++`、`updated` は、マップに新しい対応関係を追加するか、既存の対応関係を更新する。 -
      • 減算である --- は、対応関係をマップから削除する。
      • -* **サブコレクション取得演算**である `keys`、`keySet`、`keysIterator`、`values`、`valuesIterator` は、マップのキーや値を様々な形で別に返す。 -* **変換演算**である `filterKeys` と `mapValues` は、既存のマップの対応関係をフィルターしたり変換することで新たなマップを生成する。 - -### Map トレイトの演算 ### - -| 使用例 | 振る舞い| -| ------ | ------ | -| **検索演算:** | | -| `ms get k` |マップ `ms` 内のキー `k` に関連付けられた値のオプション値、もしくは、キーが見つからない場合、`None`。| -| `ms(k)` |(展開した場合、`ms apply k`) マップ `ms` 内のキー `k` に関連付けられた値、もしくは、キーが見つからない場合は例外。| -| `ms getOrElse (k, d)` |マップ `ms` 内のキー `k` に関連付けられた値、もしくは、キーが見つからない場合、デフォルト値 `d`。| -| `ms contains k` |`ms` がキー `k` への写像を含むかを調べる。| -| `ms isDefinedAt k` |`contains` に同じ。 | -| **加算と更新演算:**| | -| `ms + (k -> v)` |`ms` 内の全ての写像と、キー `k` から値 `v` への写像 `k -> v` を含むマップ。| -| `ms + (k -> v, l -> w)` |`ms` 内の全ての写像と、渡されたキーと値のペアを含むマップ。| -| `ms ++ kvs` |`ms` 内の全ての写像と、`kvs`内の全てのキーと値のペアを含むマップ。| -| `ms updated (k, v)` |`ms + (k -> v)` に同じ。| -| **減算:** | | -| `ms - k` |キー `k` からの写像を除く、`ms` 内の全ての写像。| -| `ms - (k, 1, m)` |渡されたキーからの写像を除く、`ms` 内の全ての写像。| -| `ms -- ks` |`ks`内のキーからの写像を除く、`ms` 内の全ての写像。| -| **サブコレクション取得演算:** | | -| `ms.keys` |`ms`内の全てのキーを含む iterable。| -| `ms.keySet` |`ms`内の全てのキーを含む集合。| -| `ms.keyIterator` |`ms`内の全てのキーを返すイテレータ。| -| `ms.values` |`ms`内のキーに関連付けられた全ての値を含む iterable。| -| `ms.valuesIterator` |`ms`内のキーに関連付けられた全ての値を返すイテレータ。| -| **変換演算:** | | -| `ms filterKeys p` |キーが条件関数 `p` を満たす `ms`内の写像のみを含むマップのビュー。| -| `ms mapValues f` |`ms`内のキーに関連付けられた全ての値に関数 `f` を適用して得られるマップのビュー。| - -可変マップ ([`mutable.Map`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/Map.html)) は他にも以下の表にまとめた演算をサポートする。 - -### mutable.Map トレイトの演算 ### - -| 使用例 | 振る舞い| -| ------ | ------ | -| **加算と更新演算:** | | -| `ms(k) = v` |(展開した場合、`ms.update(x, v)`)。マップ `ms` に副作用としてキー `k` から値 `v` への写像を加え、既に `k` からの写像がある場合は上書きする。| -| `ms += (k -> v)` |マップ `ms` に副作用としてキー `k` から値 `v` への写像を加え、`ms`自身を返す。| -| `ms += (k -> v, l -> w)` |マップ `ms` に副作用として渡された写像を加え、`ms`自身を返す。| -| `ms ++= kvs` |マップ `ms` に副作用として `kvs`内の全ての写像を加え、`ms`自身を返す。| -| `ms put (k, v)` |マップ `ms` にキー `k` から値 `v` への写像を加え、以前の `k` からの写像のオプション値を返す。| -| `ms getOrElseUpdate (k, d)`|マップ `ms`内にキー `k` が定義されている場合は、関連付けられた値を返す。定義されていない場合は、`ms` に写像 `k -> d` を加え、`d` を返す。| -| **減算:**| | -| `ms -= k` |マップ `ms` から副作用としてキー `k` からの写像を削除して、`ms`自身を返す。| -| `ms -= (k, l, m)` |マップ `ms` から副作用として渡されたキーからの写像を削除して、`ms`自身を返す。| -| `ms --= ks` |マップ `ms` から副作用として `ks`内の全てのキーからの写像を削除して、`ms`自身を返す。| -| `ms remove k` |マップ `ms` からキー `k` からの写像を削除して、以前の `k` からの写像のオプション値を返す。| -| `ms retain p` |`ms`内の写像でキーが条件関数 `p` を満たすものだけを残す。| -| `ms.clear()` |`ms` から全ての写像を削除する。| -| **変換演算:** | | -| `ms transform f` |マップ `ms`内の全ての関連付けされた値を関数 `f` を使って変換する。| -| **クローン演算:** | | -| `ms.clone` |`ms` と同じ写像を持つ新しい可変マップを返す。| - -マップの加算と減算は、集合のそれにならう。集合と同様、非破壊的な演算である `+`、`-`、と `updated` を提供するが、加算マップをコピーする必要があるため、これらはあまり使われることがない。そのかわり、可変マップは通常 `m(key) = value` か `m += (key -> value)` という2種類の更新演算を使って上書き更新される。さらに前に `key` から関連付けされていた値を -`Option`値で返すか、マップに `key` が無ければ `None` を返すというバリアントである `m put (key, value)` もある。 - -`getOrElseUpdate` はキャッシュとして振る舞うマップにアクセスするのに役立つ。例えば、関数 `f` により呼び出される時間のかかる計算があるとする: - - scala> def f(x: String) = { - println("taking my time."); sleep(100) - x.reverse } - f: (x: String)String - -さらに、`f` には副作用を伴わず、同じ引数で何回呼び出しても同じ戻り値が返ってくると仮定する。この場合、引数と以前の `f` 計算結果の対応関係をマップに格納して、引数がマップに無いときだけ `f` の結果を計算すれば時間を節約できる。この時、マップは関数 `f` の計算の**キャッシュ**であると言える。 - - val cache = collection.mutable.Map[String, String]() - cache: scala.collection.mutable.Map[String,String] = Map() - -これにより、より効率的な、キャッシュするバージョンの関数 `f` を作成することができる: - - scala> def cachedF(s: String) = cache.getOrElseUpdate(s, f(s)) - cachedF: (s: String)String - scala> cachedF("abc") - taking my time. - res3: String = cba - scala> cachedF("abc") - res4: String = cba - -`getOrElseUpdate` の第二引数は「名前渡し」(by-name) であるため、上の `f("abc")` は `getOrElseUpdate` が必要とする場合、つまり第一引数が `cache` に無い場合においてのみ計算されることに注意してほしい。 `cachedF` をより率直に、普通の map 演算を用いて実装することもできるが、コードは少し長くなる: - - def cachedF(arg: String) = cache get arg match { - case Some(result) => result - case None => - val result = f(x) - cache(arg) = result - result - } - -### 同期集合と同期マップ ### - -`SynchronizedMap` トレイトを好みのマップ実装にミックスインすることでスレッドセーフな可変マップを得ることができる。例えば、以下のコードが示すように、`HashMap` に `SynchronizedMap` をミックスインすることができる。この例は `Map` と `SynchronizedMap` の二つのトレイト、そして `HashMap` という一つのクラスを `scala.collection.mutable` パッケージからインポートすることから始まる。例の残りは `makeMap` というメソッドを宣言するシングルトンオブジェクト `MapMaker` を定義する。`makeMap` メソッドは戻り値の型を文字列をキーとして文字列を値とする可変マップだと宣言する。 - - import scala.collection.mutable.{Map, - SynchronizedMap, HashMap} - object MapMaker { - def makeMap: Map[String, String] = { - new HashMap[String, String] with - SynchronizedMap[String, String] { - override def default(key: String) = - "Why do you want to know?" - } - } - } - -`makeMap` 本文の第1ステートメントは `SynchronizedMap` トレイトをミックスインする新しい可変 `HashMap` を作成する: - - new HashMap[String, String] with - SynchronizedMap[String, String] - -このコードを与えられると、Scala コンパイラは `SynchronizedMap` をミックスインする `HashMap` の合成的な子クラスを生成し、そのインスタンスを作成する (そして、それを戻り値として返す)。この合成クラスは、以下のコードのため、`default` という名前のメソッドをオーバーライドする。: - - override def default(key: String) = - "何故知りたい?" - -通常は、ある特定のキーに対する値をマップに問い合わせて、そのキーからの写像が無い場合は`NoSuchElementException` が発生する。新たなマップのクラスを定義して `default` メソッドをオーバーライドした場合は、しかしながら、存在しないキーに対する問い合わせに対して、この新しいマップは `default` が返す値を返す。そのため、同期マップのコードでコンパイラに生成された `HashMap` の合成の子クラスは、存在しないキーに対する問い合わせに `"何故知りたい?"` と少々意地の悪い答えを返す。 - -`makeMap` メソッドが返す可変マップは `SynchronizedMap` トレイトをミックスインするため、複数のスレッドから同時に使うことができる。マップへのそれぞれのアクセスは同期化される。以下は、インタープリタ中で一つのスレッドから使用した例だ: - - scala> val capital = MapMaker.makeMap - capital: scala.collection.mutable.Map[String,String] = Map() - scala> capital ++ List("US" -> "Washington", - "France" -> "Paris", "Japan" -> "Tokyo") - res0: scala.collection.mutable.Map[String,String] = - Map(France -> Paris, US -> Washington, Japan -> Tokyo) - scala> capital("Japan") - res1: String = Tokyo - scala> capital("New Zealand") - res2: String = Why do you want to know? - scala> capital += ("New Zealand" -> "Wellington") - scala> capital("New Zealand") - res3: String = Wellington - -同期集合も同期マップと同じ要領で作成することができる。例えば、このように `SynchronizedSet` トレイトをミックスインすることで同期 `HashSet` を作ることができる: - - import scala.collection.mutable - val synchroSet = - new mutable.HashSet[Int] with - mutable.SynchronizedSet[Int] - -最後に、同期コレクションを使うことを考えているならば、`java.util.concurrent` の並行コレクションを使うことも考慮した方がいいだろう。 diff --git a/ja/overviews/collections/migrating-from-scala-27.md b/ja/overviews/collections/migrating-from-scala-27.md deleted file mode 100644 index 39233df353..0000000000 --- a/ja/overviews/collections/migrating-from-scala-27.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -layout: overview-large -title: Scala 2.7 からの移行 - -discourse: false - -partof: collections -num: 18 -outof: 18 -language: ja ---- - -既存の Scala アプリケーションの新しいコレクションへの移植はほぼ自動的であるはずだ。問題となり得ることはいくつかしかない。 - -一般的論として、Scala 2.7 コレクションの古い機能はそのまま残っているはずだ。機能の中には廃止予定となったものもあり、それは今後のリリースで撤廃されるということだ。Scala 2.8 でそのような機能を使ったコードをコンパイルすると**廃止予定警告** (deprecation warning) が発生する。その意味や性能特性を変えて 2.8 に残った演算もあり、その場合は廃止予定にするのは無理だった。このような場合は 2.8 でコンパイルすると**移行警告** (migration warning) が出される。コードをどう変えればいいのかも提案してくれる完全な廃止予定警告と移行警告を得るには、`-deprecation` と `-Xmigration` フラグを `scalac` に渡す (`-Xmigration` は `X` で始まるため、拡張オプションであることに注意)。同じオプションを `scala` REPL に渡すことで対話セッション上で警告を得ることができる。具体例としては: - - >scala -deprecation -Xmigration - Welcome to Scala version 2.8.0.final - Type in expressions to have them evaluated. - Type :help for more information. - scala> val xs = List((1, 2), (3, 4)) - xs: List[(Int, Int)] = List((1,2), (3,4)) - scala> List.unzip(xs) - :7: warning: method unzip in object List is deprecated: use xs.unzip instead of List.unzip(xs) - List.unzip(xs) - ^ - res0: (List[Int], List[Int]) = (List(1, 3),List(2, 4)) - scala> xs.unzip - res1: (List[Int], List[Int]) = (List(1, 3),List(2, 4)) - scala> val m = xs.toMap - m: scala.collection.immutable.Map[Int,Int] = Map((1,2), (3,4)) - scala> m.keys - :8: warning: method keys in trait MapLike has changed semantics: - As of 2.8, keys returns Iterable[A] rather than Iterator[A]. - m.keys - ^ - res2: Iterable[Int] = Set(1, 3) - -旧ライブラリより完全に置き換えられ、廃止予定警告を出すのが無理だったものが 2つある。 - -1. 以前の `scala.collection.jcl` パッケージは撤廃された。 このパッケージは Scala 上で Java コレクションライブラリの設計を真似しようとしたが、それは多くの対称性を壊してしまった。Java コレクションが欲しい人の多くは `jcl` を飛ばして `java.util` を直接使用していた。Scala 2.8 は、`jcl` パッケージの代わりに、[`JavaConversions`](conversions-between-java-and-scala-collections.html) オブジェクトにて両方のライブラリ間の自動変換機構を提供する。 -2. 投射 (projection) は一般化され、きれいにされ、現在はビューとして提供される。投射はほとんど使われていなかったようなので、この変更に影響を受けるコードは少ないはずだ。 - -よって、`jcl` か投射を使っている場合は多少コードの書き換えが必要になるかもしれない。 diff --git a/ja/overviews/collections/overview.md b/ja/overviews/collections/overview.md deleted file mode 100644 index 7293bc3f80..0000000000 --- a/ja/overviews/collections/overview.md +++ /dev/null @@ -1,101 +0,0 @@ ---- -layout: overview-large -title: 可変コレクションおよび不変コレクション - -discourse: false - -partof: collections -num: 2 -language: ja ---- - -Scala のコレクションは、体系的に可変および不変コレクションを区別している。**可変** (mutable) コレクションは上書きしたり拡張することができる。これは副作用としてコレクションの要素を変更、追加、または削除することができることを意味する。一方、**不変** (immutable) コレクションは変わることが無い。追加、削除、または更新を模倣した演算は提供されるが、全ての場合において演算は新しいコレクションを返し、古いコレクションは変わることがない。 - -コレクションクラスの全ては `scala.collection` パッケージもしくは `mutable`、`immutable`、`generic` のどれかのサブパッケージに定義されている。クライアントコードに必要なコレクションのクラスのほどんどには可変性に関して異なる特性を持つ 3つの形態が定義されおり、ぞれぞれ `scala.collection`、`scala.collection.immutable`、か `scala.collection.mutable` のパッケージに存在する。 - -`scala.collection.immutable` パッケージのコレクションは、誰にとっても不変であることが保証されている。 -そのようなコレクションは作成後には一切変更されることがない。したがって、異なる時点で何回同じコレクションの値にアクセスしても常に同じ要素を持つコレクションが得られることに依存できる。 - -`scala.collection.mutable` パッケージのコレクションは、コレクションを上書き変更する演算がある。 -だから可変コレクションを扱うということは、どのコードが、何時どのコレクションを変更したのかということを理解する必要があることを意味する。 - -`scala.collection` パッケージのコレクションは、可変か不変かのどちらでもありうる。例えば [`collection.IndexedSeq[T]`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/IndexedSeq.html) -は、[`collection.immutable.IndexedSeq[T]`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/IndexedSeq.html) と [`collection.mutable.IndexedSeq[T]`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/IndexedSeq.html) 両方の親クラスだ。一般的に、`scala.collection`パッケージの基底コレクションは不変コレクションと同じインターフェイスを定義し、`scala.collection.mutable` パッケージ内の可変コレクションは、副作用を伴う変更演算を不変インターフェイスに加える。 - -基底コレクションと不変コレクションの違いは、不変なコレクションのクライアントは、他の誰もコレクションを変更しないという保証があるのに対し、基底コレクションのクライアントは自分ではコレクションを変更しなかったという約束しかできない。たとえ静的な型がコレクションを変更するような演算を提供していなくても、実行時の型は他のクライアントが手を加えることができる可変コレクションである可能性がある。 - -デフォルトでは Scala は常に不変コレクションを選ぶ。たとえば、`scala` パッケージのデフォルトのバインディングにより、なんの接頭辞や import もなくただ `Set` と書くと不変な集合 (set) が返ってき、`Iterable` と書くと不変で反復可能 (iterable)なコレクションが返ってくる。可変なデフォルト実装を取得するには、`collection.mutable.Set` -または `collection.mutable.Iterable` と明示的に記述する必要がある。 - -可変と不変の両方のバージョンのコレクションを使用する場合に便利な慣例は `collection.mutable` パッケージだけをインポートすることだ。 - - import scala.collection.mutable - -これにより、接頭辞なしの `Set` は不変なコレクションを参照するのに対し、`mutable.Set` は可変版を参照する。 - -コレクション階層内の最後のパッケージは `collection.generic` だ。 -このパッケージには、コレクションを実装するための基本的なパーツが含まれている。 -コレクションクラスがいくつかの演算を `generic` 内のクラスに委譲することはよくあるが、 フレームワークのユーザーが `generic` 内のクラスが必要になることは普通はありえない。 - -利便性と後方互換性のために、いつくかの重要な型は `scala` パッケージ内に別名を定義してあるため、インポート無しで単純な名前でコレクションを使うことができる。[`List`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/List.html) 型が良い例で、以下の名前でもアクセスすることができる - - scala.collection.immutable.List // 定義元 - scala.List // scala パッケージのエイリアス経由 - List // scala._ パッケージは - // 常に自動的にインポートされるため - -エイリアスされているその他の型は次のとおり: -[`Traversable`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Traversable.html)、[`Iterable`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Iterable.html)、[`Seq`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Seq.html)、[`IndexedSeq`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/IndexedSeq.html)、[`Iterator`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Iterator.html)、[`Stream`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Stream.html)、[`Vector`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Vector.html)、[`StringBuilder`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/StringBuilder.html)、[`Range`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Range.html)。 - -次の図は `scala.collection` パッケージ内の全てのコレクションを示す。 -これらはすべて、高レベルの抽象クラスやトレイトで一般に可変と不変の両方の実装を持っている。 - -[]({{ site.baseurl }}/resources/images/collections.png) - -次の図は `scala.collection.immutable` パッケージ内の全てのコレクションを示す。 - -[]({{ site.baseurl }}/resources/images/collections.immutable.png) - -そして、次の図は `scala.collection.mutable` パッケージ内の全てのコレクションを示す。 - -[]({{ site.baseurl }}/resources/images/collections.mutable.png) - -(以上三つ全ての図は decodified.com の Matthias によって生成された。) - -## コレクションAPIの概要 - -最も重要なコレクションクラスは上の図に示されている。 -これらの全てのクラスに共通な部分が沢山ある。 -例えば、全てのコレクションは、クラス名を書いた後で要素を書くという統一された構文で作成することができる: - - Traversable(1, 2, 3) - Iterable("x", "y", "z") - Map("x" -> 24, "y" -> 25, "z" -> 26) - Set(Color.red, Color.green, Color.blue) - SortedSet("hello", "world") - Buffer(x, y, z) - IndexedSeq(1.0, 2.0) - LinearSeq(a, b, c) - -特定のコレクションの実装にもこの原則が適用される: - - List(1, 2, 3) - HashMap("x" -> 24, "y" -> 25, "z" -> 26) - -これらのコレクションは、`toString` を呼び出すと上の表記方法で表示される。 - -すべてのコレクションが `Traversable` によって提供される API をサポートするが、理にかなうところでは型を特殊化している。 -たとえば、`Traversable` クラスの `map` メソッドは別の `Traversable` を戻り値として返すが、結果の型はサブクラスでオーバーライドされる。 -たとえば、`List` が `map` を呼び出しても再び `List` が返ってき、`Set` が `map` を呼び出すと `Set` が返ってくる、といういう具合だ。 - - scala> List(1, 2, 3) map (_ + 1) - res0: List[Int] = List(2, 3, 4) - scala> Set(1, 2, 3) map (_ * 2) - res0: Set[Int] = Set(2, 4, 6) - -コレクションライブラリ中のあらゆる所で実装されているこの振る舞いは**戻り値同型の原則** と呼ばれる。 - -コレクションの階層のクラスのほとんどは基底、不変、可変の3種類とも存在する。 -唯一の例外は、可変コレクションにのみ存在する `Buffer` トレイトだ。 - -これより、これらのクラスを一つづつ見ていく。 diff --git a/ja/overviews/collections/performance-characteristics.md b/ja/overviews/collections/performance-characteristics.md deleted file mode 100644 index 4f53a1be61..0000000000 --- a/ja/overviews/collections/performance-characteristics.md +++ /dev/null @@ -1,84 +0,0 @@ ---- -layout: overview-large -title: 性能特性 - -discourse: false - -partof: collections -num: 12 -language: ja ---- - -これまでの説明で異なるコレクションが異なる性能特性 (performance characteristics) を持つことが分かった。性能特性はコレクション型を比較する第一の基準としてよく使われる。以下の 2つの表にコレクションの主な演算の性能特性をまとめた。 - -列型の性能特性: - -| | head | tail | apply | 更新 | 先頭に追加 | 最後に追加 | 挿入 | -| -------- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | -| **不変** | | | | | | | | -| `List` | 定数 | 定数 | 線形 | 線形 | 定数 | 線形 | - | -| `Stream` | 定数 | 定数 | 線形 | 線形 | 定数 | 線形 | - | -| `Vector` |実質定数|実質定数|実質定数|実質定数| 実質定数 | 実質定数 | - | -| `Stack` | 定数 | 定数 | 線形 | 線形 | 定数 | 線形 | 線形 | -| `Queue` |ならし定数|ならし定数|線形| 線形 | 線形 | 定数 | - | -| `Range` | 定数 | 定数 | 定数 | - | - | - | - | -| `String` | 定数 | 線形 | 定数 | 線形 | 線形 | 線形 | - | -| **可変** | | | | | | | | -| `ArrayBuffer` | 定数 | 線形 | 定数 | 定数 | 線形 | ならし定数 | 線形 | -| `ListBuffer` | 定数 | 線形 | 線形 | 線形 | 定数 | 定数 | 線形 | -|`StringBuilder`| 定数 | 線形 | 定数 | 定数 | 線形 | ならし定数 | 線形 | -| `MutableList` | 定数 | 線形 | 線形 | 線形 | 定数 | 定数 | 線形 | -| `Queue` | 定数 | 線形 | 線形 | 線形 | 定数 | 定数 | 線形 | -| `ArraySeq` | 定数 | 線形 | 定数 | 定数 | - | - | - | -| `Stack` | 定数 | 線形 | 線形 | 線形 | 定数 | 線形 | 線形 | -| `ArrayStack` | 定数 | 線形 | 定数 | 定数 | ならし定数| 線形 | 線形 | -| `Array` | 定数 | 線形 | 定数 | 定数 | - | - | - | - -集合とマップ型の性能特性: - -| | 検索 | 追加 | 削除 | 最小 | -| -------- | ---- | ---- | ---- | ---- | -| **不変** | | | | | -| `HashSet`/`HashMap`| 実質定数| 実質定数| 実質定数 | 線形 | -| `TreeSet`/`TreeMap`| Log | Log | Log | Log | -| `BitSet` | 定数 | 線形 | 線形 | 実質定数1| -| `ListMap` | 線形 | 線形 | 線形 | 線形 | -| **可変** | | | | | -| `HashSet`/`HashMap`|実質定数 |実質定数|実質定数| 線形 | -| `WeakHashMap` |実質定数 |実質定数|実質定数| 線形 | -| `BitSet` | 定数 |ならし定数| 定数 |実質定数1| -| `TreeSet` | Log | Log | Log | Log | - -脚注: 1 ビットが密にパックされていることを前提にしている。 - -表の値を以下に説明する: - -| | | -| --- | ---- | -| **定数** | 演算は (高速な) 定数時間で完了する。| -| **実質定数** | 演算は実質定数時間で完了するが、ベクトルの最大長やハッシュキーの分布など何らかの前提に依存する。| -| **ならし定数** | 演算は「ならし定数時間」で完了する。演算の呼び出しの中には定数時間よりも長くかかるものもあるが、多くの演算の実行時間の平均を取った場合定数時間となる。 | -| **Log** | 演算はコレクションのサイズの対数に比例した時間で完了する。 | -| **線形** | 演算は線形時間、つまりコレクションのサイズに比例した時間で完了する。 | -| **-** | 演算はサポートされていない。 | - -最初の表は、不変と可変両方の列型の以下の演算の性能特性をまとめる: - -| | | -| --- | ---- | -| **head** | 列の最初の要素を選択する。 | -| **tail** | 最初の要素以外の全ての要素から成る新たな列を生成する。 | -| **apply** | 添字によるアクセス。 | -| **更新** | 不変列のときは (`updated` による) 関数型の更新、可変列のときは (`update` による) 副作用としての上書き更新。 | -| **先頭に追加**| 列の先頭への要素の追加。不変列のときは新たな列を生成する。可変列のときは現在の列を上書きする。 | -| **最後に追加** | 列の最後に要素を追加する。不変列のときは新たな列を生成する。可変列のときは現在の列を上書きする。 | -| **挿入** | 要素を列の任意の位置に挿入する。この演算は可変列にのみサポートされている。 | - -次の表は、不変と可変両方の集合とマップの以下の演算の性能特性をまとめる: - -| | | -| --- | ---- | -| **検索** | 要素が集合に含まれるかを調べるか、マップからキーに関連付けられた値を選択する。 | -| **追加** | 集合に新たな要素を追加するか、マップにキー/値ペアを追加する。 | -| **削除** | 集合から要素を削除するか、マップからキーを削除する。 | -| **最小** | 集合の最小要素かマップの最小キー。 | diff --git a/ja/overviews/collections/seqs.md b/ja/overviews/collections/seqs.md deleted file mode 100644 index 3e6a97e149..0000000000 --- a/ja/overviews/collections/seqs.md +++ /dev/null @@ -1,111 +0,0 @@ ---- -layout: overview-large -title: 列トレイト Seq、IndexedSeq、および LinearSeq - -discourse: false - -partof: collections -num: 5 -language: ja ---- - -列 ([`Seq`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Seq.html)) トレイトは、長さ (`length`) があり、それぞれの要素に `0` から数えられた固定された添字 (index) がある `Iterable` の一種だ。 - -以下の表にまとめられた列の演算は以下のカテゴリーに分けることができる: - -* **添字と長さの演算** `apply`、 `isDefinedAt`、 `length`、 `indices`、および `lengthCompare`。`Seq` では `apply` メソッドは添字の意味で使われるため、`Seq[T]`型の列は `Int` を引数 (添字) としてをとり、`T`型の要素を返す部分関数だ。つまり、`Seq[T]` は `PartialFunction[Int, T]` を継承する。列内の要素はゼロから列の長さ (`length`) − 1 まで添字付けられている。列の `length` メソッドは一般コレクションにおける `size` メソッドの別名だ。`lengthCompare` メソッドは、たとえどちらかの列が無限の長さを持っていても、二つの列の長さを比較することができる。 -* **添字検索演算**である `indexOf`、 `lastIndexOf`、 `indexofSlice`、 `lastIndexOfSlice`、 `indexWhere`、 `lastIndexWhere`、 `segmentLength`、 `prefixLength` は、渡された値もしくは条件関数に合致する要素の添字を返す。 -* **加算**である `+:`、`:+`、`padTo` は、列の先頭か最後に要素を追加した新しい列を返す。 -* **更新演算**である `updated`、`patch` は、元の列に何らかの要素を上書きした列を返す。 -* **並べ替え演算**である `sorted`、`sortWith`、`sortBy` は、列内の要素を何らかの基準に基づいて並べ替える。 -* **逆転演算**である `reverse`、`reverseIterator`、`reverseMap` は、列内の要素を逆順に返すか処理する。 -* **比較演算**である `startsWith`、 `endsWith`、 `contains`、 `containsSlice`、 `corresponds` は、二つの列を関連付けるか、列の中から要素を検索する。 -* **集合演算**である `intersect`、 `diff`、 `union`、 `distinct` は、二つの列間で集合演算のようなものを行うか、列内の要素の重複を削除する。 - -列が可変の場合は、追加で副作用のある `update` メソッドを提供し、列内の要素を上書きすることができる。 -Scala の他の構文の例にならって、`seq(idx) = elem` は `seq.update(idx, elem)` の略記法であるため、`update` によって便利な代入構文がただで手に入る。`update` と `updated` の違いに注意してほしい。 `update` は列内の要素を上書きし、可変列でのみ使用可能だ。 -`updated` は全ての列で使用可能であり、元の列は変更せずに常に新しい列を返す。 - -### Seq トレイトの演算 - -| 使用例 | 振る舞い | -| ------ | ------ | -| **添字と長さの演算:** | | -| `xs(i)` |(展開した場合、`xs apply i`)。`xs` の添字 `i` の位置の要素。| -| `xs isDefinedAt i` |`xs.indices` に `i` が含まれているか調べる。 | -| `xs.length` |列の長さ (`size` と同様)。 | -| `xs.lengthCompare ys` |`xs` が `ys` より短い場合は `-1`、長い場合は `+1`、同じ長さの場合は `0` を返す。いずれかの列が無限でも正常に作動する。| -| `xs.indices` |0 から `xs.length - 1` までの `xs` の添字の範囲。 | -| **添字検索演算:** | | -| `xs indexOf x` |`xs`内で `x` と等しい最初の要素の添字 (数種の別形がある)。| -| `xs lastIndexOf x` |`xs`内で `x` と等しい最後の要素の添字 (数種の別形がある)。| -| `xs indexOfSlice ys` |`xs` の添字で、それと後続の要素が、列 `ys` と同値になる最初のもの。| -| `xs lastIndexOfSlice ys` |`xs` の添字で、それと後続の要素が、列 `ys` と同値になる最後のもの。| -| `xs indexWhere p` |`xs`内で条件関数 `p` を満たす最初の要素の添字 (数種の別形がある)。| -| `xs segmentLength (p, i)`|全ての要素が途切れなく条件関数 `p` を満たし、`xs(i)` から始まる、最長の `xs` の切片の長さ。| -| `xs prefixLength p` |全ての要素が途切れなく条件関数 `p` を満たす、最長の `xs` の先頭切片の長さ。| -| **加算:** | | -| `x +: xs` |`xs` の要素の先頭に `x` を追加した、新しい列。 | -| `xs :+ x` |`xs` の要素の最後に `x` を追加した、新しい列。 | -| `xs padTo (len, x)` |`xs` の長さが `len` になるまで最後に値 `x` を追加していった列。| -| **更新演算:** | | -| `xs patch (i, ys, r)` |`xs`内の、`i` から始まる `r`個の要素をパッチ `ys`内の要素と置換した列。| -| `xs updated (i, x)` |`xs`の添字 `i` の要素を `x` に置換したコピー。 | -| `xs(i) = x` |(展開した場合、`xs.update(i, x)`、ただし可変列でのみ使用可能)。`xs`の添字 `i` の位置の要素を `x` と上書きする。| -| **並べ替え演算:** | | -| `xs.sorted` |`xs` の要素型の標準的な順序付けを用いて、`xs` の要素を並べ替えることによって得られる新しい列。| -| `xs sortWith lt` |比較関数 `lt` 用いて `xs` の要素を並べ替えることによって得られる新しい列。| -| `xs sortBy f` |`xs` の要素を並べ替えることによって得られる新しい列。二つの要素の比較は、両者を関数 `f` に適用してその結果を比較することによって行われる。| -| **逆転演算:** | | -| `xs.reverse` |`xs`内の要素を逆順にした列。 | -| `xs.reverseIterator` |`xs`内の全ての要素を逆順に返すイテレータ。 | -| `xs reverseMap f` |`xs`内の要素に逆順に関数 `f` を `map` して得られる列。| -| **比較演算:** | | -| `xs startsWith ys` |`xs` が列 `ys` から始まるかを調べる (数種の別形がある)。| -| `xs endsWith ys` |`xs` が列 `ys` で終わるかを調べる (数種の別形がある)。| -| `xs contains x` |`xs` が `x` と等しい要素を含むかを調べる。 | -| `xs containsSlice ys` |`xs` が `ys` と等しい連続した切片を含むかを調べる。 | -| `(xs corresponds ys)(p)` |`xs` と `ys` の対応した要素が、二項条件関数の `p` を満たすかを調べる。| -| **集合演算:** | | -| `xs intersect ys` |列 `xs` と `ys` の積集合で、`xs` における要素の順序を保ったもの。| -| `xs diff ys` |列 `xs` と `ys` の差集合で、`xs` における要素の順序を保ったもの。| -| `xs union ys` |和集合; `xs ++ ys` に同じ| -| `xs.distinct` |`xs` の部分列で要素の重複を一切含まないもの。 | - -[`Seq`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Seq.html) トレイトには [`LinearSeq`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/IndexedSeq.html) と [`IndexedSeq`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/IndexedSeq.html) -という二つの子トレイトがある。 -これらは新しい演算を定義しないが、それぞれ異なった性能特性をもつ。 -線形列 (linear sequence) は効率的な `head` と `tail` 演算を持ち、一方添字付き列 (indexed sequence) は効率的な`apply`、`length`、および (可変の場合) `update` 演算を持つ。 -よく使われる線形列の例に `scala.collection.immutable.List` と `scala.collection.immutable.Stream` がある。よく使われる添字付き列の例としては `scala.Array` と `scala.collection.mutable.ArrayBuffer` がある。 -`Vector` は添字付き列と線形列の間の興味深い折衷案だ。 -事実上定数時間のオーバーヘッドで添字アクセスと線形アクセスを提供するからだ。 -そのため、ベクトルは添字アクセスと線形アクセスの両方を混合して使用してるアクセスパターンにおける良い基盤となる。 -ベクトルに関しては、また[後ほど詳しくみていく](concrete-immutable-collection-classes.html#vectors)。 - -### バッファ ### - -可変列に分類されるものの中で重要なものに `Buffer` がある。バッファは既存の要素を上書きできるだけでなく、要素を挿入したり、削除したり、効率的にバッファの最後に新しい要素を追加したりできる。バッファがサポートする新しいメソッドの中で主要なものは、要素を最後に追加する `+=` と `++=`、先頭に追加する `+=:` と `++=:`、要素を挿入する `insert` と `insertAll`、そして要素を削除する `remove` と `-=` だ。以下の表にこれらの演算をまとめた。 - -よく使われるバッファの実装に `ListBuffer` と `ArrayBuffer` がある。名前が示すとおり、`ListBuffer` は `List` に支えられており、要素を効率的に `List` に変換できる。一方、`ArrayBuffer` は配列に支えられており、これも素早く配列に変換できる。 - -#### Buffer クラスの演算 #### - -| 使用例 | 振る舞い| -| ------ | ------ | -| **加算:** | | -| `buf += x` |バッファの最後に要素 `x` を追加し、`buf` 自身を戻り値として返す。| -| `buf += (x, y, z)` |渡された要素をバッファの最後に追加する。| -| `buf ++= xs` |`xs`内の全ての要素をバッファの最後に追加する。| -| `x +=: buf` |バッファの先頭に要素 `x` を追加する。| -| `xs ++=: buf` |`xs`内の全ての要素をバッファの先頭に追加する。| -| `buf insert (i, x)` |バッファの添字 `i` の位置に要素 `x` を挿入する。| -| `buf insertAll (i, xs)` |`xs`内の全ての要素をバッファの添字 `i` の位置に挿入する。| -| **減算:** | | -| `buf -= x` |バッファから要素 `x` を削除する。| -| `buf remove i` |バッファの添字 `i` の位置の要素を削除する。| -| `buf remove (i, n)` |バッファの添字 `i` の位置から始まる `n`個の要素を削除する。| -| `buf trimStart n` |バッファの先頭の要素 `n`個を削除する。| -| `buf trimEnd n` |バッファの最後の要素 `n`個を削除する。| -| `buf.clear()` |バッファの全ての要素を削除する。| -| **クローン演算:** | | -| `buf.clone` |`buf` と同じ要素を持った新しいバッファ。| diff --git a/ja/overviews/collections/sets.md b/ja/overviews/collections/sets.md deleted file mode 100644 index cc25abe2a4..0000000000 --- a/ja/overviews/collections/sets.md +++ /dev/null @@ -1,150 +0,0 @@ ---- -layout: overview-large -title: 集合 - -discourse: false - -partof: collections -num: 6 -language: ja ---- - -集合 ([`Set`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Set.html)) は要素の重複の無い `Iterable` だ。集合一般に定義される演算は次の表にまとめてあり、可変集合に関してはその次の表も見てほしい。これらの演算は以下のカテゴリーに分類される: - -* **条件演算**である `contains`、`apply`、`subsetOf`。`contains` メソッドは集合が任意の要素を含むかを調べる。集合での `apply` メソッドは `contains` と同じであるため、`set(elem)` は `set contains elem` と同じだ。これは集合が要素を含んでいれば `true` を返す関数として使えることを意味する。 - -例えば、 - - - val fruit = Set("apple", "orange", "peach", "banana") - fruit: scala.collection.immutable.Set[java.lang.String] = - Set(apple, orange, peach, banana) - scala> fruit("peach") - res0: Boolean = true - scala> fruit("potato") - res1: Boolean = false - - -* **加算**である `+` と `++` は、単一もしくは複数の要素を集合に追加し、新たな集合を返す。 -* **減算**である `-` と `--` は、単一もしくは複数の要素を集合から削除し、新たな集合を返す。 -* **集合演算**である和集合、積集合、および差集合。これらの演算には文字形とシンボル形の二つの形がある。文字バージョンは `intersect`、`union`、および `diff` で、シンボルバージョンは `&`、`|`、と `&~` だ。`Set` が `Traversable` から継承する `++` は `union` と `|` の更なる別名だと考えることができるが、`++` は `Traversable` の引数を取るが、`union` と `|` は集合を取る。 - -### Set トレイトの演算 ### - -| 使用例 | 振る舞い| -| ------ | ------ | -| **条件演算:** | | -| `xs contains x` |`xs` が `x` を含むかを調べる。| -| `xs(x)` |`xs contains x` に同じ。 | -| `xs subsetOf ys` |`xs` が `ys` の部分集合であるかを調べる。| -| **加算:** | | -| `xs + x` |`xs`内の全ての要素および `x` を含んだ集合。| -| `xs + (x, y, z)` |`xs`内の全ての要素および渡された要素を含んだ集合。| -| `xs ++ ys` |`xs`内の全ての要素と `ys`内の全ての要素を含んだ集合。| -| **減算:** | | -| `xs - x` |`x` を除き、`xs`内の全ての要素を含んだ集合。| -| `xs - (x, y, z)` |渡された要素を除き、`xs`内の全ての要素を含んだ集合。| -| `xs -- ys` |`ys`内の要素を除き、`xs`内の全ての要素を含んだ集合。| -| `xs.empty` |`xs` と同じクラスの空集合。| -| **集合演算:** | | -| `xs & ys` |`xs` と `ys` の積集合。| -| `xs intersect ys` |`xs & ys` に同じ| -| `xs | ys` |`xs` と `ys` の和集合。| -| `xs union ys` |`xs | ys` に同じ| -| `xs &~ ys` |`xs` と `ys` の差集合。| -| `xs diff ys` |`xs &~ ys` に同じ| - -可変集合は、この表にまとめてあるとおり、加算、減算、更新演算などの新たなメソッドを追加する。 - -### mutable.Set トレイトの演算 ### - -| 使用例 | 振る舞い| -| ------ | ------ | -| **加算:** | | -| `xs += x` |集合 `xs` に副作用として要素 `x` を加え、`xs`自身を返す。| -| `xs += (x, y, z)` |集合 `xs` に副作用として渡された要素を加え、`xs`自身を返す。| -| `xs ++= ys` |集合 `xs` に副作用として `ys`内の全ての要素を加え、`xs`自身を返す。| -| `xs add x` |集合 `xs` に要素 `x` を加え、以前に集合に含まれていなければ `true` を返し、既に含まれていれば `false` を返す。| -| **減算:** | | -| `xs -= x` |集合 `xs` から副作用として要素 `x` を削除して、`xs`自身を返す。| -| `xs -= (x, y, z)` |集合 `xs` から副作用として渡された要素を削除して、`xs`自身を返す。| -| `xs --= ys` |集合 `xs` から副作用として `ys`内の全ての要素を削除して、`xs`自身を返す。| -| `xs remove x` |集合 `xs` から要素 `x` を削除、以前に集合に含まれていれば `true` を返し、含まれていなければ `false` を返す。| -| `xs retain p` |`xs`内の要素で条件関数 `p` を満たすものだけを残す。| -| `xs.clear()` |`xs` から全ての要素を削除する。| -| **更新演算:** | | -| `xs(x) = b` |(展開した場合、`xs.update(x, b)`)。ブーリアン値の引数 `b` が `true` ならば `xs` に `x` を加え、それ以外なら `xs` から `x` を削除する。| -| **クローン演算:** | | -| `xs.clone` |`xs` と同じ要素を持つ新しい可変集合。| - -不変集合と同様に、可変集合も要素追加のための `+` と `++` 演算、および要素削除のための `-` と `--` 演算を提供する。しかし、これらは集合をコピーする必要があるため可変集合ではあまり使われることがない。可変集合はより効率的な `+=` と `-=` という更新方法を提供する。`s += elem` という演算は、集合 `s` に副作用として `elem` を集合に加え、変化した集合そのものを戻り値として返す。同様に、`s -= elem` は集合から `elem` を削除して、変化した集合を戻り値として返す。`+=` と `-=` の他にも、traversable やイテレータの全ての要素を追加または削除する一括演算である `++=` と `--=` がある。 - -メソッド名として `+=` や `-=` が選ばれていることによって、非常に似たコードが可変集合と不変集合のどちらでも動くことを意味する。不変集合 `s` を使った次の REPL のやりとりを見てほしい: - - scala> var s = Set(1, 2, 3) - s: scala.collection.immutable.Set[Int] = Set(1, 2, 3) - scala> s += 4 - scala> s -= 2 - scala> s - res2: scala.collection.immutable.Set[Int] = Set(1, 3, 4) - -ここでは `immutable.Set`型の `var` に対して `+=` と `-=` を使った。`s += 4` のようなステートメントは、`s = s + 4` の略だ。つまり、これは集合 `s` に対して追加メソッドの `+` を呼び出して、結果を変数`s` に代入しなおしてる。次に、可変集合でのやりとりを見てほしい。 - - scala> val s = collection.mutable.Set(1, 2, 3) - s: scala.collection.mutable.Set[Int] = Set(1, 2, 3) - scala> s += 4 - res3: s.type = Set(1, 4, 2, 3) - scala> s -= 2 - res4: s.type = Set(1, 4, 3) - -結果は前回のやりとりと非常に似通ったものになった: `Set(1, 2, 3)` から始めて、最後に `Set(1, 3, 4)` -を得た。ステートメントは前回と同じに見えるが、実際には違うことを行っている。`s` `+=` `4` は今度は可変集合 `s` の `+=` メソッドを呼び出し、その場で集合を上書きしているのだ。同様に、`s -= 2` は同じ集合の `-=` メソッドを呼び出している。 - -この2つのやりとりの比較から重要な原則を導き出せる。`val` に格納された可変コレクションと、`var` に格納された不変コレクションは、大抵の場合にお互いを置換できるということだ。これはコレクションに対して上書きで更新されたのか新たなコレクションが作成されたのかを第三者が観測できるような別名の参照がない限り成り立つ原則だ。 - -可変集合は `+=` と `-=` の別形として `add` と `remove` を提供する。違いは `add` と `remove` は集合に対して演算の効果があったかどうかを示す `Boolean` の戻り値を返すことだ。 - -現在の可変集合のデフォルトの実装では要素を格納するのにハッシュテーブルを使っている。不変集合のデフォルトの実装は集合の要素数に応じて方法を変えている。空集合はシングルトンで表される。サイズが4つまでの集合は全要素をフィールドとして持つオブジェクトとして表される。それを超えたサイズの不変集合は[ハッシュトライ](#hash-tries)として表される。 - -このような設計方針のため、(例えば 4以下の) 小さいサイズの集合を使う場合は、通常の場合、可変集合に比べて不変集合の方が、よりコンパクトで効率的だ。集合のサイズが小さいと思われる場合は、不変集合を試してみてはいかがだろうか。 - -集合のサブトレイトとして `SortedSet` と `BitSet` の2つがある。 - -### 整列済み集合 ### - -整列済み集合は ([`SortedSet`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/SortedSet.html)) -は (集合の作成時に指定された) 任意の順序で要素を (`iterator` や `foreach` を使って) 返す事ができる集合だ。[`SortedSet`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/SortedSet.html) クラスのデフォルトの表現は、左の子ツリー内の全ての要素が右の子ツリーの全ての要素よりも小さいという恒常条件を満たす順序付けされた二分木だ。これにより、通りがけ順 (in-order) で探索するだけで、木の全ての要素を昇順に返すことができる。Scala の[`immutable.TreeSet`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/TreeSet.html) クラスは **赤黒木** を使ってこの恒常条件を実装している。また、この木構造は、**平衡木**であり、ルートから全て葉のまでの長さの違いは最大で1要素しかない。 - -空の [`TreeSet`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/TreeSet.html) を作成するには、まず順序付けを指定する: - - scala> val myOrdering = Ordering.fromLessThan[String](_ > _) - myOrdering: scala.math.Ordering[String] = ... - -次に、その順序付けの空の木集合を作成するには: - - scala> TreeSet.empty(myOrdering) - res1: scala.collection.immutable.TreeSet[String] = TreeSet() - -順序付けの引数を省略して、空集合の要素型を指定することもできる。その場合は、要素型のデフォルトの順序付けが使われる。 - - scala> TreeSet.empty[String] - res2: scala.collection.immutable.TreeSet[String] = TreeSet() - -(例えば連結やフィルターによって) 新たな木集合を作成した場合、それは元の集合と同じ順序付けを保つ。たとえば、 - - scala> res2 + ("one", "two", "three", "four") - res3: scala.collection.immutable.TreeSet[String] = TreeSet(four, one, three, two) - -整列済み集合は要素の範囲もサポートする。例えば、`range` メソッドは開始要素以上、終了要素未満の全ての要素を返す。また、`from` メソッドは開始要素以上の全ての要素を、集合の順序付けで返す。両方のメソッドの戻り値もまた整列済み集合だ。用例: - - scala> res3 range ("one", "two") - res4: scala.collection.immutable.TreeSet[String] = TreeSet(one, three) - scala> res3 from "three" - res5: scala.collection.immutable.TreeSet[String] = TreeSet(three, two) - - -### ビット集合 ### - -ビット集合 ([`BitSet`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/BitSet.html)) は非負整数の要素の集合で、何ワードかのパックされたビットにより実装されている。[`BitSet`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/BitSet.html) クラスは、内部で `Long` の配列を用いている。最初の `Long` は第0 〜 63 の要素を受け持ち、次のは第64 〜 127 の要素という具合だ。全ての `Long` の、それぞれの 64ビットは、対応する要素が集合に含まれる場合は 1 にセットされ、含まれない場合は 0 になる。このため、ビット集合のサイズは格納されている整数の最大値に依存する。`N` がその最大の整数値の場合、集合のサイズは `N/64` `Long` ワード、または `N/8` バイト、にステータス情報のためのバイトを追加したものだ。 - -このため、たくさんの小さい要素を含む場合、ビット集合は他の集合に比べてコンパクトである。ビット集合のもう一つの利点は `contains` を使った所属判定や、`+=` や `-=` を使った要素の追加や削除が非常に効率的であることだ。 diff --git a/ja/overviews/collections/strings.md b/ja/overviews/collections/strings.md deleted file mode 100644 index 55f6072e7b..0000000000 --- a/ja/overviews/collections/strings.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -layout: overview-large -title: 文字列 - -discourse: false - -partof: collections -num: 11 -language: ja ---- - -配列と同様、文字列 (`String`) は直接には列ではないが、列に変換することができ、また、文字列は全ての列演算をサポートする。以下に文字列に対して呼び出すことができる演算の具体例を示す。 - - scala> val str = "hello" - str: java.lang.String = hello - scala> str.reverse - res6: String = olleh - scala> str.map(_.toUpper) - res7: String = HELLO - scala> str drop 3 - res8: String = lo - scala> str slice (1, 4) - res9: String = ell - scala> val s: Seq[Char] = str - s: Seq[Char] = WrappedString(h, e, l, l, o) - -これらの演算は二つの暗黙の変換により実現されている。例えば、上記の最後の行で文字列が `Seq` に変換されている所では優先度の低い `String` から `WrappedString` への変換が自動的に導入されている (`WrappedString` は -`immutable.IndexedSeq` の子クラスだ)。一方、`reverse`、`map`、`drop`、および `slice` メソッドの呼び出しでは優先度の高い `String` から `StringOps` への変換が自動的に導入されており、これは全ての不変列のメソッドを文字列に追加する。 diff --git a/ja/overviews/collections/trait-iterable.md b/ja/overviews/collections/trait-iterable.md deleted file mode 100644 index 1a8b6817d0..0000000000 --- a/ja/overviews/collections/trait-iterable.md +++ /dev/null @@ -1,73 +0,0 @@ ---- -layout: overview-large -title: Iterable トレイト - -discourse: false - -partof: collections -num: 4 -language: ja ---- - -反復可能 ([`Iterable`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Iterable.html) トレイトはコレクション階層の上から2番目に位置する。このトレイトの全メソッドは、コレクション内の要素を1つづつ返す抽象メソッド `iterator` に基づいている。`Iterable` では、`Traversable` トレイトの `foreach` メソッドも `iterator`に基づいて実装されている。以下が実際の実装だ: - - def foreach[U](f: Elem => U): Unit = { - val it = iterator - while (it.hasNext) f(it.next()) - } - -多くの `Iterable` のサブクラスは、より効率的な実装を提供するため、上の `foreach` の標準実装をオーバーライドしている。 `foreach` は `Traversable` の全ての演算の基となっているため、効率的であることが重要なのだ。 - -`Iterable` にはイテレータを返すもう2つのメソッドがある: `grouped` と `sliding` だ。これらのイテレータは単一の要素を返すのではなく、元のコレクションの部分列を返す。これらのメソッドに渡された引数がこの部分列の最大サイズとなる。`grouped` メソッドは要素を n 個づつの「かたまり」にして返すのに対し、 `sliding` は n 個の要素から成る「窓」をスライドさせて返す。この二つのメソッドの違いは REPL でのやりとりを見れば明らかになるはずだ。: - - scala> val xs = List(1, 2, 3, 4, 5) - xs: List[Int] = List(1, 2, 3, 4, 5) - scala> val git = xs grouped 3 - git: Iterator[List[Int]] = non-empty iterator - scala> git.next() - res3: List[Int] = List(1, 2, 3) - scala> git.next() - res4: List[Int] = List(4, 5) - scala> val sit = xs sliding 3 - sit: Iterator[List[Int]] = non-empty iterator - scala> sit.next() - res5: List[Int] = List(1, 2, 3) - scala> sit.next() - res6: List[Int] = List(2, 3, 4) - scala> sit.next() - res7: List[Int] = List(3, 4, 5) - -`Iterable` トレイトは、 `Traversable` からのメソッドの他に、イテレータがあることで効率的に実装することができる他のメソッドを追加する。それらのメソッドを以下の表にまとめる。 - -### Iterable トレイトの演算 - -| 使用例 | 振る舞い | -| ------ | ------ | -| **抽象メソッド:** | | -| `xs.iterator` |`xs`内の全ての要素を `foreach` が走査するのと同じ順序で返すイテレータ。| -| **他のイテレータ:**   | | -| `xs grouped size` |このコレクション内の要素を固定サイズの「かたまり」にして返すイテレータ。| -| `xs sliding size` |このコレクション内の要素を固定サイズの「窓」をスライドさせて返すイテレータ。| -| **サブコレクション取得演算:** | | -| `xs takeRight n` |`xs` の最後の `n` 個の要素から成るコレクション (順序が定義されていない場合は、任意の `n` 個の要素から成るコレクション)。| -| `xs dropRight n` |コレクションから `xs` `takeRight` `n` を除いた残りの部分。| -| **zip 演算:** | | -| `xs zip ys` |`xs` と `ys` のそれぞれから対応する要素をペアにした `Iterable`。| -| `xs zipAll (ys, x, y)` |`xs` と `ys` のそれぞれから対応する要素をペアにした `Iterable` で、もし片方が短い場合は `x` か `y` を使って長いほうに合わせる。| -| `xs.zipWithIndex` |`xs`内の要素とその添字をペアにした `Iterable`。| -| **比較演算:**    | | -| `xs sameElements ys` |`xs` と `ys` が同じ要素を同じ順序で格納しているかを調べる。| - -継承階層では `Iterable` 直下に [`Seq`](https://www.scala-lang.org/api/current/scala/collection/Seq.html)、[`Set`](https://www.scala-lang.org/api/current/scala/collection/Set.html)、[`Map`](https://www.scala-lang.org/api/current/scala/collection/Map.html``) という三つのトレイトがある。 -この三つのトレイトに共通することは `apply` メソッドと `isDefinedAt` メソッドを持ったトレイト [` -PartialFunction`](https://www.scala-lang.org/api/current/scala/PartialFunction.html) を実装しているということだ。 -しかし、`PartialFunction` の実装方法は三者三様である。 - -列は `apply` を位置的な添字として用いられており、要素は常に `0` -から数えられる。だから、`Seq(1, 2, 3)(1)` は `2` を返す。 -集合では `apply` は所属を調べるのに用いられる。 -例えば、`Set('a', 'b', 'c')('b')` は `true` を返し、`Set()('a')` は `false` を返す。 -最後に、マップでは `apply` は要素の選択に用いられている。 -例えば、`Map('a' -> 1, 'b' -> 10, 'c' -> 100)('b')` は `10` を返す。 - -次に、この3つのコレクションをより詳しく説明しよう。 diff --git a/ja/overviews/collections/trait-traversable.md b/ja/overviews/collections/trait-traversable.md deleted file mode 100644 index 619946c051..0000000000 --- a/ja/overviews/collections/trait-traversable.md +++ /dev/null @@ -1,118 +0,0 @@ ---- -layout: overview-large -title: Traversable トレイト - -discourse: false - -partof: collections -num: 3 -language: ja ---- - -走査可能 ([`Traversable`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Traversable.html))トレイトはコレクション階層の最上位に位置する。訳注: 木構造などでノードを一つづつ走査することを traverse と言う。また、-able で終わるトレイトは名詞としても使われるため、「走査可能なもの」という意味だ。 `Traversable` の抽象的な演算は `foreach` のみだ: - - def foreach[U](f: Elem => U) - -`Traverable` を実装するコレクションクラスは、このメソッドを定義するだけでいい。逆に言うと、その他全てのメソッドは `Traversable` から継承することができる。 - -`foreach` メソッドは、コレクション中の全ての要素を走査して、渡された演算 `f` を各々の要素に適用することを意図している。 -この演算の型は `Elem => U` であり、`Elem` はコレクションの要素の型で、`U` は任意の戻り値型だ。 `f` の呼び出しはそれに伴う副作用のためだけに行われ、`f` の戻り値の全ては `foreach` によって破棄される。 - -`Traversable` が定義する全ての具象メソッド (concrete method) を次の表に列挙した。これらのメソッドは次のカテゴリに分類される: - -* **加算**である `++` は、2つの `Traversable` を連結するか、あるイテレータが返す全ての要素を `Traversable` に追加する。 -* **map 演算**である`map`、`flatMap`、及び `collect` はコレクションの要素に何らかの関数を適用して新しいコレクションを生成する。 -* **変換演算**である `toArray`、`toList`、`toIterable`、`toSeq`、`toIndexedSeq`、`toStream`、`toSet`、`toMap` は `Traversable` なコレクションを別のより特定のものに変える。実行時のコレクション型が既に要求されているコレクション型と一致する場合、これらの全ての変換は引数をそのまま返す。例えば、リストに `toList` を適用した場合、リストそのものを返す。 -* **コピー演算** `copyToBuffer` と `copyToArray`。名前のとおり、これらの演算はコレクションの要素をバッファまたは配列にコピーする。 -* **サイズ演算** `isEmpty`、`nonEmpty`、`size`、および `hasDefiniteSize`。 `Traversable` なコレクションは有限または無限のサイズを取りうる。無限の `Traversable` コレクションの例としては自然数のストリームである `Stream.from(0)` がある。 -`hasDefiniteSize` メソッドはコレクションが無限である可能性があるかを示す。`hasDefiniteSize` が `true` を返す場合、コレクション確実に有限だ。`false` を返す場合、コレクションはまだ完全に展開されていないことを示し、それは無限か有限のどちらである可能性もある。 -* **要素取得演算** `head`、`last`、`headOption`、`lastOption`、および `find`。これらはコレクションの最初または最後の要素、または他の条件に一致する最初の要素を選択する。しかし、全てのコレクションにおいて「最初」と「最後」の意味が明確に定義されているわけではないことに注意してほしい。たとえば、ハッシュ集合はハッシュキーの並びで要素を格納するかもしれないが、ハッシュキーは実行するたびに変わる可能性がある。その場合、ハッシュ集合の「最初」の要素はプログラムを実行するたびに異なるかもしれない。 -あるコレクションから常に同じ順序で要素を得られる場合、そのコレクションは**順序付け** (ordered) されているという。 -ほとんどのコレクションは順序付けされているが、(ハッシュ集合など)いくつかののコレクションは順序付けされていない ― -順序付けを省くことで多少効率が上がるのだ。順序付けは再現性のあるテストを書くのに不可欠であり、デバッグの役に立つ。 -そのため Scala のコレクションは、全てのコレクション型に対して順序付けされた選択肢を用意してある。 -例えば、`HashSet` に代わる順次付けされたものは `LinkedHashSet` だ。 -* **サブコレクション取得演算** `tail`、`init`、`slice`、`take`、`drop`、`takeWhile`、`dropWhile`、`filter`、`filterNot`、`withFilter`。 -これら全ての演算は添字の範囲や何らかの条件関数によって識別されたサブコレクションを返す。 -* **分割演算**である `splitAt`、`span`、`partition`、`groupBy` の全てはコレクションの要素をいくつかのサブコレクションに分割する。 -* **要素条件演算**である`exists`、`forall`、`count` は与えられた条件関数を使ってコレクションをテストする。 -* **fold 演算**である `foldLeft`、`foldRight`、`/:`、`:\`、`reduceLeft`、`reduceRight` は次々と二項演算を隣接する要素に適用していく。 -* **特定 fold 演算**である `sum`、`product`、`min`、`max` は特定の型(numeric か comparable)のコレクションでのみ動作する。 -* **文字列演算**である `mkString`、`addString`、`stringPrefix` はコレクションを文字列に変換する方法を提供する。 -* **ビュー演算**はオーバーロードされた二つの `view` メソッドによって構成される。 - ビューは遅延評価されたコレクションだ。ビューについての詳細は[後ほど](views.html)。 - -### Traversableトレイトの演算 - -| 使用例 | 振る舞い | -| ------ | ------ | -| **抽象メソッド:** | | -| `xs foreach f` |`xs` 内の全ての要素に対して関数 `f` を実行する。 | -| **加算:** | | -| `xs ++ ys` |`xs` と `ys` の両方の要素から成るコレクション。 `ys` は [`TraversableOnce`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/TraversableOnce.html) なコレクション、つまり [`Traversable`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Traversable.html) または [`Iterator`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Iterator.html) だ。| -| **map 演算:** | | -| `xs map f` |`xs` 内の全ての要素に関数 `f` を適用することによって得られるコレクション。| -| `xs flatMap f` |`xs` 内の全ての要素に対してコレクション値を返す関数 `f` を適用し、その結果を連結したコレクション。| -| `xs collect f` |`xs` 内の全ての要素に対して部分関数 `f` が定義されている場合のみ適応し、その結果を集めたコレクション。| -| **変換演算:** | | -| `xs.toArray` |コレクションを配列に変換する。 | -| `xs.toList` |コレクションをリストに変換する。 | -| `xs.toIterable` |コレクションを `Iterable` に変換する。 | -| `xs.toSeq` |コレクションを列に変換する。 | -| `xs.toIndexedSeq` |コレクションを添字付き列に変換する。 | -| `xs.toStream` |コレクションを遅延評価されたストリームに変換する。 | -| `xs.toSet` |コレクションを集合に変換する。 | -| `xs.toMap` |キー/値のペアを持つコレクションをマップに変換する。コレクションが要素としてのペアを持たない場合、この演算を呼び出すと静的型エラーがおこる。| -| **コピー演算:** | | -| `xs copyToBuffer buf` |コレクション内の全ての要素をバッファ `buf` にコピーする。| -| `xs copyToArray(arr, s, n)`|最大 `n` 個のコレクションの要素を配列 `arr` の添字 `s` より始まる位置にコピーする。最後の2つの引数は省略可能だ。| -| **サイズ演算:** | | -| `xs.isEmpty` |コレクションが空であるかどうかを調べる。 | -| `xs.nonEmpty` |コレクションに要素が含まれているかを調べる。 | -| `xs.size` |コレクション内の要素の数。 | -| `xs.hasDefiniteSize` |`xs` が有限のサイズであることが明らかな場合 true を返す。| -| **要素取得演算:** | | -| `xs.head` |コレクションの最初の要素 (順序が定義されていない場合は、任意の要素)。| -| `xs.headOption` |`xs` の最初の要素のオプション値、または `xs` が空の場合 `None`。| -| `xs.last` |コレクションの最後の要素 (順序が定義されていない場合は、任意の要素)。| -| `xs.lastOption` |`xs` の最後の要素のオプション値、または `xs` が空の場合 `None`。| -| `xs find p` |`xs` の中で条件関数 `p` を満たす最初の要素のオプション値、または条件を満たす要素が無い場合 `None`。| -| **サブコレクション取得演算:** | | -| `xs.tail` |コレクションから `xs.head` を除いた残りの部分。 | -| `xs.init` |コレクションから `xs.last` を除いた残りの部分。 | -| `xs slice (from, to)` |`xs` の一部の添字範囲内 (`from` 以上 `to` 未満) にある要素から成るコレクション。 | -| `xs take n` |`xs` の最初の `n` 個の要素から成るコレクション (順序が定義されていない場合は、任意の `n` 個の要素から成るコレクション)。| -| `xs drop n` |コレクションから `xs take n` を除いた残りの部分。 | -| `xs takeWhile p` |`xs` 内の要素を最初から次々とみて、条件関数 `p` を満たす限りつないでいったコレクション。| -| `xs dropWhile p` |`xs` 内の要素を最初から次々とみて、条件関数 `p`を満たす限り除いていったコレクション。| -| `xs filter p` |`xs` 内の要素で条件関数 `p` を満たすものから成るコレクション。| -| `xs withFilter p` |このコレクションを非正格 (non-strict) に filter したもの。後続の `map`, `flatMap`, `foreach`, および `withFilter` への呼び出しは `xs` の要素のうち条件関数 `p` が true に評価されるもののみに適用される。| -| `xs filterNot p` |`xs` 内の要素で条件関数 `p` を満たさないものから成るコレクション。| -| **分割演算:** | | -| `xs splitAt n` |`xs` を `n` の位置で二分して `(xs take n, xs drop n)` と同値のコレクションのペアを返す。| -| `xs span p` |`xs` を条件関数 `p` に応じて二分して `(xs takeWhile p, xs.dropWhile p)` と同値のペアを返す。| -| `xs partition p` |`xs` を条件関数 `p` を満たすコレクションと満たさないものに二分して `(xs filter p, xs.filterNot p)` と同値のペアを返す。| -| `xs groupBy f` |`xs` を判別関数 `f` に応じてコレクションのマップに分割する。| -| **要素条件演算:** | | -| `xs forall p` |`xs` 内の全ての要素に条件関数 `p` が当てはまるかを示す Boolean 値。| -| `xs exists p` |`xs` 内に条件関数 `p` を満たす要素があるかどうかを示す Boolean 値。| -| `xs count p` |`xs` 内の要素で条件関数 `p` 満たすものの数。| -| **fold 演算:** | | -| `(z /: xs)(op)` |`z` から始めて、左から右へと `xs` 内の隣接する要素に二項演算 `op` を次々と適用したもの。| -| `(xs :\ z)(op)` |`z` から始めて、右から左へと `xs` 内の隣接する要素に二項演算 `op` を次々と適用したもの。| -| `xs.foldLeft(z)(op)` |`(z /: xs)(op)` に同じ。 | -| `xs.foldRight(z)(op)` |`(xs :\ z)(op)` に同じ。 | -| `xs reduceLeft op` |左から右へと空ではないコレクション `xs` 内の隣接する要素に二項演算 `op` を次々と適用したもの。| -| `xs reduceRight op` |右から左へと空ではないコレクション `xs` 内の隣接する要素に二項演算 `op` を次々と適用したもの。| -| **特定 fold 演算:** | | -| `xs.sum` |コレクション `xs` 内の数値要素の値の和。 | -| `xs.product` |コレクション `xs` 内の数値要素の値の積。 | -| `xs.min` |コレクション `xs` 内の順序付けされたの値の最小値。 | -| `xs.max` |コレクション `xs` 内の順序付けされたの値の最大値。 | -| **文字列演算:** | | -| `xs addString (b, start, sep, end)`|`xs` 内の要素を `sep` で区切った後、`start` と `end` で挟んだ文字列を `StringBuilder b` に追加する。 `start`, `sep`, `end` は全て省略可能。| -| `xs mkString (start, sep, end)`|`xs` 内の要素を `sep` で区切った後、`start` と `end` で挟んだ文字列に変換する。 `start`, `sep`, `end` は全て省略可能。| -| `xs.stringPrefix` |`xs.toString` で返される文字列の先頭にあるコレクション名。| -| **ビュー演算:** | | -| `xs.view` |`xs` に対するビューを生成する。 | -| `xs view (from, to)` |`xs` の一部の添字範囲内を表すビューを生成する。 | diff --git a/ja/overviews/collections/views.md b/ja/overviews/collections/views.md deleted file mode 100644 index 2de76355ce..0000000000 --- a/ja/overviews/collections/views.md +++ /dev/null @@ -1,130 +0,0 @@ ---- -layout: overview-large -title: ビュー - -discourse: false - -partof: collections -num: 14 -language: ja ---- - -コレクションには新たなコレクションを構築するメソッドがたくさんある。例えば `map`、`filter`、`++` などがある。これらのメソッドは1つ以上のコレクションをレシーバとして取り、戻り値として別のコレクションを生成するため**変換演算子** -(transformer) と呼ばれる。 - -変換演算子を実装するには主に二つの方法がある。**正格** (strict) 法は変換演算子の戻り値として全ての要素を含む新たなコレクションを返す。非正格法、もしくは**遅延** (lazy) 法と呼ばれる方法は、結果のコレクションの代理のみを構築して返し、実際の要素は必用に応じて構築される。 - -非正格な変換演算子の具体例として、以下の遅延 map 演算の実装を見てほしい: - - def lazyMap[T, U](coll: Iterable[T], f: T => U) = new Iterable[U] { - def iterator = coll.iterator map f - } - -`lazyMap` は、渡されたコレクション `coll` の全要素を総なめすることなく新しい `Iterable` を構築していることに注意してほしい。代わりに、渡された関数の `f` が新しいコレクションの `iterator` に必要に応じて適用される。 - -全ての変換演算子を遅延実装している `Stream` を除いて、Scala のコレクションは全ての変換演算子をデフォルトで正格法で実装している。しかし、コレクションのビューにより、体系的に全てのコレクションを遅延したものに変え、また逆に戻すことができる。**ビュー** (view) は特殊なコレクションの一種で、何らかのコレクションに基づいているが全ての変換演算子を遅延実装してる。 - -あるコレクションからそのビューへと移行するには、そのコレクションに対して `view` メソッドを呼び出す。`xs` というコレクションがあるとすると、`xs.view` は変換演算子が遅延実装されている以外は同一のコレクションだ。ビューから正格なコレクションに戻るには `force` メソッドを使う。 - -具体例を見てみよう。2つの関数を続けて写像したい `Int`型のベクトルがあるとする: - - scala> val v = Vector(1 to 10: _*) - v: scala.collection.immutable.Vector[Int] = - Vector(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) - scala> v map (_ + 1) map (_ * 2) - res5: scala.collection.immutable.Vector[Int] = - Vector(4, 6, 8, 10, 12, 14, 16, 18, 20, 22) - -最後のステートメントにおいて、式 `v map (_ + 1)` は新たなベクトルを構築し、それは第2の `map (_ * 2)` -の呼び出しによって3つ目のベクトルに変換される。多くの状況において、最初の `map` への呼び出しで中間結果のためだけのベクトルが構築されるのは無駄にしかならない。上記の例では、`(_ + 1)` と `(_ * 2)` の2つの関数を合成して `map` を1回だけ実行したほうが速くなるだろう。両方の関数が1ヶ所にあるならば手で合成することも可能だろう。しかし、しばしばデータ構造への連続した変換はプログラム内の別々のモジュールによって実行される。もしそうならば、これらの変換を融合することはモジュール性を犠牲してしまう。中間結果を回避する、より汎用性のある方法は、ベクトルをまずビューに変え全ての変換をビューに適用した後、ビューからベクトルに逆変換することだ: - - scala> (v.view map (_ + 1) map (_ * 2)).force - res12: Seq[Int] = Vector(4, 6, 8, 10, 12, 14, 16, 18, 20, 22) - -同じ演算の手順をもう1度ひとつひとつ見てみよう: - - scala> val vv = v.view - vv: scala.collection.SeqView[Int,Vector[Int]] = - SeqView(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) - -`v.view` を適用することで遅延評価される `Seq` である `SeqView` が得られる。`SeqView` には2つの型パラメータがある。第1の `Int` はビューの要素の型を示す。第2の `Vector[Int]` は `view` -を逆変換する時の型コンストラクタを示す。 - -最初の `map` を適用することでビューは以下を返す: - - scala> vv map (_ + 1) - res13: scala.collection.SeqView[Int,Seq[_]] = SeqViewM(...) - -`map` の戻り値は、`SeqViewM(...)` と表示された値だ。この値は本質的に、ベクトル `v` に対して関数 `(_ + 1)` を使って `map` を適用する必要があるということを記録するラッパーだ。しかし、ビューが強制実行 (`force`) されるまでは map 演算は適用されない。`SeqView` の後ろに付けられた「M」は、ビューが `map` 演算を表すことを示す。他の文字は別の遅延された演算を示す。例えば、「S」は遅延した `slice` 演算を示し、「R」は遅延した `reverse` 演算を示す。先ほどの結果に、次の `map` を適用しよう。 - - scala> res13 map (_ * 2) - res14: scala.collection.SeqView[Int,Seq[_]] = SeqViewMM(...) - -今度は2回の map 演算を含む `SeqView` が得られるため、「M」も二回表示される: `SeqViewMM(...)`。 最後に、先ほどの結果を逆変換すると: - - scala> res14.force - res15: Seq[Int] = Vector(4, 6, 8, 10, 12, 14, 16, 18, 20, 22) - -格納されていた両方の関数は強制実行 (`force`) の一部として適用され、新しいベクトルが構築される。これにより、中間結果のデータ構造は必要なくなった。 - -1つ注意してほしいのは、最終結果の静的型が `Vector` ではなく `Seq` であるということだ。 型をさかのぼってみると、最初の遅延 map が適用された時点で既に戻り値の静的型が `SeqViewM[Int, Seq[_]]` 型であったことが分かる。つまり、ビューが特定の列型である `Vector` に適応されたという「知識」が失われたということだ。ビューの実装には多量のコードを要するものがあるため、Scala コレクションライブラリは汎用コレクション型にのみビューを提供するが、特定の実装にはビューは提供されない。(配列はこの例外で、配列に遅延演算を適用しても戻り値は再び静的型の `Array` を返す。) - -ビューを使うことを考慮する二つの理由がある。第一は、パフォーマンスだ。コレクションをビューに切り替えることで中間結果の構築を避けれることを既に説明した。このような節約は時として大切な結果をもたらす。もう一つの具体例として、単語のリストから回文を探す問題を考える。回文とは前後のどちらから読んでも同じ語句だ。必要な定義を以下に示す: - - def isPalindrome(x: String) = x == x.reverse - def findPalidrome(s: Seq[String]) = s find isPalindrome - -ここで非常に長い `words` という列があり、列の最初の百万語の中から単一の回文を探したいと仮定する。`findPalidrome` の定義を再利用できるだろうか。当然、以下のように書くことはできる: - - findPalindrome(words take 1000000) - -これは、列の中から最初の百万語を選択するのと、単一の回文を探すという二つの側面をきれいに分担する。しかし、たとえ列の最初の語が回文であったとしても、百万語から成る中間結果の列を常に構築してしまうという欠点がある。つまり、999999語が中間結果にコピーされ、その後検査もされないということが起こりえる。ここで多くのプログラマは諦めて、先頭 n個の部分列に対して検索を行う特殊なバージョンの回文検索を書いてしまう。ビューがあれば、あなたはそんな事をしなくても済む。こう書けばいいからだ: - - findPalindrome(words.view take 1000000) - -関心事の分業を保ちつつも、これは百万要素の列の代わりに軽量なビューオブジェクトのみを構築する。これにより、パフォーマンスとモジュール性の択一をしなくても済む。 - -次の事例として、可変列に対するビューを見てみたい。可変列のビューにいくつかの変換関数を適用することで、元の列内の要素を選択的に更新する制限枠として機能することができる。ここに配列 `arr` があると仮定する: - - scala> val arr = (0 to 9).toArray - arr: Array[Int] = Array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9) - -その配列への制限枠を作るのに、`arr` のビューのスライスを作ることができる: - - scala> val subarr = arr.view.slice(3, 6) - subarr: scala.collection.mutable.IndexedSeqView[ - Int,Array[Int]] = IndexedSeqViewS(...) - -これにより、配列 `arr` の 3〜5 の位置を参照するビュー `subarr` が得られる。ビューは要素をコピーせず、参照のみを提供する。ここで、列の要素を変更するメソッドがあると仮定する。例えば、以下の `negate` メソッドは、与えれれた整数列の全ての要素の符号を反転する: - - scala> def negate(xs: collection.mutable.Seq[Int]) = - for (i <- 0 until xs.length) xs(i) = -xs(i) - negate: (xs: scala.collection.mutable.Seq[Int])Unit - -配列 `arr` の 3〜5 の位置の要素の符号を反転したいとする。これに `negate` が使えるだろうか。 ビューを使えば、簡単にできる: - - scala> negate(subarr) - scala> arr - res4: Array[Int] = Array(0, 1, 2, -3, -4, -5, 6, 7, 8, 9) - -何が起こったかと言うと、`negate` は `subarr`内の全ての要素を変更したが、実際にはそれは `arr` -の要素のスライスだったというわけだ。ここでも、ビューがモジュール性を保つのに役立っているのが分かるだろう。上記のコードは、メソッドをどの添字の範囲に適用するのかという問題と、どのメソッドを適用するのかという問題を見事に切り離している。 - -これだけ粋なビューの使用例を見た後だと、なぜ正格コレクションがあるのか疑問に思うかもしれない。理由の一つとして、性能を比較すると遅延コレクションが常に正格コレクションに勝るとは限らないというものがある。コレクションのサイズが小さい場合、ビュー内でクロージャを作成し適用するためのオーバーヘッドが、中間結果のためのデータ構造を回避することによる利得を上回ってしまうことが多いのだ。恐らくより重要な理由として、遅延した演算が副作用を伴う場合、ビューの評価が非常に混乱したものとなってしまうというものがある。 - -Scala 2.8 以前で多くのユーザが陥った失敗例を以下に示す。これらのバージョンでは `Range` 型が遅延評価されたため、実質的にビューのように振舞った。多くの人が以下のようにして複数の actor を作成しようとした: - - val actors = for (i <- 1 to 10) yield actor { ... } - -`actor` メソッドと後続の中括弧に囲まれたコードは actor を作成し始動するべきだが、驚いた事にどの actor も実行されていなかった。なぜ何も起こらなかったのかは、for 式が `map` の適用と等価であることを思い出せば説明がつく: - - val actors = (1 to 10) map (i => actor { ... }) - -`(1 to 10)` により生成された範囲はビューのように振舞ったため、`map` の戻り値もビューとなった。つまり、どの要素も計算されず、結果的にどの actor も作成されなかったのだ! 範囲の式全体を強制実行すれば actor は作成されただろうが、actor -を作動させるためにそれが必要なことは全く明白では無い。 - -このような不意打ちを避けるため、Scala 2.8 ではより規則的なルールを採用する。ストリームを除く、全てのコレクションは正格評価される。正格コレクションから遅延コレクションに移行する唯一の方法は `view` メソッドによる。戻る唯一の方法は `force` による。よって、Scala 2.8 では先程の `actors` の定義は期待した通りに 10個の actor を作成し始動する。以前のような、予期しない振る舞いをさせるには、明示的に `view` メソッドを呼び出す必要がある: - - val actors = for (i <- (1 to 10).view) yield actor { ... } - -まとめると、ビューは効率性の問題とモジュール性の問題を仲裁する強力な道具だ。しかし、遅延評価の面倒に巻き込まれないためには、ビューの使用は二つのシナリオに限るべきだ。一つは、ビューの適用を副作用を伴わない純粋関数型のコードに限ること。もしくは、明示的に全ての変更が行われる可変コレクションに適用することだ。避けたほうがいいのは、ビューと新たなコレクションを作成しつつ副作用を伴う演算を混合することだ。 diff --git a/ja/overviews/core/collections.md b/ja/overviews/core/collections.md deleted file mode 100644 index 15219520aa..0000000000 --- a/ja/overviews/core/collections.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -layout: overview -overview: collections -partof: collections -language: ja -title: Scala コレクションライブラリ ---- diff --git a/ja/overviews/core/futures.md b/ja/overviews/core/futures.md deleted file mode 100644 index f05a980e1a..0000000000 --- a/ja/overviews/core/futures.md +++ /dev/null @@ -1,732 +0,0 @@ ---- -layout: overview -label-color: success -label-text: New in 2.10 -overview: futures -language: ja -title: Future と Promise - -discourse: false ---- - -**Philipp Haller, Aleksandar Prokopec, Heather Miller, Viktor Klang, Roland Kuhn, Vojin Jovanovic 著**
        -**Eugene Yokota 訳** - -## 概要 - -**Future** は並列に実行される複数の演算を取り扱うのに便利な方法を提供する。それは効率的でノンブロッキングな方法だ。 -大まかな考え方はシンプルなもので、`Future` はまだ存在しない計算結果に対するプレースホルダのようなものだ。 -一般的に、`Future` の結果は並行に計算され後で集計することができる。 -このように並行なタスクを合成することで、より速く、非同期で、ノンブロッキングな並列コードとなることが多い。 - -デフォルトでは、Future も Promise もノンブロッキングであり、典型的なブロッキング演算の代わりにコールバックを使う。 -コールバックの使用を概念的にも構文的にも単純化するために、Scala は Future をノンブロッキングに合成する `flatMap`、`foreach`、`filter` といったコンビネータを提供する。 -ブロックすることは可能で、(推奨されないが) 絶対に必要だという場面においては Future をブロックすることもできる。 - - - -## Future - -`Future` は、ある時点において利用可能となる可能性のある値を保持するオブジェクトだ。 -この値は、なんらかの計算結果であることが多い。 -その計算が例外とともに失敗する可能性があるため、`Future` は計算が例外を投げる場合を想定して例外を保持することもできる。 -ある `Future` が値もしくは例外を持つとき、`Future` は**完了**したという。 -`Future` が値とともに完了した場合、`Future` はその値とともに**成功**したという。 -`Future` が例外とともに完了した場合、`Future` はその例外とともに**失敗**したという。 - -`Future` には 1回だけ代入することができるという重要な特性がある。 -一度 Future オブジェクトが値もしくは例外を持つと、実質不変となり、それが上書きされることは絶対に無い。 - -Future オブジェクトを作る最も簡単な方法は、非同期の計算を始めてその結果を持つ `Future` を返す -`future` メソッドを呼び出すことだ。 -計算結果は `Future` が完了すると利用可能になる。 - -ここで注意して欲しいのは `Future[T]` は Future オブジェクトの型であり、 -`future` はなんらかの非同期な計算を作成しスケジュールして、その計算結果とともに完了する -Future オブジェクトを返すメソッドだということだ。 - -具体例で説明しよう。 -ある人気ソーシャルネットワークの API を想定して、与えられたユーザの友達のリストを取得できるものとする。 -まず新しいセッションを開いて、ある特定のユーザの友達リストを申請する: - - import scala.concurrent._ - import ExecutionContext.Implicits.global - - val session = socialNetwork.createSessionFor("user", credentials) - val f: Future[List[Friend]] = Future { - session.getFriends() - } - -上の例では、まず `scala.concurrent` パッケージの内容をインポートすることで -`Future` 型と `future` が見えるようにしている。 -2つ目のインポートは追って説明する。 - -次に、仮想の `createSessionFor` メソッドを呼んでサーバにリクエストを送るセッション変数を初期化している。 - -ユーザの友達リストを取得するには、ネットワークごしにリクエストを送信する必要があり、それは長い時間がかかる可能性がある。 -これは `getFriends` メソッドで例示されている。 -応答が返ってくるまでの間に CPU を有効に使うには、プログラムの残りをブロックするべきではない。 -つまり、この計算は非同期にスケジュールされるべきだ。 -ここで使われている `future` メソッドはまさにそれを行い、与えれたブロックを並行に実行する。 -この場合は、リクエストを送信し応答を待ち続ける。 - -サーバが応答すると Future `f` 内において友達リストが利用可能となる。 - -試みが失敗すると、例外が発生するかもしれない。 -以下の例では、`session` 変数の初期化が不正なため、`future` ブロック内の計算が -`NullPointerException` を投げる。この Future `f` は、この例外とともに失敗する: - - val session = null - val f: Future[List[Friend]] = Future { - session.getFriends - } - -上の `import ExecutionContext.Implicits.global` -という一文はデフォルトのグローバルな実行コンテキスト (execution context) をインポートする。 -実行コンテキストは渡されたタスクを実行し、スレッドプールのようなものだと考えていい。 -これらは、非同期計算がいつどのように実行されるかを取り扱うため、`future` メソッドに欠かせないものだ。 -独自の実行コンテキストを定義して `future` -とともに使うことができるが、今のところは上記のようにデフォルトの実行コンテキストをインポートできるということが分かれば十分だ。 - -この例ではネットワークごしにリクエストを送信して応答を待つという仮想のソーシャルネットワーク API を考えてみた。 -すぐに試してみることができる非同期の計算の例も考えてみよう。 -テキストファイルがあったとして、その中である特定のキーワードが最初に出てきた位置を知りたいとする。 -この計算はファイルの内容をディスクから読み込むのにブロッキングする可能性があるため、他の計算と並行実行するのは理にかなっている。 - - val firstOccurence: Future[Int] = Future { - val source = scala.io.Source.fromFile("myText.txt") - source.toSeq.indexOfSlice("myKeyword") - } - -### コールバック - -これで非同期な計算を始めて新しい Future オブジェクトを作る方法は分かったけども、計算結果が利用可能となったときにそれを使って何かをする方法をまだみていない。 -多くの場合、計算の副作用だけじゃなくて、その結果に興味がある。 - -Future の実装の多くは、Future の結果を知りたくなったクライアントは Future が完了するまで自分の計算をブロックすることを強要する。そうしてやっと Future の計算結果を得られた後に自分の計算を続行できるようになる。 -後でみるように、この方式も Scala の Future API で可能となっているが、性能という観点から見ると Future -にコールバックを登録することで完全にノンブロッキングで行う方が好ましいと言える。 -このコールバックは Future が完了すると非同期に呼び出される。 -コールバックの登録時に Future が既に完了している場合は、コールバックは非同期に実行されるか、もしくは同じスレッドで逐次的に実行される。 - -コールバックを登録する最も汎用的な方法は、`Try[T] => U` 型の関数を受け取る `onComplete` メソッドを使うことだ。 -このコールバックは、Future が成功すれば `Success[T]` 型の値に適用され、失敗すれば `Failure[T]` 型の値に適用される。 - -この `Try[T]` は、それが何らか型の値を潜在的に保持するモナドだという意味において -`Option[T]` や `Either[T, S]` に似ている。 -しかし、これは値か Throwable なオブジェクトを保持することに特化して設計されている。 -`Option[T]` が値 (つまり `Some[T]`) を持つか、何も持たない (つまり `None`) -のに対して、`Try[T]` は値を持つ場合は `Success[T]` で、それ以外の場合は `Failure[T]` で必ず例外を持つ。 -`Failure[T]` は、何故値が無いのかを説明できるため、`None` よりも多くの情報を持つ。 -同様に `Try[T]` を `Either[Throwable, T]`、つまり左値を `Throwable` に固定した特殊形だと考えることもできる。 - -ソーシャルネットワークの例に戻って、最近の自分の投稿した文のリストを取得して画面に表示したいとする。 -これは `List[String]` を返す `getRecentPosts` メソッドを呼ぶことで行われ、戻り値には最近の文のリストが入っている: - - val f: Future[List[String]] = Future { - session.getRecentPosts - } - - f onComplete { - case Success(posts) => for (post <- posts) println(post) - case Failure(t) => println("エラーが発生した: " + t.getMessage) - } - -`onComplete` メソッドは、Future 計算の失敗と成功の両方の結果を扱えるため、汎用性が高い。 -成功した結果のみ扱う場合は、(部分関数を受け取る) `onSuccess` コールバックを使う: - - val f: Future[List[String]] = Future { - session.getRecentPosts - } - - f onSuccess { - case posts => for (post <- posts) println(post) - } - -失敗した結果のみ扱う場合は、`onFailure` コールバックを使う: - - val f: Future[List[String]] = Future { - session.getRecentPosts - } - - f onFailure { - case t => println("エラーが発生した: " + t.getMessage) - } - - f onSuccess { - case posts => for (post <- posts) println(post) - } - -`onFalure` コールバックは Future が失敗した場合、つまりそれが例外を保持する場合のみ実行される。 - -部分関数は `isDefinedAt` メソッドを持つため、`onFailure` メソッドはコールバックが特定の `Throwable` に対して定義されている場合のみ発火される。 -以下の例では、登録された `onFailure` コールバックは発火されない: - - val f = Future { - 2 / 0 - } - - f onFailure { - case npe: NullPointerException => - println("これが表示されているとしたらビックリ。") - } - -キーワードの初出の位置を検索する例に戻ると、キーワードの位置を画面に表示したいかもしれない: - - val firstOccurence: Future[Int] = Future { - val source = scala.io.Source.fromFile("myText.txt") - source.toSeq.indexOfSlice("myKeyword") - } - - firstOccurence onSuccess { - case idx => println("キーワードの初出位置: " + idx) - } - - firstOccurence onFailure { - case t => println("ファイルの処理に失敗した: " + t.getMessage) - } - -`onComplete`、`onSuccess`、および `onFailure` メソッドは全て `Unit` 型を返すため、これらの呼び出しを連鎖させることができない。 -これは意図的な設計で、連鎖された呼び出しが登録されたコールバックの実行の順序を暗示しないようにしている -(同じ Future に登録されたコールバックは順不同に発火される)。 - -ここで、コールバックが実際のところ**いつ**呼ばれるのかに関して説明する必要がある。 -Future 内の値が利用可能となることを必要とするため、Future が完了した後でのみ呼び出されることができる。 -しかし、Future を完了したスレッドかコールバックを作成したスレッドのいずれかにより呼び出されるという保証は無い。 -かわりに、コールバックは Future オブジェクトが完了した後のいつかに何らかスレッドにより実行される。 -これをコールバックが実行されるのは **eventually** だという。 - -さらに、コールバックが実行される順序は、たとえ同じアプリケーションを複数回実行した間だけでも決定してない。 -実際、コールバックは逐次的に呼び出されるとは限らず、一度に並行実行されるかもしれない。 -そのため、以下の例における変数 `totalA` は計算されたテキスト内の正しい小文字と大文字の `a` の合計数を持たない場合がある。 - - @volatile var totalA = 0 - - val text = Future { - "na" * 16 + "BATMAN!!!" - } - - text onSuccess { - case txt => totalA += txt.count(_ == 'a') - } - - text onSuccess { - case txt => totalA += txt.count(_ == 'A') - } - -2つのコールバックが順次に実行された場合は、変数 `totalA` は期待される値 `18` を持つ。 -しかし、これらは並行して実行される可能性もあるため、その場合は `totalA` は -`+=` が atomic な演算ではないため、 -(つまり、読み込みと書き込みというステップから構成されており、それが他の読み込みと書き込みの間に挟まって実行される可能性がある) -`16` または `2` という値になってしまう可能性もある。 - -万全を期して、以下にコールバックの意味論を列挙する: - - -
          -
        1. Future に onComplete コールバックを登録することで、対応するクロージャが Future が完了した後に eventually に実行されることが保証される。
        2. - -
        3. onSuccessonFailure コールバックを登録することは onComplete と同じ意味論を持つ。ただし、クロージャがそれぞれ成功したか失敗した場合のみに呼ばれるという違いがある。
        4. - -
        5. 既に完了した Future にコールバックを登録することは (1 により) コールバックが eventually に実行されることとなる。
        6. - -
        7. Future に複数のコールバックが登録された場合は、それらが実行される順序は定義されない。それどころか、コールバックは並行に実行される可能性がある。しかし、ExecutionContext の実装によっては明確に定義された順序となる可能性もある。
        8. - -
        9. 例外を投げるコールバックがあったとしても、他のコールバックは実行される。
        10. - -
        11. 完了しないコールバックがあった場合 (つまりコールバックに無限ループがあった場合)他のコールバックは実行されない可能性がある。そのような場合はブロックする可能性のあるコールバックは blocking 構文を使うべきだ (以下参照)。
        12. - -
        13. コールバックの実行後はそれは Future オブジェクトから削除され、GC 対象となる。
        14. -
        - -### 関数型合成と for 内包表記 - -上でみたコールバック機構により Future の結果を後続の計算に連鎖することが可能となった。 -しかし、場合によっては不便だったり、コードが肥大化することもある。 -具体例で説明しよう。 -為替トレードサービスの API があって、米ドルを有利な場合のみ買いたいとする。 -まずコールバックを使ってこれを実現してみよう: - - val rateQuote = Future { - connection.getCurrentValue(USD) - } - - rateQuote onSuccess { case quote => - val purchase = Future { - if (isProfitable(quote)) connection.buy(amount, quote) - else throw new Exception("有益ではない") - } - - purchase onSuccess { - case _ => println(amount + " USD を購入した") - } - } - -まずは現在の為替相場を取得する `rateQuote` という `Future` を作る。 -この値がサーバから取得できて Future が成功した場合は、計算は -`onSuccess` コールバックに進み、ここで買うかどうかの決断をすることができる。 -ここでもう 1つの Future である `purchase` を作って、有利な場合のみ買う決定をしてリクエストを送信する。 -最後に、`purchase` が完了すると、通知メッセージを標準出力に表示する。 - -これは動作するが、2つの理由により不便だ。 -第一に、`onSuccess` を使わなくてはいけなくて、2つ目の Future である -`purchase` をその中に入れ子にする必要があることだ。 -例えば `purchase` が完了した後に別の貨幣を売却したいとする。 -それはまた `onSuccess` の中でこのパターンを繰り返すことになり、インデントしすぎで理解しづらく肥大化したコードとなる。 - -第二に、`purchase` は他のコードのスコープ外にあり、`onSuccess` -コールバック内においてのみ操作することができる。 -そのため、アプリケーションの他の部分は `purchase` を見ることができず、他の貨幣を売るために別の -`onSuccess` コールバックを登録することもできない。 - -これらの 2つの理由から Future はより自然な合成を行うコンビネータを提供する。 -基本的なコンビネータの 1つが `map` で、これは与えられた Future -とその値に対する投射関数から、元の Future が成功した場合に投射した値とともに完了する新しい Future を生成する。 -Future の投射はコレクションの投射と同様に考えることができる。 - -上の例を `map` コンビネータを使って書き換えてみよう: - - val rateQuote = Future { - connection.getCurrentValue(USD) - } - - val purchase = rateQuote map { quote => - if (isProfitable(quote)) connection.buy(amount, quote) - else throw new Exception("有益ではない") - } - - purchase onSuccess { - case _ => println(amount + " USD を購入した") - } - -`rateQuote` に対して `map` を使うことで `onSuccess` コールバックを一切使わないようになった。 -それと、より重要なのが入れ子が無くなったことだ。 -ここで他の貨幣を売却したいと思えば、`purchase` に再び `map` するだけでいい。 - -しかし、`isProfitable` が `false` を返して、例外が投げられた場合はどうなるだろう? -その場合は `purchase` は例外とともに失敗する。 -さらに、コネクションが壊れて `getCurrentValue` が例外を投げて `rateQuote` -が失敗した場合を想像してほしい。 -その場合は、投射する値が無いため `purchase` は自動的に `rateQuote` と同じ例外とともに失敗する。 - -結果として、もし元の Future が成功した場合は、返される Future は元の Future の値を投射したものとともに完了する。 -もし投射関数が例外を投げた場合は Future はその例外とともに完了する。 -もし元の Future が例外とともに失敗した場合は、返される Future も同じ例外を持つ。 -この例外を伝搬させる意味論は他のコンビネータにおいても同様だ。 - -Future の設計指針の 1つは for 内包表記から利用できるようにすることだった。 -このため、Future は `flatMap`、`filter` そして `foreach` コンビネータを持つ。 -`flatMap` メソッドは値を新しい Future `g` に投射する関数を受け取り、`g` -が完了したときに完了する新たな Future を返す。 - -米ドルをスイス・フラン (CHF) と交換したいとする。 -両方の貨幣の為替レートを取得して、両者の値に応じて購入を決定する必要がある。 -以下に for 内包表記を使った `flatMap` と `withFilter` の例をみてみよう: - - val usdQuote = Future { connection.getCurrentValue(USD) } - val chfQuote = Future { connection.getCurrentValue(CHF) } - - val purchase = for { - usd <- usdQuote - chf <- chfQuote - if isProfitable(usd, chf) - } yield connection.buy(amount, chf) - - purchase onSuccess { - case _ => println(amount + " CHF を購入した") - } - -この `purchase` は `usdQuote` と `chfQuote` が完了した場合のみ完了する。 -これら 2つの Future の値に依存するため、それよりも早く自分の計算を始めることができない。 - -上の for 内包表記は以下のように翻訳される: - - val purchase = usdQuote flatMap { - usd => - chfQuote - .withFilter(chf => isProfitable(usd, chf)) - .map(chf => connection.buy(amount, chf)) - } - -これは for 内包表記に比べて分かりづらいが、`flatMap` 演算をより良く理解するために解析してみよう。 -`flatMap` 演算は自身の値を別の Future へと投射する。 -この別の Future が完了すると、戻り値の Future もその値とともに完了する。 -上記の例では、`flatMap` は `usdQuote` Future の値を用いて `chfQuote` -の値をある特定の値のスイス・フランを購入するリクエストを送信する 3つ目の Future に投射している。 -結果の Future である `purchase` は、この 3つ目の Future が `map` から帰ってきた後にのみ完了する。 - -これは難解だが、幸いな事に `flatMap` 演算は使いやすく、また分かりやすい -for 内包表記以外の場合はあまり使われない。 - -`filter` コンビネータは、元の Future の値が条件関数を満たした場合のみその値を持つ新たな Future を作成する。 -それ以外の場合は新しい Future は `NoSuchElementException` とともに失敗する。 -Future に関しては、`filter` の呼び出しは `withFilter` の呼び出しと全く同様の効果がある。 - -`collect` と `filter` コンビネータの関係はコレクション API におけるこれらのメソッドの関係に似ている。 - -`foreach` コンビネータで注意しなければいけないのは値が利用可能となった場合に走査するのにブロックしないということだ。 -かわりに、`foreach` のための関数は Future が成功した場合のみ非同期に実行される。 -そのため、`foreach` は `onSuccess` コールバックと全く同じ意味を持つ。 - -`Future` トレイトは概念的に (計算結果と例外という) 2つの型の値を保持することができるため、例外処理のためのコンビネータが必要となる。 - -`rateQuote` に基いて何らかの額を買うとする。 -`connection.buy` メソッドは `amount` と期待する `quote` を受け取る。 -これは買われた額を返す。 -`quote` に変更があった場合は、何も買わずに `QuoteChangedException` を投げる。 -例外の代わりに `0` を持つ Future を作りたければ `recover` コンビネータを用いる: - - val purchase: Future[Int] = rateQuote map { - quote => connection.buy(amount, quote) - } recover { - case QuoteChangedException() => 0 - } - -`recover` コンビネータは元の Future が成功した場合は同一の結果を持つ新たな Future -を作成する。成功しなかった場合は、元の Future を失敗させた `Throwable` -に渡された部分関数が適用される。 -もしそれが `Throwable` を何らかの値に投射すれば、新しい Future はその値とともに成功する。 -もしその `Throwable` に関して部分関数が定義されていなければ、結果となる -Future は同じ `Throwable` とともに失敗する。 - -`recoverWith` コンビネータは元の Future が成功した場合は同一の結果を持つ新たな Future -を作成する。成功しなかった場合は、元の Future を失敗させた `Throwable` -に渡された部分関数が適用される。 -もしそれが `Throwable` を何らかの Future に投射すれば、新しい Future はその Future とともに成功する。 -`recover` に対する関係は `flatMap` と `map` の関係に似ている。 - -`fallbackTo` コンビネータは元の Future が成功した場合は同一の結果を持ち、成功しなかった場合は引数として渡された Future の成功した値を持つ新たな Future を作成する。 -この Future と引数の Future が両方失敗した場合は、新しい Future はこの Future の例外とともに失敗する。 -以下に米ドルの値を表示することを試みて、米ドルの取得に失敗した場合はスイス・フランの値を表示する具体例をみてみよう: - - val usdQuote = Future { - connection.getCurrentValue(USD) - } map { - usd => "値: " + usd + " USD" - } - val chfQuote = Future { - connection.getCurrentValue(CHF) - } map { - chf => "値: " + chf + "CHF" - } - - val anyQuote = usdQuote fallbackTo chfQuote - - anyQuote onSuccess { println(_) } - -`andThen` コンビネータは副作用の目的のためだけに用いられる。 -これは、成功したか失敗したかに関わらず現在の Future と全く同一の結果を返す新たな Future を作成する。 -現在の Future が完了すると、`andThen` に渡されたクロージャが呼び出され、新たな Future -はこの Future と同じ結果とともに完了する。 -これは複数の `andThen` 呼び出しが順序付けられていることを保証する。 -ソーシャルネットワークからの最近の投稿文を可変セットに保存して、全ての投稿文を画面に表示する以下の具体例をみてみよう: - - val allposts = mutable.Set[String]() - - Future { - session.getRecentPosts - } andThen { - case Success(posts) => allposts ++= posts - } andThen { - case _ => - clearAll() - for (post <- allposts) render(post) - } - -まとめると、Future に対する全てのコンビネータは元の Future に関連する新たな Future -を返すため、純粋関数型だといえる。 - -### 投射 - -例外として返ってきた結果に対して for 内包表記が使えるように Future は投射を持つ。 -元の Future が失敗した場合は、`failed` 投射は `Throwable` 型の値を持つ Future を返す。 -もし元の Future が成功した場合は、`failed` 投射は `NoSuchElementException` -とともに失敗する。以下は例外を画面に表示する具体例だ: - - val f = Future { - 2 / 0 - } - for (exc <- f.failed) println(exc) - -以下の例は画面に何も表示しない: - - val f = Future { - 4 / 2 - } - for (exc <- f.failed) println(exc) - - - - - - - -### Future の拡張 - -Future API にユーティリティメソッドを追加して拡張できるようにすることを予定している。 -これによって、外部フレームワークはより特化した使い方を提供できるようになる。 - -## ブロッキング - -前述のとおり、性能とデッドロックの回避という理由から Future をブロックしないことを強く推奨する。 -コールバックとコンビネータを使うことが Future の結果を利用するのに適した方法だ。 -しかし、状況によってはブロックすることが必要となるため、Future API と Promise API -においてサポートされている。 - -前にみた為替取引の例だと、アプリケーションの最後に全ての Future -が完了することを保証するためにブロックする必要がある。 -Future の結果に対してブロックする方法を以下に具体例で説明しよう: - - import scala.concurrent._ - import scala.concurrent.duration._ - - def main(args: Array[String]) { - val rateQuote = Future { - connection.getCurrentValue(USD) - } - - val purchase = rateQuote map { quote => - if (isProfitable(quote)) connection.buy(amount, quote) - else throw new Exception("有益ではない") - } - - Await.result(purchase, 0 nanos) - } - -Future が失敗した場合は、呼び出し元には Future が失敗した例外が送られてくる。 -これは `failed` 投射を含むため、元の Future が成功した場合は -`NoSuchElementException` が投げられることとなる。 - -代わりに、`Await.ready` を呼ぶことで Future が完了するまで待機するがその結果を取得しないことができる。 -同様に、このメソッドを呼んだ時に Future が失敗したとしても例外は投げられない。 - -`Future` トレイトは `ready()` と `result()` というメソッドを持つ `Awaitable` トレイトを実装する。 -これらのメソッドはクライアントからは直接呼ばれず、実行コンテキストからのみ呼ばれる。 - -`Awaitable` トレイトを実装することなくブロックする可能性のある第三者のコードを呼び出すために、以下のように -`blocking` 構文を使うことができる: - - blocking { - potentiallyBlockingCall() - } - -ブロックされたコードは例外を投げるかもしれない。その場合は、呼び出し元に例外が送られる。 - -## 例外処理 - -非同期の計算が処理されない例外を投げた場合、その計算が行われた Future は失敗する。 -失敗した Future は計算値のかわりに `Throwable` のインスタンスを格納する。 -`Future` は、`Throwable` に適用することができる `PartialFunction` を受け取る -`onFailure` コールバックメソッドを提供する。 -以下の特別な例外に対しては異なる処理が行われる: - -1. `scala.runtime.NonLocalReturnControl[_]`。この例外は戻り値に関連する値を保持する。 -典型的にはメソッド本文内の `return` 構文はこの例外を用いた `throw` へと翻訳される。 -この例外を保持するかわりに、関連する値が Future もしくは Promise に保存される。 - -2. `ExecutionException`。`InterruptedException`、`Error`、もしくは `scala.util.control.ControlThrowable` -が処理されないことで計算が失敗した場合に格納される。 -この場合は、処理されなかった例外は `ExecutionException` に保持される。 -これらの例外は非同期計算を実行するスレッド内で再び投げられる。 -この理由は、通常クライアント側で処理されないクリティカルもしくは制御フロー関連の例外が伝搬することを回避し、同時に Future の計算が失敗したことをクライアントに通知するためだ。 - -より正確な意味論の説明は [`NonFatal`](http://www.scala-lang.org/api/current/index.html#scala.util.control.NonFatal$) を参照。 - -## Promise - -これまでの所、`future` メソッドを用いた非同期計算により作成される `Future` オブジェクトのみをみてきた。 -しかし、Future は **Promise** を用いて作成することもできる。 - -Future がリードオンリーのまだ存在しない値に対するプレースホルダ・オブジェクトの一種だと定義されるのに対して、Promise -は書き込み可能で、1度だけ代入できるコンテナで Future を完了させるものだと考えることができる。 -つまり、Promise は `success` メソッドを用いて (約束を「完了させる」ことで) Future を値とともに成功させることができる。 -逆に、Promise は `failure` メソッドを用いて Future を例外とともに失敗させることもできる。 - -Promise の `p` は `p.future` によって返される Future を完了させる。 -この Future は Promise `p` に特定のものだ。実装によっては `p.future eq p` の場合もある。 - -ある計算が値を生産し、別の計算がそれを消費する Producer-Consumer の具体例を使って説明しよう。 -この値の受け渡しは Promise を使って実現している。 - - import scala.concurrent.{ Future, Promise } - import scala.concurrent.ExecutionContext.Implicits.global - - val p = Promise[T]() - val f = p.future - - val producer = Future { - val r = produceSomething() - p success r - continueDoingSomethingUnrelated() - } - - val consumer = Future { - startDoingSomething() - f onSuccess { - case r => doSomethingWithResult() - } - } - -ここでは、まず Promise を作って、その `future` メソッドを用いて完了される `Future` -を取得する。 -まず何らかの計算が実行され、`r` という結果となり、これを用いて Future `f` を完了させ、`p` という約束を果たす。 -ここで注意してほしいのは、`consumer` は `producer` が `continueDoingSomethingUnrelated()` を実行し終えてタスクが完了する前に結果を取得できることだ。 - -前述の通り、Promise は 1度だけ代入できるという意味論を持つ。 -そのため、完了させるのも 1回だけだ。 -既に完了 (もしくは失敗した) Promise に対して `success` を呼び出すと -`IllegalStateException` が投げられる。 - -以下は Promise を失敗させる具体例だ。 - - val p = Promise[T]() - val f = p.future - - val producer = Future { - val r = someComputation - if (isInvalid(r)) - p failure (new IllegalStateException) - else { - val q = doSomeMoreComputation(r) - p success q - } - } - -ここでは `producer` は中間結果 `r` を計算して、それが妥当であるか検証する。 -不正であれば、Promise `p` を例外を用いて完了させることで Promise を失敗させる。 -それ以外の場合は、`producer` は計算を続行して Promise `p` を妥当な結果用いて完了させることで、Future -`f` を完了させる。 - -Promise は潜在的な値である `Try[T]` (失敗した結果の `Failure[Throwable]` -もしくは成功した結果の `Success[T]`) -を受け取る `complete` メソッドを使って完了させることもできる。 - -`success` 同様に、既に完了した Promise に対して `failure` や `complete` を呼び出すと -`IllegalStateException` が投げられる。 - -これまでに説明した Promise の演算とモナディックで副作用を持たない演算を用いて合成した Future -を使って書いたプログラムの便利な特性としてそれらが決定的 (deterministic) であることが挙げられる。 -ここで決定的とは、プログラムで例外が投げられなければ、並列プログラムの実行スケジュールのいかんに関わらずプログラムの結果 -(Future から観測される値) は常に同じものとなるという意味だ。 - -場合によってはクライアントは Promise が既に完了していないときにのみ完了させたいこともある -(例えば、複数の HTTP がそれぞれ別の Future から実行されていて、クライアントは最初の戻ってきた -HTTP レスポンスにのみ興味がある場合で、これは最初に Promise を完了させる Future に対応する)。 -これらの理由のため、Promise には `tryComplete`、`trySuccess`、および `tryFailure` というメソッドがある。 -クライアントはこれらのメソッドを使った場合はプログラムの結果は決定的でなくなり実行スケジュールに依存することに注意するべきだ。 - -`completeWith` メソッドは別の Future を用いて Promise を完了させる。 -渡された Future が完了すると、その Promise も Future の値とともに完了する。 -以下のプログラムは `1` と表示する: - - val f = Future { 1 } - val p = Promise[Int]() - - p completeWith f - - p.future onSuccess { - case x => println(x) - } - -Promise を例外とともに失敗させる場合は、`Throwable` の 3つのサブタイプが特殊扱いされる。 -Promise を失敗させた `Throwable` が `scala.runtime.NonLocalReturnControl` -の場合は、Promise は関連する値によって完了させる。 -Promise を失敗させた `Throwable` が `Error`、`InterruptedException`、もしくは -`scala.util.control.ControlThrowable` の場合は、`Throwable` -は新たな `ExecutionException` の理由としてラッピングされ Promise が失敗させられる。 - -Promise、Future の `onComplete` メソッド、そして `future` -構文を使うことで前述の関数型合成に用いられるコンビネータの全てを実装することができる。 -例えば、2つの Future `f` と `g` を受け取って、(最初に成功した) `f` か `g` -のどちらかを返す `first` という新しいコンビネータを実装したいとする。 -以下のように書くことができる: - - def first[T](f: Future[T], g: Future[T]): Future[T] = { - val p = Promise[T]() - - f onSuccess { - case x => p.tryComplete(x) - } - - g onSuccess { - case x => p.tryComplete(x) - } - - p.future - } - - - - -## ユーティリティ - -並列アプリケーション内における時間の扱いを単純化するために `scala.concurrent` -は `Duration` という抽象体を導入する。 -`Duration` は既に他にもある一般的な時間の抽象化を目的としていない。 -並列ライブラリとともに使うことを目的とするため、`scala.concurrent` パッケージ内に置かれている。 - -`Duration` は時の長さを表す基底クラスで、それは有限でも無限でもありうる。 -有限の時間は `FiniteDuration` クラスによって表され `Long` の長さと `java.util.concurrent.TimeUnit` -によって構成される。 -無限時間も `Duration` を継承し、これは `Duration.Inf` と `Duration.MinusInf` という 2つのインスタンスのみが存在する。 -このライブラリは暗黙の変換のためのいくつかの `Duration` のサブクラスを提供するが、これらは使用されるべきではない。 - -抽象クラスの `Duration` は以下のメソッドを定義する: - -1. 時間の単位の変換 (`toNanos`、`toMicros`、`toMillis`、 -`toSeconds`、`toMinutes`、`toHours`、`toDays`、及び `toUnit(unit: TimeUnit)`)。 -2. 時間の比較 (`<`、`<=`、`>`、および `>=`)。 -3. 算術演算 (`+`、`-`、`*`、`/`、および `unary_-`)。 -4. この時間 `this` と引数として渡された時間の間の最小値と最大値 (`min`、`max`)。 -5. 時間が有限かの検査 (`isFinite`)。 - -`Duration` は以下の方法で作成することができる: - -1. `Int` もしくは `Long` 型からの暗黙の変換する。例: `val d = 100 millis`。 -2. `Long` の長さと `java.util.concurrent.TimeUnit` を渡す。例: `val d = Duration(100, MILLISECONDS)`。 -3. 時間の長さを表す文字列をパースする。例: `val d = Duration("1.2 µs")`。 - -`Duration` は `unapply` メソッドも提供するため、パータンマッチング構文の中から使うこともできる。以下に具体例をみる: - - import scala.concurrent.duration._ - import java.util.concurrent.TimeUnit._ - - // 作成 - val d1 = Duration(100, MILLISECONDS) // from Long and TimeUnit - val d2 = Duration(100, "millis") // from Long and String - val d3 = 100 millis // implicitly from Long, Int or Double - val d4 = Duration("1.2 µs") // from String - - // パターンマッチング - val Duration(length, unit) = 5 millis diff --git a/ja/overviews/core/macros.md b/ja/overviews/core/macros.md deleted file mode 100644 index aa66256fad..0000000000 --- a/ja/overviews/core/macros.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -layout: overview -partof: macros -overview: macros -language: ja -label-color: important -label-text: Experimental -title: マクロ - -discourse: false ---- diff --git a/ja/overviews/core/parallel-collections.md b/ja/overviews/core/parallel-collections.md deleted file mode 100644 index 5b518ebb4d..0000000000 --- a/ja/overviews/core/parallel-collections.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -layout: overview -overview: parallel-collections -partof: parallel-collections -language: ja -title: Scala 並列コレクションライブラリ - -discourse: false ---- diff --git a/ja/overviews/core/reflection.md b/ja/overviews/core/reflection.md deleted file mode 100644 index cf269b9679..0000000000 --- a/ja/overviews/core/reflection.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -layout: overview -partof: reflection -overview: reflection -language: ja -label-color: important -label-text: Experimental -title: リフレクション - -discourse: false ---- \ No newline at end of file diff --git a/ja/overviews/core/string-interpolation.md b/ja/overviews/core/string-interpolation.md deleted file mode 100644 index 1b3e6d34f6..0000000000 --- a/ja/overviews/core/string-interpolation.md +++ /dev/null @@ -1,132 +0,0 @@ ---- -layout: overview -discourse: true -label-color: success -language: ja -label-text: New in 2.10 -overview: string-interpolation -title: 文字列の補間 - -discourse: false ---- - -**Josh Suereth 著**
        -**Eugene Yokota 訳** - -## はじめに - -Scala 2.10.0 より、Scala は文字列の補間 (string interpolation) というデータから文字列を作成する機構を提供する。 -文字列の補間を使ってユーザは加工文字列リテラル (processed string literal) 内に直接変数の参照を埋め込むことができる。具体例で説明しよう: - - val name = "James" - println(s"Hello, $name") // Hello, James - -上の例において、リテラル `s"Hello, $name"` は加工文字列リテラルだ。これはコンパイラがこのリテラルに対して何らかの追加処理を実行するということだ。加工文字リテラルは `"` の前にいくつかの文字を書くことで表記される。文字列の補間は [SIP-11](http://docs.scala-lang.org/sips/pending/string-interpolation.html) によって導入され、実装の詳細もそこに書かれている。 - -## 用例 - -Scala は `s`、`f`、そして `raw` という 3つの補間子 (interpolator) をあらかじめ提供する。 - -### `s` 補間子 - -文字列リテラルの先頭に `s` を追加することで文字列の中で直接変数が使えるようになる。先ほどの例で既にみた機能だ: - - val name = "James" - println(s"Hello, $name") // Hello, James - -この例では、 `s` で加工される文字列の中に `$name` が入れ子になっている。`s` 補間子は `name` 変数の値をこの位置に挿入しなければいけないと知っているため、結果として `Hello, James` という文字列となる。`s` 補間子を使って、スコープ内にあるどの名前でも文字列の中に埋め込むことができる。 - -文字列の補間は任意の式を受け取る事もできる。具体例でみると - - println(s"1 + 1 = ${1 + 1}") - -は文字列 `1 + 1 = 2` と表示する。`${}` を使って任意の式を埋め込むことができる。 - -### `f` 補間子 - -文字列リテラルの先頭に `f` を追加することで、他の言語での `printf` のような簡単な書式付き文字列を作ることができる。`f` 補間子を使った場合は、全ての変数参照は `%d` のように `printf` 形式の書式を指定する必要がある。具体例で説明しよう: - - val height = 1.9d - val name = "James" - println(f"$name%s is $height%2.2f meters tall") // James is 1.90 meters tall - -`f` 補間子は型安全だ。整数のみで動作する書式に `Double` を渡すとコンパイラはエラーを表示する。例えば: - - val height: Double = 1.9d - - scala> f"$height%4d" - :9: error: type mismatch; - found : Double - required: Int - f"$height%4d" - ^ - -`f` 補間子は Java にある文字列の書式付き出力ユーティリティを利用している。`%` 文字の後に書くことが許されている書式は [Formatter の javadoc](http://docs.oracle.com/javase/jp/1.5.0/api/java/util/Formatter.html#detail) に説明されている。変数の後に `%` 文字が無い場合は、`%s` 書式 (`String`) だと仮定される。 - -### `raw` 補間子 - -`raw` 補間子は `s` 補間子に似ているが、違いは文字列リテラル内でエスケープを実行しないことだ。以下の加工文字列をみてみよう: - - scala> s"a\nb" - res0: String = - a - b - -ここで、`s` 補間子は `\n` を改行文字に置換した。`raw` 補間子はそれを行わない。 - - scala> raw"a\nb" - res1: String = a\nb - -`raw` 補間子は `\n` のような式が改行になることを回避したい場合に便利だ。 - -3つのデフォルトの補間子の他にユーザは独自の補間子を定義することもできる。 - -## カスタム補間子 - -Scala では、全ての加工文字列リテラルは簡単なコード変換だ。コンパイラが以下のような形式の文字列リテラルを見つけると - - id"string content" - -これは [`StringContext`](http://www.scala-lang.org/api/current/index.html#scala.StringContext) のインスタンスへのメソッドの呼び出し (`id`) へと変換される。このメソッドは implicit スコープ内で提供することもできる。独自の文字列の補間を定義するには、`StringContext` に新しいメソッドを追加する implicit クラスを作るだけでいい。以下に具体例で説明する: - - // 注意: 実行時のインスタンス化を避けるために AnyVal を継承する。 - // これに関しては値クラスのガイドを参照。 - implicit class JsonHelper(val sc: StringContext) extends AnyVal { - def json(args: Any*): JSONObject = sys.error("TODO - IMPLEMENT") - } - - def giveMeSomeJson(x: JSONObject): Unit = ... - - giveMeSomeJson(json"{ name: $name, id: $id }") - -この例では、文字列の補間を使って JSON リテラル構文に挑戦している。この構文を使うには implicit クラスの `JsonHelper` がスコープにある必要があり、また `json` メソッドを実装する必要がある。注意して欲しいのは書式文字列リテラルが文字列ではなく、`JSONObject` を返す点だ。 - -コンパイラが `json"{ name: $name, id: $id }"` を見つけると、以下の式に書き換える: - - new StringContext("{ name: ", ", id: ", " }").json(name, id) - -さらに、implicit クラスは以下のように書き換える: - - new JsonHelper(new StringContext("{ name: ", ", id: ", " }")).json(name, id) - -そのため、`json` メソッドは生の String 部分と渡される式の値をみることができる。シンプルな (だけどバギーな) 実装を以下に示す: - - implicit class JsonHelper(val sc: StringContext) extends AnyVal { - def json(args: Any*): JSONObject = { - val strings = sc.parts.iterator - val expressions = args.iterator - var buf = new StringBuffer(strings.next) - while(strings.hasNext) { - buf append expressions.next - buf append strings.next - } - parseJson(buf) - } - } - -加工文字列の文字列部分は `StringContext` の `parts` メンバーとして提供される。 -式の値は `json` メソッドに `args` パラメータとして渡される。`json` メソッドは、それを受け取って大きな文字列を生成した後 JSON へとパースする。より洗練された実装は文字列を生成せずに生の文字列と式の値から直接 JSON を構築するだろう。 - -## 制約 - -文字列の補間は現在パターンマッチング文の中では動作しない。この機能は Scala 2.11 リリースを予定している。 diff --git a/ja/overviews/core/value-classes.md b/ja/overviews/core/value-classes.md deleted file mode 100644 index ea034f5d44..0000000000 --- a/ja/overviews/core/value-classes.md +++ /dev/null @@ -1,271 +0,0 @@ ---- -layout: overview -label-color: success -label-text: New in 2.10 -language: ja -overview: value-classes -title: 値クラスと汎用トレイト - -discourse: false ---- - -**Mark Harrah 著**
        -**Eugene Yokota 訳** - -## はじめに - -**値クラス** (value class) は実行時のオブジェクトの割り当てを回避するための Scala の新しい機構だ。 -これは新たに定義付けされる `AnyVal` のサブクラスによって実現される。 -これは [SIP-15](http://docs.scala-lang.org/sips/pending/value-classes.html) にて提案された。 -以下に最小限の値クラスの定義を示す: - - class Wrapper(val underlying: Int) extends AnyVal - -これはただ1つの、public な `val` パラメータを持ち、これが内部での実行時のデータ構造となる。 -コンパイル時の型は `Wrapper` だが、実行時のデータ構造は `Int` だ。 -値クラスは `def` を定義することができるが、`val`、`var`、または入れ子の `trait`、`class`、`object` は許されない: - - class Wrapper(val underlying: Int) extends AnyVal { - def foo: Wrapper = new Wrapper(underlying * 19) - } - -値クラスは汎用トレイト (universal trait) のみを拡張することができる。また、他のクラスは値クラスを拡張することはできない。 -汎用トレイトは `Any` を拡張するトレイトで、メンバとして `def` のみを持ち、初期化を一切行わない。 -汎用トレイトによって値クラスはメソッドの基本的な継承ができるようになるが、これはメモリ割り当てのオーバーヘッドを伴うようにもなる。具体例で説明しよう: - - trait Printable extends Any { - def print(): Unit = println(this) - } - class Wrapper(val underlying: Int) extends AnyVal with Printable - - val w = new Wrapper(3) - w.print() // Wrapper のインスタンスをここでインスタンス化する必要がある - -以下の項で用例、メモリ割り当てが発生するかしないかの詳細、および値クラスの制約を具体例を使ってみていきたい。 - -## 拡張メソッド - -値クラスの使い方の1つに implicit クラス ([SIP-13](http://docs.scala-lang.org/sips/pending/implicit-classes.html)) と組み合わせてメモリ割り当てを必要としない拡張メソッドとして使うというものがある。implicit クラスは拡張メソッドを定義するより便利な構文を提供する一方、値クラスは実行時のオーバーヘッドを無くすことができる。この良い例が標準ライブラリの `RichInt` クラスだ。これは値クラスであるため、`RichInt` のメソッドを使うのに `RichInt` のインスタンスを作る必要はない。 - -`RichInt` から抜粋した以下のコードは、それが `Int` を拡張して `3.toHexString` という式が書けるようにしていることを示す: - - implicit class RichInt(val self: Int) extends AnyVal { - def toHexString: String = java.lang.Integer.toHexString(self) - } - -実行時には、この `3.toHexString` という式は、新しくインスタンス化されるオブジェクトへのメソッド呼び出しではなく、静的なオブジェクトへのメソッド呼び出しと同様のコード (`RichInt$.MODULE$.extension$toHexString(3)`) へと最適化される。 - -## 正当性 - -値クラスのもう1つの使い方として、実行時のメモリ割り当て無しにデータ型同様の型安全性を得るというものがある。 -例えば、距離を表すデータ型はこのようなコードになるかもしれない: - - class Meter(val value: Double) extends AnyVal { - def +(m: Meter): Meter = new Meter(value + m.value) - } - -以下のような 2つの距離を加算するコード - - val x = new Meter(3.4) - val y = new Meter(4.3) - val z = x + y - -は実際には `Meter` インスタンスを割り当てず、組み込みの `Double` 型のみが実行時に使われる。 - -注意: 実際には、case class や拡張メソッドを用いてよりきれいな構文を提供することができる。 - -## メモリ割り当てが必要になるとき - -JVM は値クラスをサポートしないため、Scala は場合によっては値クラスをインスタンス化する必要がある。 -完全な詳細は [SIP-15](http://docs.scala-lang.org/sips/pending/value-classes.html) を参照してほしい。 - -### メモリ割り当ての概要 - -以下の状況において値クラスのインスタンスはインスタンス化される: - - -
          -
        1. 値クラスが別の型として扱われるとき。
        2. -
        3. 値クラスが配列に代入されるとき。
        4. -
        5. パターンマッチングなどにおいて、実行時の型検査を行うとき。
        6. -
        - -### メモリ割り当ての詳細 - -値クラスの値が、汎用トレイトを含む別の型として扱われるとき、値クラスのインスタンスの実体がインスタンス化される必要がある。 -具体例としては、以下の `Meter` 値クラスをみてほしい: - - trait Distance extends Any - case class Meter(val value: Double) extends AnyVal with Distance - -`Distance` 型の値を受け取るメソッドは実体の `Meter` インスタンスが必要となる。 -以下の例では `Meter` クラスはインスタンス化される: - - def add(a: Distance, b: Distance): Distance = ... - add(Meter(3.4), Meter(4.3)) - -`add` のシグネチャが以下のようであれば - - def add(a: Meter, b: Meter): Meter = ... - -メモリ割り当ては必要無い。 -値クラスが型引数として使われる場合もこのルールがあてはまる。 -例えば、`identify` を呼び出すだけでも `Meter` インスタンスの実体が作成されることが必要となる: - - def identity[T](t: T): T = t - identity(Meter(5.0)) - -メモリ割り当てが必要となるもう1つの状況は、配列への代入だ。たとえその値クラスの配列だったとしてもだ。具体例で説明する: - - val m = Meter(5.0) - val array = Array[Meter](m) - -この配列は、内部表現の `Double` だけではなく `Meter` の実体を格納する。 - -最後に、パターンマッチングや `asInstaneOf` のような型検査は値クラスのインスタンスの実体を必要とする: - - case class P(val i: Int) extends AnyVal - - val p = new P(3) - p match { // new P instantiated here - case P(3) => println("Matched 3") - case P(x) => println("Not 3") - } - -## 制約 - -JVM が値クラスという概念をサポートしていないこともあり、値クラスには現在いくつかの制約がある。 -値クラスの実装とその制約の詳細に関しては [SIP-15](http://docs.scala-lang.org/sips/pending/value-classes.html) を参照。 - -### 制約の概要 - -値クラスは … - - -
          -
        1. … ただ1つの public で値クラス以外の型の val パラメータを持つプライマリコンストラクタのみを持つことができる。
        2. -
        3. … specialized な型パラメータを持つことができない。
        4. -
        5. … 入れ子のローカルクラス、トレイト、やオブジェクトを持つことがでない。
        6. -
        7. equalshashCode メソッドを定義することができない。
        8. -
        9. … トップレベルクラスか静的にアクセス可能なオブジェクトのメンバである必要がある。
        10. -
        11. def のみをメンバとして持つことができる。特に、lazy valvarval をメンバとして持つことができない。
        12. -
        13. … 他のクラスによって拡張されることができない。
        14. -
        - -### 制約の具体例 - -この項ではメモリ割り当ての項で取り扱わなかった制約の具体例を色々みていく。 - -コンストラクタのパラメータを複数持つことができない: - - class Complex(val real: Double, val imag: Double) extends AnyVal - -Scala コンパイラは以下のエラーメッセージを生成する: - - Complex.scala:1: error: value class needs to have exactly one public val parameter - class Complex(val real: Double, val imag: Double) extends AnyVal - ^ - -コンストラクタのパラメータは `val` である必要があるため、名前渡しのパラメータは使うことができない: - - NoByName.scala:1: error: `val' parameters may not be call-by-name - class NoByName(val x: => Int) extends AnyVal - ^ - -Scala はコンストラクタのパラメータとして `lazy val` を許さないため、それも使うことができない。 -複数のコンストラクタを持つことができない: - - class Secondary(val x: Int) extends AnyVal { - def this(y: Double) = this(y.toInt) - } - - Secondary.scala:2: error: value class may not have secondary constructors - def this(y: Double) = this(y.toInt) - ^ - -値クラスは `lazy val` や `val` のメンバ、入れ子の `class`、`trait`、や `object` を持つことができない: - - class NoLazyMember(val evaluate: () => Double) extends AnyVal { - val member: Int = 3 - lazy val x: Double = evaluate() - object NestedObject - class NestedClass - } - - Invalid.scala:2: error: this statement is not allowed in value class: private[this] val member: Int = 3 - val member: Int = 3 - ^ - Invalid.scala:3: error: this statement is not allowed in value class: lazy private[this] var x: Double = NoLazyMember.this.evaluate.apply() - lazy val x: Double = evaluate() - ^ - Invalid.scala:4: error: value class may not have nested module definitions - object NestedObject - ^ - Invalid.scala:5: error: value class may not have nested class definitions - class NestedClass - ^ - -以下のとおり、ローカルクラス、トレイト、オブジェクトも許されないことに注意: - - class NoLocalTemplates(val x: Int) extends AnyVal { - def aMethod = { - class Local - ... - } - } - -現在の実装の制約のため、値クラスを入れ子とすることができない: - - class Outer(val inner: Inner) extends AnyVal - class Inner(val value: Int) extends AnyVal - - Nested.scala:1: error: value class may not wrap another user-defined value class - class Outer(val inner: Inner) extends AnyVal - ^ - -また、構造的部分型はメソッドのパラメータや戻り型に値クラスを取ることができない: - - class Value(val x: Int) extends AnyVal - object Usage { - def anyValue(v: { def value: Value }): Value = - v.value - } - - Struct.scala:3: error: Result type in structural refinement may not refer to a user-defined value class - def anyValue(v: { def value: Value }): Value = - ^ - -値クラスは非汎用トレイトを拡張することができない。また、値クラスを拡張することもできない。 - - trait NotUniversal - class Value(val x: Int) extends AnyVal with notUniversal - class Extend(x: Int) extends Value(x) - - Extend.scala:2: error: illegal inheritance; superclass AnyVal - is not a subclass of the superclass Object - of the mixin trait NotUniversal - class Value(val x: Int) extends AnyVal with NotUniversal - ^ - Extend.scala:3: error: illegal inheritance from final class Value - class Extend(x: Int) extends Value(x) - ^ - -2つ目のエラーメッセージは、明示的には値クラスに `final` 修飾子が指定されなくても、それが暗に指定されていることを示している。 - -値クラスが 1つのパラメータしかサポートしないことによって生じるもう1つの制約は、値クラスがトップレベルであるか、静的にアクセス可能なオブジェクトのメンバである必要がある。 -これは、入れ子になった値クラスはそれを内包するクラスへの参照を2つ目のパラメータとして受け取る必要があるからだ。 -そのため、これは許されない: - - class Outer { - class Inner(val x: Int) extends AnyVal - } - - Outer.scala:2: error: value class may not be a member of another class - class Inner(val x: Int) extends AnyVal - ^ - -しかし、これは内包するオブジェクトがトップレベルであるため許される: - - object Outer { - class Inner(val x: Int) extends AnyVal - } diff --git a/ja/overviews/index.md b/ja/overviews/index.md deleted file mode 100644 index a7a33f1c9a..0000000000 --- a/ja/overviews/index.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -layout: guides-index -language: ja -title: ガイドと概要 ---- - -
        -

        コアライブラリ

        -
        - * Scala コレクションライブラリ - * [はじめに](/ja/overviews/collections/introduction.html) - * [可変コレクションおよび不変コレクション](/ja/overviews/collections/overview.html) - * [Traversable トレイト](/ja/overviews/collections/trait-traversable.html) - * [Iterable トレイト](/ja/overviews/collections/trait-iterable.html) - * [列トレイト Seq、IndexedSeq、および LinearSeq](/ja/overviews/collections/seqs.html) - * [集合](/ja/overviews/collections/sets.html) - * [マップ](/ja/overviews/collections/maps.html) - * [具象不変コレクションクラス](/ja/overviews/collections/concrete-immutable-collection-classes.html) - * [具象可変コレクションクラス](/ja/overviews/collections/concrete-mutable-collection-classes.html) - * [配列](/ja/overviews/collections/arrays.html) - * [文字列](/ja/overviews/collections/strings.html) - * [性能特性](/ja/overviews/collections/performance-characteristics.html) - * [等価性](/ja/overviews/collections/equality.html) - * [ビュー](/ja/overviews/collections/views.html) - * [イテレータ](/ja/overviews/collections/iterators.html) - * [コレクションの作成](/ja/overviews/collections/creating-collections-from-scratch.html) - * [Java と Scala 間のコレクションの変換](/ja/overviews/collections/conversions-between-java-and-scala-collections.html) - * [Scala 2.7 からの移行](/ja/overviews/collections/migrating-from-scala-27.html) - * [文字列の補間](/ja/overviews/core/string-interpolation.html) New in 2.10 - * [値クラスと汎用トレイト](/ja/overviews/core/value-classes.html) New in 2.10 - -
        -

        並列および並行プログラミング

        -
        - * [Future と Promise](/ja/overviews/core/futures.html) New in 2.10 - * Scala 並列コレクションライブラリ - * [概要](/ja/overviews/parallel-collections/overview.html) - * [具象並列コレクションクラス](/ja/overviews/parallel-collections/concrete-parallel-collections.html) - * [並列コレクションへの変換](/ja/overviews/parallel-collections/conversions.html) - * [並行トライ](/ja/overviews/parallel-collections/ctries.html) - * [並列コレクションライブラリのアーキテクチャ](/ja/overviews/parallel-collections/architecture.html) - * [カスタム並列コレクションの作成](/ja/overviews/parallel-collections/custom-parallel-collections.html) - * [並列コレクションの設定](/ja/overviews/parallel-collections/configuration.html) - * [性能の測定](/ja/overviews/parallel-collections/performance.html) - -
        -

        メタプログラミング

        -
        - * リフレクション Experimental - * [概要](/ja/overviews/reflection/overview.html) - * [環境、ユニバース、ミラー](/ja/overviews/reflection/environment-universes-mirrors.html) - * [シンボル、構文木、型](/ja/overviews/reflection/symbols-trees-types.html) - * [アノテーション、名前、スコープ、その他](/ja/overviews/reflection/annotations-names-scopes.html) - * [型タグとマニフェスト](/ja/overviews/reflection/typetags-manifests.html) - * [スレッドセーフティ](/ja/overviews/reflection/thread-safety.html) - * マクロ Experimental - * [ユースケース](/ja/overviews/macros/usecases.html) - * [blackbox vs whitebox](/ja/overviews/macros/blackbox-whitebox.html) - * [def マクロ](/ja/overviews/macros/overview.html) - * [準クォート](/ja/overviews/quasiquotes/intro.html) - * [マクロバンドル](/ja/overviews/macros/bundles.html) - * [implicit マクロ](/ja/overviews/macros/implicits.html) - * [抽出子マクロ](/ja/overviews/macros/extractors.html) - * [型プロバイダ](/ja/overviews/macros/typeproviders.html) - * [マクロアノテーション](/ja/overviews/macros/annotations.html) - * [マクロパラダイス](/ja/overviews/macros/paradise.html) - * [ロードマップ](/ja/overviews/macros/roadmap.html) diff --git a/ja/overviews/macros/annotations.md b/ja/overviews/macros/annotations.md deleted file mode 100644 index 225c6acfd6..0000000000 --- a/ja/overviews/macros/annotations.md +++ /dev/null @@ -1,109 +0,0 @@ ---- -layout: overview-large - -language: ja -discourse: false - -partof: macros -num: 9 -outof: 11 - -title: マクロアノテーション ---- -MACRO PARADISE - -**Eugene Burmako 著**
        -**Eugene Yokota 訳** - -マクロアノテーションはマクロパラダイスプラグインからのみ利用可能だ (Scala 2.10.x、2.11.x、2.12.x 系列全て同様)。 -この機能が正式な Scala に入る可能性は、Scala 2.13 には残されているが、一切保証されていない。 -[マクロパラダイス](/ja/overviews/macros/paradise.html)ページの説明にしたがってコンパイラプラグインをダウンロードしてほしい。 - -## 一巡り - -マクロアノテーションは定義レベルでテキスト抽象化を実現する。Scala がマクロだと認識可能な定義であればトップレベルでも入れ子の定義でも、このアノテーションを付けることで (1つまたは複数の) メンバに展開させることができる。マクロパラダイスの以前のバージョンと比較して、2.0 のマクロパラダイスは以下の点を改善した: - -
          -
        1. クラスやオブジェクトだけではなく任意の定義に適用できるようになった。
        2. -
        3. クラスを展開してコンパニオンオブジェクトを変更もしくは新規に生成できるようになった。
        4. -
        - -これでコード生成に関して様々な可能性が広がったと言える。 - -この項では、役に立たないけども例としては便利な、注釈対象をログに書き込むこと以外は何もしないマクロを書いてみよう。 -最初のステップは、`StaticAnnotation` を継承して、`macroTransform` マクロを定義する。 -(この本文の `???` は 2.10.2 以降から使えるものだ。) - - import scala.reflect.macros.Context - import scala.language.experimental.macros - import scala.annotation.StaticAnnotation - - class identity extends StaticAnnotation { - def macroTransform(annottees: Any*) = macro ??? - } - -`macroTransform` マクロは型指定の無い (untyped) 注釈対象を受け取り (Scala には他に記法が無いためこのシグネチャの型は `Any` となる)、単数もしくは複数の結果を生成する (単数の結果はそのまま返せるが、複数の結果の場合はリフレクション API に他に良い記法が無いため `Block` にラッピングして返す)。 - -この時点で、一つの注釈対象に対して単一の結果は分かるが、複数対複数のマッピングがどのようになるのか疑問に思っている方もいるだろう。この過程はルールによって決定される: - -
          -
        1. あるクラスが注釈され、それにコンパニオンがある場合は、両者ともマクロに渡される。 (しかし、逆は真ではない。もしオブジェクトが注釈され、それにコンパニオンクラスがあってもオブジェクトのみが展開される)
        2. -
        3. あるクラス、メソッド、もしくは型のパラメータが注釈される場合は、そのオーナーも展開される。まずは注釈対象、次にオーナー、そして上記のルールに従ってコンパニオンが渡される。
        4. -
        5. 注釈対象は任意の数および種類の構文木に展開することができ、コンパイラはマクロの構文木を結果の構文木に透過的に置換する。
        6. -
        7. あるクラスが同じ名前を持つクラスとオブジェクトに展開する場合は、それらはコンパニオンとなる。これにより、コンパニオンが明示的に宣言されていないクラスにもコンパニオンオブジェクトを生成することができるようになる。
        8. -
        9. トップレベルでの展開は注釈対象の数を、種類、および名前を保持しなくてはいけない。唯一の例外はクラスがクラスと同名のオブジェクトに展開できることだ。その場合は、上記のルールによってそれらは自動的にコンパニオンとなる。
        10. -
        - -以下に、`identity` アノテーションマクロの実装例を示す。 -`@identity` が値か型パラメータに適用された場合のことも考慮に入れる必要があるため、ロジックは少し複雑になっている。コンパイラプラグイン側からは容易に標準ライブラリを変更できないため、このボイラープレートをヘルパー内でカプセル化できなかったため、解法がローテクになっていることは許してほしい。 -(ちなみに、このボイラープレートそのものも適切なアノテーションマクロによって抽象化できるはずなので、将来的にはそのようなマクロが提供できるかもしれない。) - - object identityMacro { - def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = { - import c.universe._ - val inputs = annottees.map(_.tree).toList - val (annottee, expandees) = inputs match { - case (param: ValDef) :: (rest @ (_ :: _)) => (param, rest) - case (param: TypeDef) :: (rest @ (_ :: _)) => (param, rest) - case _ => (EmptyTree, inputs) - } - println((annottee, expandees)) - val outputs = expandees - c.Expr[Any](Block(outputs, Literal(Constant(())))) - } - } - - - - - - - - - - - - - - - - - - - - - - - - -
        コード例表示
        @identity class C(<empty>, List(class C))
        @identity class D; object D(<empty>, List(class D, object D))
        class E; @identity object E(<empty>, List(object E))
        def twice[@identity T]
        -(@identity x: Int) = x * 2
        (type T, List(def twice))
        -(val x: Int, List(def twice))
        - -Scala マクロの精神に則り、マクロアノテーションは柔軟性のために可能な限り型指定を無くし (untyped; マクロ展開前に型検査を必須としないこと)、利便性のために可能な限り型付けた (typed; マクロ展開前に利用可能な型情報を取得すること)。注釈対象は型指定が無いため、後付けでシグネチャ (例えばクラスメンバのリストなど) を変更できる。しかし、Scala マクロを書くということはタイプチェッカと統合するということであり、マクロアノテーションもそれは同じだ。そのため、マクロ展開時には全ての型情報を得ることができる -(例えば、包囲するプログラムに対してリフレクションを使ったり、現行スコープ内から型検査を行ったり、implicit の検索を行うことができる)。 - -## blackbox vs whitebox - -マクロアノテーションは [whitebox](/ja/overviews/macros/blackbox-whitebox.html) である必要がある。 -マクロアノテーションを [blackbox](/ja/overviews/macros/blackbox-whitebox.html) だと宣言すると正しく動作しない。 diff --git a/ja/overviews/macros/blackbox-whitebox.md b/ja/overviews/macros/blackbox-whitebox.md deleted file mode 100644 index a9ff9151c8..0000000000 --- a/ja/overviews/macros/blackbox-whitebox.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -layout: overview-large - -language: ja -discourse: false - -partof: macros -num: 2 -outof: 11 - -title: blackbox vs whitebox ---- -EXPERIMENTAL - -**Eugene Burmako 著**
        -**Eugene Yokota 訳** - -Scala 2.11.x および Scala 2.12.x 系列では、マクロ機能が blackbox と whitebox という二つに分かれることになった。 -この blackbox/whitebox の分化は Scala 2.10.x では実装されていない。Scala 2.10.x 向けのマクロパラダイスでも、これは実装されていない。 - -## マクロの成功 - -マクロが正式な Scala 2.10 リリースの一部になったことで、研究分野でも業界でもプログラマは様々な問題に対して独創的なマクロの利用方法を考えだしていて、我々の当初の期待をはるかに上回る反応を得ている。 - -エコシステムにおけるマクロがあまりにも速く重要なものとなってきているため、Scala 2.10 が実験的機能としてリリースされた数カ月後の Scala 言語チームの会議の中では、Scala 2.12 までにはマクロを標準化して Scala 言語の一人前の機能とすることが決定された。 - -追記 Scala 2.12 までにマクロを安定化させるのはそう生易しいことでは無いことが分かった。この調査を受けて、Scala でメタプログラミングを行うための新たな基盤となる [scala.meta](http://scalameta.org) が生まれ、Scala 2.12 リリースと同時に最初のベータ版を出すことを目指している。これが将来の Scala に入ることになるかもしれない。今の所は、Scala 2.12 ではマクロやリフレクション関連の変更は予定されていない。全機能が Scala 2.10 と 2.11 同様に実験的機能扱いのままで、機能が削除されることもない。本稿が書かれた背景となった状況は変わったが、書かれた内容はまだ生きているのでこのまま読み進んでほしい。 - -マクロには多くの種類があるため、それぞれを注意深く調べて、どれを標準に入れるのかを厳選することを決めた。評価するときに必然として出てきた疑問がいくつかある。何故マクロ機能はこれほどまでに成功したのか? マクロが使われる理由は何か? - -理解しづらいメタプログラミングという概念が def マクロで表現されることで馴染みやすい型付けされたメソッド呼び出しという概念に乗っかることができるからではないか、というのが我々の仮説だ。これによって、ユーザが書くコードは無闇に肥大化したり、読み易さを犠牲とせずにより多くの意味を吸収できるようになった。 - -## blackbox と whitebox マクロ - -しかし、ときとして def マクロは「ただのメソッド呼び出し」という概念を超越することがある。例えば、展開されたマクロは、元のマクロの戻り値の型よりも特化された型を持つ式を返すことが可能だ。Scala 2.10 では、StackOverflow の [Static return type of Scala macros](http://stackoverflow.com/questions/13669974/static-return-type-of-scala-macros) で解説したように、そのような展開は特化された型を保持し続けることができる。 - -この興味深い機能がもたらす柔軟性によって、[偽装型プロバイダ](http://meta.plasm.us/posts/2013/07/11/fake-type-providers-part-2/)、[具現化の拡張](/sips/pending/source-locations.html)、[関数従属性の具現化](/ja/overviews/macros/implicits.html#fundep_materialization)、[抽出子マクロ](https://github.com/paulp/scala/commit/84a335916556cb0fe939d1c51f27d80d9cf980dc)などを可能とするけども、書かれたコードの明確さ (人にとってもマシンにとっても) が犠牲になるという側面がある。 - -普通のメソッド同様に振る舞うマクロと戻り値の型を細別化 (refine) するマクロという決定的な区別を明確にするために、blackbox マクロと whitebox マクロという概念を導入することにした。型シグネチャに忠実に従うマクロは、振る舞いを理解するのに実装を知る必要が無い (ブラックボックスとして扱うことができる) ため、**blackbox マクロ** (blackbox macro) と呼ぶ。 -Scala の型システムを使ってシグネチャを持つことができないマクロは **whitebox マクロ** (whitebox macro) と呼ぶ。(whitebox def マクロもシグネチャは持つが、これらのシグネチャは近似値でしかない) - -blackbox マクロと whitebox マクロの両方とも大切なことは認識しているけども、より簡単に説明したり、規格化したり、サポートしやすい blackbox マクロの方に我々としては多くの信頼を置いている。そのため、標準化されてるマクロには blackbox マクロのみが含まれる予定だ。将来的に whitebox マクロも予定の中に入るかもしれないけども、今のところは何とも言えない。 - -## 区別の成文化 - -まずは 2.11 リリースにおいて、def マクロのシグネチャにおいて blackbox マクロと whitebox マクロを区別して、マクロによって `scalac` が振る舞いを変えられることにすることで標準化への初手とする。これは準備段階の一手で、blackbox も whitebox も Scala 2.11 では実験的機能扱いのままだ。 - -この区別は `scala.reflect.macros.Context` を `scala.reflect.macros.blackbox.Context` と `scala.reflect.macros.whitebox.Context` によって置き換えることで表現する。もしマクロの実装が第一引数として `blackbox.Context` を受け取る定義ならば、それを使う def マクロは blackbox となり、 `whitebox.Context` の方も同様となる。当然のことながら互換性のために素の `Context` も方も存在し続けることになるけども、廃止勧告を出すことで blackbox か whitebox かを選ぶことを推奨していく方向となる。 - -blackbox def マクロは Scala 2.10 の def マクロと異なる扱いとなる。Scala の型検査において以下の制限が加わる: - -1. blackbox マクロが構文木 `x` に展開するとき、展開される式は型注釈 `(x: T)` でラップされる。この `T` は blackbox マクロの宣言された戻り値の型に、マクロ適用時に一貫性を持つように型引数やパス依存性を適用したものとなる。これによって、blackbox マクロを[型プロバイダ](http://meta.plasm.us/posts/2013/07/11/fake-type-providers-part-2/)のための手段としての利用は無効となる。 -1. Scala の型推論アルゴリズムが終わった後でも blackbox マクロの適用に未決定の型パラメータが残る場合、これらの型パラメータは普通のメソッドと同様に強制的に型推論が実行される。これによって blackbox マクロから型推論に影響を与えることが不可能となり、[関数従属性の具現化](/ja/overviews/macros/implicits.html#fundep_materialization)に使うことが無効となる。 -1. blackbox マクロの適用が implicit の候補として使われる場合、implicit 検索がそのマクロを選択するまでは展開は実行されない。これによって [implicit マクロの入手可能性を動的に計算する](/sips/pending/source-locations.html)ことが無効となる。 -1. blackbox マクロの適用がパターンマッチの抽出子として使われる場合、無条件でコンパイラエラーを発生するようにして、マクロで実装された[パターンマッチングのカスタマイズ](https://github.com/paulp/scala/commit/84a335916556cb0fe939d1c51f27d80d9cf980dc)を無効となる。 - -whitebox def マクロは Scala 2.10 での def マクロ同様に動作する。一切の制限が無いため、2.10 のマクロで出来ていたことの全てが 2.11 と 2.12 でも行えるはずだ。 diff --git a/ja/overviews/macros/bundles.md b/ja/overviews/macros/bundles.md deleted file mode 100644 index ce73020b8a..0000000000 --- a/ja/overviews/macros/bundles.md +++ /dev/null @@ -1,50 +0,0 @@ ---- -layout: overview-large -language: ja - -discourse: false - -partof: macros -num: 4 -outof: 11 - -title: マクロバンドル ---- -EXPERIMENTAL - -**Eugene Burmako 著**
        -**Eugene Yokota 訳** - -マクロバンドル (macro bundle) は、Scala 2.11.x および Scala 2.12.x 系列に含まれる機能だ。マクロバンドルは Scala 2.10.x では実装されていない。Scala 2.10.x 向けのマクロパラダイスでも、これは実装されていない。 - -## マクロバンドル - -Scala 2.10.x においてマクロ実装は関数として表されている。コンパイラがマクロ定義の適用を見つけると、マクロ実装を呼び出すという単純なものだ。しかし、実際に使ってみると以下の理由によりただの関数では不十分なことがあることが分かった: - -
          -
        1. 関数に制限されることで複雑なマクロのモジュール化がしづらくなる。マクロのロジックがマクロ実装外のヘルパートレイトに集中していて、マクロ実装がヘルパーをインスタンス化するだけのラッパーになってしまっているのは典型的な例だ。
        2. -
        3. さらに、マクロのパラメータがマクロのコンテキストにパス依存であるため、ヘルパーと実装をつなぐのに特殊なおまじないを必要とする。
        4. -
        - -マクロバンドルは、マクロ実装を -`c: scala.reflect.macros.blackbox.Context` か -`c: scala.reflect.macros.whitebox.Context`をコンストラクタのパラメータとして受け取るクラス内で実装することで、コンテキストをマクロ実装側のシグネチャで宣言しなくても済むようになり、モジュール化を簡単にする。 - - import scala.reflect.macros.blackbox.Context - - class Impl(val c: Context) { - def mono = c.literalUnit - def poly[T: c.WeakTypeTag] = c.literal(c.weakTypeOf[T].toString) - } - - object Macros { - def mono = macro Impl.mono - def poly[T] = macro Impl.poly[T] - } - -## blackbox vs whitebox - -マクロバンドルは、[blackbox](/ja/overviews/macros/blackbox-whitebox.html) と [whitebox](/ja/overviews/macros/blackbox-whitebox.html) -の両方のマクロの実装に使うことができる。マクロバンドルのコンストラクタのパラメータに -`scala.reflect.macros.blackbox.Context` の型を渡せば blackbox マクロになって、 -`scala.reflect.macros.whitebox.Context` ならば whitebox マクロになる。 diff --git a/ja/overviews/macros/extractors.md b/ja/overviews/macros/extractors.md deleted file mode 100644 index fdc9c73fe7..0000000000 --- a/ja/overviews/macros/extractors.md +++ /dev/null @@ -1,86 +0,0 @@ ---- -layout: overview-large -language: ja - -discourse: false - -partof: macros -num: 6 -outof: 11 - -title: 抽出子マクロ ---- -EXPERIMENTAL - -**Eugene Burmako 著**
        -**Eugene Yokota 訳** - -抽出子マクロ (extractor macro) は、Scala 2.11.x および Scala 2.12.x 系列に含まれる機能で、Scala 2.11.0-M5 にて Paul Phillips 氏によって導入された name-based extractor によって可能となった。抽出子マクロは Scala 2.10.x では実装されていない。Scala 2.10.x 向けのマクロパラダイスでも、これは実装されていない。 - -## パターン - -具体例で説明するために、以下のような `unapply` メソッドがあるとする -(話を簡単にするために、被検査体を具象型とするが、テストで示した通りこの抽出子を多相とすることも可能だ): - - def unapply(x: SomeType) = ??? - -呼び出しの対象は (`c.prefix`) と被検査体 (scrutinee; `x` に入るもの) の型を使って -呼び出しごとに unapply の抽出シグネチャを生成するマクロをこれで書くことができ、 -その結果のシグネチャはタイプチェッカに渡される。 - -例えば、以下はパターンマッチに被検査体をそのまま返すマクロの定義だ -(複数の被抽出体を表現するシグネチャを扱うためには [scala/scala#2848](https://github.com/scala/scala/pull/2848) を参照)。 - - def unapply(x: SomeType) = macro impl - def impl(c: Context)(x: c.Tree) = { - q""" - new { - class Match(x: SomeType) { - def isEmpty = false - def get = x - } - def unapply(x: SomeType) = new Match(x) - }.unapply($x) - """ - } - - -ドメインに特化したマッチングの論理を実装するマッチャーはいいとして、その他の所でボイラープレートがかなり多いが、 -typer とのよどみない会話をお膳立てするには全ての部分が必要であると思われる。 -まだ改善の余地はあると思うが、タイプチェッカに手を入れないことには不可能だと思う。 - -このパターンは構造的部分型を使っているが、不思議なことに生成されるコードはリフレクションを使った呼び出しが含まれていない -(これは `-Xlog-reflective-calls` をかけた後で自分でも生成されたコードを読んで二重に検査した)。 -これは謎だが、抽出子マクロに性能ペナルティを受けないということなので、良いニュースだ。 - -と言いたいところだが、残念ながら、値クラスはローカルでは宣言できないため、マッチャーを値クラスにすることはできなかった。 -しかしながら、この制限が将来的に無くなることを願って、無くなり次第タレコミが入るように小鳥を仕組んできた ([neg/t5903e](https://github.com/scala/scala/blob/00624a39ed84c3fd245dd9df7454d4cec4399e13/test/files/neg/t5903e/Macros_1.scala#L1))。 - -## 用例 - -このパターンが特に有用なのは文字列補間子のパターンマッチャーで、泥臭い方法を使わずに変幻自在のパターンマッチを実装できる。 -例えば、準クォートの `unapply` はこれでハードコードしなくてすむようになる: - - def doTypedApply(tree: Tree, fun0: Tree, args: List[Tree], ...) = { - ... - fun.tpe match { - case ExtractorType(unapply) if mode.inPatternMode => - // this hardcode in Typers.scala is no longer necessary - if (unapply == QuasiquoteClass_api_unapply) macroExpandUnapply(...) - else doTypedUnapply(tree, fun0, fun, args, mode, pt) - } - } - -実装の大まかな方針としては、`c.prefix` を分解する抽出子マクロを書いて、`StringContext` のパーツを解析して、上記のコードと同様のマッチャーを生成すればいい。 - -この用例や他の抽出子マクロの用例の実装は、 -[run/t5903a](https://github.com/scala/scala/tree/00624a39ed84c3fd245dd9df7454d4cec4399e13/test/files/run/t5903a)、 -[run/t5903b](https://github.com/scala/scala/tree/00624a39ed84c3fd245dd9df7454d4cec4399e13/test/files/run/t5903b)、 -[run/t5903c](https://github.com/scala/scala/tree/00624a39ed84c3fd245dd9df7454d4cec4399e13/test/files/run/t5903c)、 -[run/t5903d](https://github.com/scala/scala/tree/00624a39ed84c3fd245dd9df7454d4cec4399e13/test/files/run/t5903d) -などのテストケースを参照してほしい。 - -## blackbox vs whitebox - -抽出子マクロは [whitebox](/ja/overviews/macros/blackbox-whitebox.html) である必要がある。 -抽出子マクロを [blackbox](/ja/overviews/macros/blackbox-whitebox.html) だと宣言すると正しく動作しない。 diff --git a/ja/overviews/macros/implicits.md b/ja/overviews/macros/implicits.md deleted file mode 100644 index ab4ffdbdf5..0000000000 --- a/ja/overviews/macros/implicits.md +++ /dev/null @@ -1,139 +0,0 @@ ---- -layout: overview-large - -language: ja -discourse: false - -partof: macros -num: 5 -outof: 11 - -title: implicit マクロ ---- -EXPERIMENTAL - -**Eugene Burmako 著**
        -**Eugene Yokota 訳** - -implicit マクロは Scala 2.10.0 以降にある実験的機能だが、クリティカルなバグ修正があったため 2.10.2 以降で完全に動作するようになった。 -2.10.x と 2.11 のどちらでも、implicit マクロを使うのにマクロパラダイスは必要ない。 - -implicit マクロの拡張の 1つである関数従属性の具現化 (fundep materialization) は、2.10.0 から 2.10.4 からは使えないが、 -[マクロパラダイス](/ja/overviews/macros/paradise.html)、2.10.5、と 2.11.x で実装された。 -2.10.0 から 2.10.4 において関数従属性の具現化を拡張するのにもマクロパラダイスが必要となるため、その関数従属性の具現化を使うためにはユーザのビルドにもマクロパラダイスを含めなければいけないことに注意してほしい。 -しかし、関数従属性の具現化が展開した後ならば、その結果のコードを参照するのにはコンパイル時でも実行時でもマクロパラダイスは必要ない。 -また、2.10.5 では関数従属性の具現化の展開にはマクロパラダイスは必要ないが、ユーザ側で -Yfundep-materialization というコンパイラフラグを有効にする必要がある。 - -## implicit マクロ - -### 型クラス - -以下の例ではデータの表示を抽象化する `Showable` という型クラスを定義する。 -`show` メソッドは、明示的なパラメータとしてターゲット、そして暗黙のパラメータとして `Showable` のインスタンスという 2つのパラメータを受け取る: - - trait Showable[T] { def show(x: T): String } - def show[T](x: T)(implicit s: Showable[T]) = s.show(x) - -このように宣言された後、`show` はターゲットのみを渡すことで呼び出すことができる。 -もう一つのパラメータは `scalac` が call site のスコープ内からターゲットの型に対応する型クラスのインスタンスを導き出そうとする。もしスコープ内にマッチする暗黙の値があれば、それが推論されコンパイルは成功する。見つからなければ、コンパイルエラーが発生する。 - - implicit object IntShowable extends Showable[Int] { - def show(x: Int) = x.toString - } - show(42) // "42" - show("42") // compilation error - -### 蔓延するボイラープレート - -特に Scala における型クラスにおいてよく知られている問題の一つとして似た型のインスタンス定義が往々にして非常に似通ったものになりやすく、ボイラープレートコードの蔓延につながることが挙げられる。 - -例えば、多くのオブジェクトの場合において整形表示はクラス名を表示した後フィールドを表示するという形になる。 -これは簡潔な方法だが、実際にやってみると簡潔に実装することが大変難しく、繰り返し似たコードを書くはめになる。 - - class C(x: Int) - implicit def cShowable = new Showable[C] { - def show(c: C) = "C(" + c.x + ")" - } - - class D(x: Int) - implicit def dShowable = new Showable[D] { - def show(d: D) = "D(" + d.x + ")" - } - -このユースケースに限ると実行時リフレクションを用いて実装することができるが、リフレクションは往々にして型消去のために不正確すぎるか、オーバーヘッドのために遅すぎることが多い。 - -Lars Hupel 氏が紹介した [`TypeClass` 型クラステクニック](http://typelevel.org/blog/2013/06/24/deriving-instances-1.html)のような型レベルプログラミングに基づいたジェネリックプログラミングという方法もあるが、やはりこれも手書きの型クラスのインスタンスに比べると性能が劣化するのが現状だ。 - -### implicit の具現化 - -implicit マクロを用いることで、型クラスのインスタンスを手書きで定義する必要を無くし、性能を落とさずにボイラプレートを一切無くすことができる。 - - trait Showable[T] { def show(x: T): String } - object Showable { - implicit def materializeShowable[T]: Showable[T] = macro ... - } - -複数のインスタンス定義を書く代わりに、プログラマは、`Showable` 型クラスのコンパニオンオブジェクト内に `materializeShowable` マクロを一度だけ定義する。これにより `Showable` のインスタンスが明示的に提供されなければ materializer が呼び出される。呼び出された materializer は `T` の型情報を取得して、適切な `Showable` 型クラスのインスタンスを生成する。 - -implicit マクロの長所は、それが既存の implicit 検索のインフラに自然と溶け込むことだ。 -Scala implicit の標準機能である複数のパラメータや重複したインスタンスなどもプログラマ側は特に何もせずに implicit マクロから使うことができる。例えば、整形表示可能な要素を持つリストのためのマクロを使わない整形表示を実装して、それをマクロベースの具現化に統合させるといったことも可能だ。 - - implicit def listShowable[T](implicit s: Showable[T]) = - new Showable[List[T]] { - def show(x: List[T]) = { x.map(s.show).mkString("List(", ", ", ")") - } - } - show(List(42)) // prints: List(42) - -この場合、必須のインスタンスである `Showable[Int]` は先に定義した具現化マクロによって生成される。つまり、マクロを implicit にすることで型クラスインスタンスの具現化を自動化すると同時にマクロを使わない implicit もシームレスに統合することができる。 - -  - -## 関数従属性の具現化 - -### 動機となった具体例 - -関数従属性 (functional dependency; fundep) の具現化が生まれるキッカケとなったのは Miles Sabin さんと氏の [shapeless](https://github.com/milessabin/shapeless) ライブラリだ。2.0.0 以前のバージョンの shapeless において Miles は型間の同型射 (isomorphism) を表す `Iso` トレイトを定義していた。例えば `Iso` を使ってケースクラスとタプル間を投射することができる (実際には shapeless は `Iso` を用いてケースクラスと HList の変換を行うが、話を簡略化するためにここではタプルを用いる)。 - - trait Iso[T, U] { - def to(t: T) : U - def from(u: U) : T - } - - case class Foo(i: Int, s: String, b: Boolean) - def conv[C, L](c: C)(implicit iso: Iso[C, L]): L = iso.from(c) - - val tp = conv(Foo(23, "foo", true)) - tp: (Int, String, Boolean) - tp == (23, "foo", true) - -ここで我々は `Iso` のための implicit materializer を書こうとしたが、壁にあたってしまった。 -`conv` のような関数の適用を型検査するときに scala は型引数の `L` を推論しなければいけないが、お手上げ状態になってしまう (ドメインに特化した知識なので仕方がない)。 -結果として、`Iso[C, L]` を合成する implicit マクロを定義しても、scalac はマクロ展開時に `L` を `Nothing` だと推論してしまい、全てが崩れてしまう。 - -### 提案 - -[https://github.com/scala/scala/pull/2499](https://github.com/scala/scala/pull/2499) が示すとおり、上記の問題の解法は非常にシンプルでエレガントなものだ。 - -Scala 2.10 においてはマクロの適用は全ての型引数が推論されるまでは展開されない。しかし、そうする必要は特に無い。 -タイプチェッカはできる所まで推論して (この例の場合、`C` は `Foo` と推論され、`L` は未定となる) そこで一旦停止する。その後マクロを展開して、展開された型を補助にタイプチェッカは再び以前未定だった型引数の型検査を続行する。Scala 2.11.0 ではそのように実装されている。 - -このテクニックを具体例で例示したものとして [files/run/t5923c](https://github.com/scala/scala/tree/7b890f71ecd0d28c1a1b81b7abfe8e0c11bfeb71/test/files/run/t5923c) テストがある。 -全てがすごくシンプルになっていることに注意してほしい。implicit マクロの `materializeIso` は最初の型引数だけを使って展開コードを生成する。 -型推論は自動的に行われるので、(推論することができなかった) 2つ目の型引数のことは分からなくてもいい。 - -ただし、`Nothing` に関してはまだ[おかしい制限](https://github.com/scala/scala/blob/7b890f71ecd0d28c1a1b81b7abfe8e0c11bfeb71/test/files/run/t5923a/Macros_1.scala)があるので注意する必要がある。 - -## blackbox vs whitebox - -本稿の前半で紹介した素の具現化は [blackbox](/ja/overviews/macros/blackbox-whitebox.html) と [whitebox](/ja/overviews/macros/blackbox-whitebox.html) のどちらでもいい。 - -blackbox な具現化と whitebox な具現化には大きな違いが一つある。blackbox な implicit マクロの展開 (例えば、明示的な `c.abort` の呼び出しや展開時の型検査の失敗) -はコンパイルエラーとなるが、whitebox な implicit マクロの展開は、実際のエラーはユーザ側には報告されずに現在の implicit 検索から implicit の候補が抜けるだけになる。 -これによって、blackbox implicit マクロの方がエラー報告という意味では良いけども、whitebox implicit マクロの方が動的に無効化できるなどより柔軟性が高いというトレードオフが生じる。 - -関数従属性の具現化は [whitebox](/ja/overviews/macros/blackbox-whitebox.html) マクロじゃないと動作しないことにも注意。 -関数従属性の具現化を [blackbox](/ja/overviews/macros/blackbox-whitebox.html) だと宣言すると正しく動作しない。 - - - diff --git a/ja/overviews/macros/inference.md b/ja/overviews/macros/inference.md deleted file mode 100644 index b3330df05c..0000000000 --- a/ja/overviews/macros/inference.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -layout: overview-large -language: ja - -discourse: false - -title: 型推論補助マクロ ---- -EXPERIMENTAL - -**Eugene Burmako 著**
        -**Eugene Yokota 訳** - -このページは [/ja/overviews/macros/implicits.html](/ja/overviews/macros/implicits.html) に移動した。 diff --git a/ja/overviews/macros/overview.md b/ja/overviews/macros/overview.md deleted file mode 100644 index c61c617c78..0000000000 --- a/ja/overviews/macros/overview.md +++ /dev/null @@ -1,377 +0,0 @@ ---- -layout: overview-large -language: ja - -discourse: false - -partof: macros -num: 3 -outof: 11 - -title: def マクロ ---- -EXPERIMENTAL - -**Eugene Burmako 著**
        -**Eugene Yokota 訳** - -def マクロは Scala のバージョン 2.10.0 より追加された実験的機能だ。 -def マクロ機能の一部が、徹底した仕様が書かれることを条件に将来の Scala のいつかに安定化することが仮予定されている。 - -追記 このガイドは Scala 2.10.0 向けに書かれたもので、現在は Scala 2.11.x 系リリースサイクルのまっただ中なので当然本稿の内容が古くなっている。 -しかしながら、このガイドが廃れたかと言うとそうとも言えなくて、ここで書かれていることの全ては Scala 2.10.x と Scala 2.11.x -の両方で動作するため目を通す価値はあるはずだ。 -これを読んだ後で、[準クォート](/overviews/quasiquotes/intro.html)と -[マクロバンドル](/ja/overviews/macros/bundles.html)のガイドからマクロ定義を簡略化する最新情報を仕入れてほしい。 -さらに詳しい具体例を調べるには [macro workshop](https://github.com/scalamacros/macrology201) -も参考にしてほしい。 - -## 直観 - -以下がマクロ定義のプロトタイプだ: - - def m(x: T): R = macro implRef - -一見するとマクロ定義は普通の関数定義と変わらないが、違いが 1つあってそれは本文が条件付きキーワード `macro` で始まり、次に静的なマクロ実装メソッドの識別子が続くことだ。この識別子は qualify されていてもいい (つまり、`.` で区切ってスコープ外の識別子を参照してもいいということ)。 - -もし、型検査時にコンパイラがマクロ適用 `m(args)` を見つけると、コンパイラはそのマクロに対応するマクロ実装メソッドに `args` の抽象構文木を引数として渡して呼び出すことによってマクロ適用を展開する。マクロ実装の戻り値もまた抽象構文木で、コールサイトにおいてそれはインライン化され、それが再び型検査される。 - -以下のコードはマクロ実装 `Asserts.assertImpl` を参照するマクロ定義 `assert` を宣言する (`assertImpl` の定義も後でみる): - - def assert(cond: Boolean, msg: Any) = macro Asserts.assertImpl - -そのため、`assert(x < 10, "limit exceeded")` の呼び出しはコンパイル時における以下の呼び出しにつながる: - - assertImpl(c)(<[ x < 10 ]>, <[ “limit exceeded” ]>) - -ただし、`c` はコールサイトにおいてコンパイラが収集した情報を格納したコンテキスト引数で、残りの 2つの引数は、2つの式 `x < 10` と `"limit exceeded"` を表す抽象構文木。 - -本稿においては、式 `expr` を表す抽象構文木を `<[ expr ]>` と表記する。今回提唱された Scala 言語の拡張にはこの表記法に対応するものは含まれていない。実際には、構文木は `scala.reflect.api.Trees` トレイト内の型から構築され、上記の 2つの式は以下のようになる: - - Literal(Constant("limit exceeded")) - - Apply( - Select(Ident(TermName("x")), TermName("$less"), - List(Literal(Constant(10))))) - -ここに `assert` マクロの実装の一例を載せる: - - import scala.reflect.macros.Context - import scala.language.experimental.macros - - object Asserts { - def raise(msg: Any) = throw new AssertionError(msg) - def assertImpl(c: Context) - (cond: c.Expr[Boolean], msg: c.Expr[Any]) : c.Expr[Unit] = - if (assertionsEnabled) - <[ if (!cond) raise(msg) ]> - else - <[ () ]> - } - -この例が示すとおり、マクロ実装はいくつかのパラメータリストを持つ。まず `scala.reflect.macros.Context` 型の パラメータを 1つ受け取るリスト。次に、マクロ定義のパラメータと同じ名前を持つパラメータを列挙したリスト。しかし、もとのマクロのパラメータの型 `T` の代わりにマクロ実装のパラメータは `c.Expr[T]` 型を持つ。`Expr[T]` は `Context` に定義され `T` 型の抽象構文木をラッピングする。マクロ実装 `assertImpl` の戻り型もまたラッピングされた構文木で、`c.Expr[Unit]` 型を持つ。 - -また、マクロは実験的で、高度な機能だと考えられているため、マクロを定義するにはその機能を明示的に有効化する必要があることに注意してほしい。 -これは、ファイルごとに `import scala.language.experimental.macros` と書くか、コンパイルごとに (コンパイラスイッチとして) `-language:experimental.macros` を用いることで行われる。 -しかし、ユーザ側は特にコンパイラスイッチや追加の設定などで有効化しなくても普通のメソッド同様に見えるし、普通のメソッド同様に使うことができる。 - -### 多相的なマクロ - -マクロ定義とマクロ実装の両方ともジェネリックにすることができる。もしマクロ実装に型パラメータがあれば、マクロ定義の本文において実際の型引数が明示的に渡される必要がある。実装内での型パラメータは context bounds の `WeakTypeTag` と共に宣言することができる。その場合、適用サイトでの実際の型引数を記述した型タグがマクロの展開時に一緒に渡される。 - -以下のコードはマクロ実装 `QImpl.map` を参照するマクロ定義 `Queryable.map` を宣言する: - - class Queryable[T] { - def map[U](p: T => U): Queryable[U] = macro QImpl.map[T, U] - } - - object QImpl { - def map[T: c.WeakTypeTag, U: c.WeakTypeTag] - (c: Context) - (p: c.Expr[T => U]): c.Expr[Queryable[U]] = ... - } - -ここで、型が `Queryable[String]` である値 `q` があるとして、そのマクロ呼び出し - - q.map[Int](s => s.length) - -を考える。この呼び出しは以下の reflective なマクロ呼び出しに展開される。 - - QImpl.map(c)(<[ s => s.length ]>) - (implicitly[WeakTypeTag[String]], implicitly[WeakTypeTag[Int]]) - -## 完全な具体例 - -この節ではコンパイル時に文字列を検査して形式を適用する `printf` マクロを具体例として、最初から最後までの実装をみていく。 -説明を簡略化するために、ここではコンソールの Scala コンパイラを用いるが、後に説明があるとおりマクロは Maven や sbt からも使える。 - -マクロを書くには、まずマクロの窓口となるマクロ定義から始める。 -マクロ定義はシグネチャに思いつくまま好きなものを書ける普通の関数だ。 -しかし、その本文は実装への参照のみを含む。 -前述のとおり、マクロを定義するは `scala.language.experimental.macros` をインポートするか、特殊なコンパイラスイッチ `-language:experimental.macros` を用いて有効化する必要がある。 - - import scala.language.experimental.macros - def printf(format: String, params: Any*): Unit = macro printf_impl - -マクロ実装はそれを使うマクロ定義に対応する必要がある (通常は 1つだが、複数のマクロ定義を宣言することもできる)。簡単に言うと、マクロ定義のシグネチャ内の全ての型 `T` のパラメータはマクロ実装のシグネチャ内では `c.Expr[T]` となる必要がある。このルールの完全なリストはかなり込み入ったものだが、これは問題とならない。もしコンパイラが気に入らなければ、エラーメッセージに期待されるシグネチャを表示するからだ。 - - import scala.reflect.macros.Context - def printf_impl(c: Context)(format: c.Expr[String], params: c.Expr[Any]*): c.Expr[Unit] = ... - -コンパイラ API は `scala.reflect.macros.Context` から使うことができる。そのうち最も重要な部分であるリフレクション API は `c.universe` から使える。 -よく使われる多くの関数や型を含むため、`c.universe._` をインポートするのが慣例となっている: - - import c.universe._ - -まずマクロは渡された書式文字列をパースする必要がある。 -マクロはコンパイル時に実行されるため、値ではなく構文木に対してはたらく。 -そのため、`printf` マクロの書式文字列のパラメータは `java.lang.String` 型のオブジェクトではなくコンパイル時リテラルとなる。 -また、`printf(get_format(), ...)` だと `format` は文字列リテラルではなく関数の適用を表す AST であるため、以下のコードでは動作しない。 - - val Literal(Constant(s_format: String)) = format.tree - -典型的なマクロは Scala のコードを表す AST (抽象構文木) を作成する必要がある。(このマクロも例に漏れない) -Scala コードの生成については[リフレクションの概要](http://docs.scala-lang.org/ja/overviews/reflection/overview.html)を参照してほしい。AST の作成の他に以下のコードは型の操作も行う。 -`Int` と `String` に対応する Scala 型をどうやって取得しているのかに注目してほしい。 -リンクしたリフレクションの概要で型の操作の詳細を説明する。 -コード生成の最終ステップでは、全ての生成されたコードを `Block` へと組み合わせる。 -`reify` は AST を簡単に作成する方法を提供する。 - - val evals = ListBuffer[ValDef]() - def precompute(value: Tree, tpe: Type): Ident = { - val freshName = TermName(c.fresh("eval$")) - evals += ValDef(Modifiers(), freshName, TypeTree(tpe), value) - Ident(freshName) - } - - val paramsStack = Stack[Tree]((params map (_.tree)): _*) - val refs = s_format.split("(?<=%[\\w%])|(?=%[\\w%])") map { - case "%d" => precompute(paramsStack.pop, typeOf[Int]) - case "%s" => precompute(paramsStack.pop, typeOf[String]) - case "%%" => Literal(Constant("%")) - case part => Literal(Constant(part)) - } - - val stats = evals ++ refs.map(ref => reify(print(c.Expr[Any](ref).splice)).tree) - c.Expr[Unit](Block(stats.toList, Literal(Constant(())))) - -以下のコードは `printf` マクロの完全な定義を表す。 -追随するには、空のディレクトリを作り、コードを `Macros.scala` という名前の新しいファイルにコピーする。 - - import scala.reflect.macros.Context - import scala.collection.mutable.{ListBuffer, Stack} - - object Macros { - def printf(format: String, params: Any*): Unit = macro printf_impl - - def printf_impl(c: Context)(format: c.Expr[String], params: c.Expr[Any]*): c.Expr[Unit] = { - import c.universe._ - val Literal(Constant(s_format: String)) = format.tree - - val evals = ListBuffer[ValDef]() - def precompute(value: Tree, tpe: Type): Ident = { - val freshName = TermName(c.fresh("eval$")) - evals += ValDef(Modifiers(), freshName, TypeTree(tpe), value) - Ident(freshName) - } - - val paramsStack = Stack[Tree]((params map (_.tree)): _*) - val refs = s_format.split("(?<=%[\\w%])|(?=%[\\w%])") map { - case "%d" => precompute(paramsStack.pop, typeOf[Int]) - case "%s" => precompute(paramsStack.pop, typeOf[String]) - case "%%" => Literal(Constant("%")) - case part => Literal(Constant(part)) - } - - val stats = evals ++ refs.map(ref => reify(print(c.Expr[Any](ref).splice)).tree) - c.Expr[Unit](Block(stats.toList, Literal(Constant(())))) - } - } - -`printf` マクロを使うには、同じディレクトリ内に別のファイル `Test.scala` を作って以下のコードをコピーする。 -マクロを使用するのは関数を呼び出すのと同じぐらいシンプルであることに注目してほしい。`scala.language.experimental.macros` をインポートする必要も無い。 - - object Test extends App { - import Macros._ - printf("hello %s!", "world") - } - -マクロ機構の重要な一面は別コンパイルだ。マクロ展開を実行するためには、コンパイラはマクロ実装を実行可能な形式で必要とする。そのため、マクロ実装はメインのコンパイルを行う前にコンパイルされている必要がある。 -これをしないと、以下のようなエラーをみることになる: - - ~/Projects/Kepler/sandbox$ scalac -language:experimental.macros Macros.scala Test.scala - Test.scala:3: error: macro implementation not found: printf (the most common reason for that is that - you cannot use macro implementations in the same compilation run that defines them) - pointing to the output of the first phase - printf("hello %s!", "world") - ^ - one error found - - ~/Projects/Kepler/sandbox$ scalac Macros.scala && scalac Test.scala && scala Test - hello world! - -## コツとトリック - -### コマンドライン Scala コンパイラを用いてマクロを使う - -このシナリオは前節で説明したとおりだ。つまり、マクロとそれを使用するコードを別に呼び出した `scalac` によってコンパイルすることで、全てうまくいくはずだ。REPL をつかっているなら、さらに都合がいい。なぜなら REPL はそれぞれの行を独立したコンパイルとして扱うため、マクロを定義してすぐに使うことができる。 - -  -### Maven か sbt を用いてマクロを使う - -本稿での具体例では最もシンプルなコマンドラインのコンパイルを使っているが、マクロは Maven や sbt などのビルドツールからも使うことができる。完結した具体例としては [https://github.com/scalamacros/sbt-example](https://github.com/scalamacros/sbt-example) か [https://github.com/scalamacros/maven-example](https://github.com/scalamacros/maven-example) を見てほしいが、要点は以下の 2点だ: - -
          -
        • マクロは、scala-reflect.jar をライブラリ依存性として必要とする。
        • -
        • 別コンパイル制約により、マクロは別のプロジェクト内で定義する必要がある。
        • -
        - -### Scala IDE か Intellij IDEA を用いてマクロを使う - -別プロジェクトに分かれている限り、Scala IDE と Intellij IDEA の両方において、マクロは正しく動作することが分かっている。 - -### マクロのデバッグ - -マクロのデバッグ、すなわちマクロ展開を駆動している論理のデバッグは比較的容易だ。マクロはコンパイラ内で展開されるため、デバッガ内でコンパイラを実行するだけでいい。そのためには、以下を実行する必要がある: - -
          -
        1. デバッグ設定のクラスパスに Scala home の lib ディレクトリ内の全て (!) のライブラリを追加する。(これは、scala-library.jarscala-reflect.jar、そして scala-compiler.jar の jar ファイルを含む。
        2. -
        3. scala.tools.nsc.Main をエントリーポイントに設定する。
        4. -
        5. JVM のシステムプロパティに -Dscala.usejavacp=true を渡す (とても重要!)
        6. -
        7. コンパイラのコマンドラインの引数を -cp <マクロのクラスへのパス> Test.scala
        8. に設定する。ただし、Test.scala は展開されるマクロの呼び出しを含むテストファイルとする。 -
        - -上の手順をふめば、マクロ実装内にブレークポイントを置いてデバッガを起動できるはずだ。 - -ツールによる特殊なサポートが本当に必要なのはマクロ展開の結果 (つまり、マクロによって生成されたコード) のデバッグだ。このコードは手動で書かれていないため、ブレークポイントを設置することはできず、ステップ実行することもできない。Scala IDE と Intellij IDEA のチームはいずれそれぞれのデバッガにこのサポートを追加することになると思うが、それまでは展開されたマクロをデバッグする唯一の方法は `-Ymacro-debug-lite` という print を使った診断だけだ。これは、マクロによって生成されたコードを表示して、また生成されたコードの実行を追跡して println する。 - -### 生成されたコードの検査 - -`-Ymacro-debug-lite` を用いることで展開されたコードを準 Scala 形式と生の AST 形式の両方でみることができる。それぞれに利点があり、前者は表層的な解析に便利で、後者はより詳細なデバッグに不可欠だ。 - - ~/Projects/Kepler/sandbox$ scalac -Ymacro-debug-lite Test.scala - typechecking macro expansion Macros.printf("hello %s!", "world") at - source-C:/Projects/Kepler/sandbox\Test.scala,line-3,offset=52 - { - val eval$1: String = "world"; - scala.this.Predef.print("hello "); - scala.this.Predef.print(eval$1); - scala.this.Predef.print("!"); - () - } - Block(List( - ValDef(Modifiers(), TermName("eval$1"), TypeTree().setType(String), Literal(Constant("world"))), - Apply( - Select(Select(This(TypeName("scala")), TermName("Predef")), TermName("print")), - List(Literal(Constant("hello")))), - Apply( - Select(Select(This(TypeName("scala")), TermName("Predef")), TermName("print")), - List(Ident(TermName("eval$1")))), - Apply( - Select(Select(This(TypeName("scala")), TermName("Predef")), TermName("print")), - List(Literal(Constant("!"))))), - Literal(Constant(()))) - -### 捕獲されない例外を投げるマクロ - -マクロが捕獲されない例外を投げるとどうなるだろうか?例えば、`printf` に妥当ではない入力を渡してクラッシュさせてみよう。 -プリントアウトが示すとおり、特に劇的なことは起きない。コンパイラは自身を行儀の悪いマクロから守る仕組みになっているため、スタックトレースのうち関係のある部分を表示してエラーを報告するだけだ。 - - ~/Projects/Kepler/sandbox$ scala - Welcome to Scala version 2.10.0-20120428-232041-e6d5d22d28 (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_25). - Type in expressions to have them evaluated. - Type :help for more information. - - scala> import Macros._ - import Macros._ - - scala> printf("hello %s!") - :11: error: exception during macro expansion: - java.util.NoSuchElementException: head of empty list - at scala.collection.immutable.Nil$.head(List.scala:318) - at scala.collection.immutable.Nil$.head(List.scala:315) - at scala.collection.mutable.Stack.pop(Stack.scala:140) - at Macros$$anonfun$1.apply(Macros.scala:49) - at Macros$$anonfun$1.apply(Macros.scala:47) - at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:237) - at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:237) - at scala.collection.IndexedSeqOptimized$class.foreach(IndexedSeqOptimized.scala:34) - at scala.collection.mutable.ArrayOps.foreach(ArrayOps.scala:39) - at scala.collection.TraversableLike$class.map(TraversableLike.scala:237) - at scala.collection.mutable.ArrayOps.map(ArrayOps.scala:39) - at Macros$.printf_impl(Macros.scala:47) - - printf("hello %s!") - ^ - -### 警告とエラーの報告 - -ユーザと対話するための正式な方法は `scala.reflect.macros.FrontEnds` のメソッドを使うことだ。 -`c.error` はコンパイルエラーを報告し、`c.info` は警告を発令し、`c.abort` はエラーを報告しマクロの実行を停止する。 - - scala> def impl(c: Context) = - c.abort(c.enclosingPosition, "macro has reported an error") - impl: (c: scala.reflect.macros.Context)Nothing - - scala> def test = macro impl - defined term macro test: Any - - scala> test - :32: error: macro has reported an error - test - ^ - -[SI-6910](https://issues.scala-lang.org/browse/SI-6910) に記述されているとおり、現時点ではある位置から複数の警告やエラーの報告はサポートされていないことに注意してほしい。そのため、ある位置で最初のエラーか警告だけが報告され他は失くなってしまう。(ただし、同じ位置で後から報告されてもエラーは警告よりも優先される) - -  -### より大きなマクロを書く - -マクロ実装が実装メソッドの本文におさまりきらなくなって、モジュール化の必要性が出てくると、コンテキストパラメータを渡して回る必要があることに気付くだろう。マクロを定義するのに必要なもののほとんどがこのコンテキストにパス依存しているからだ。 - -1つの方法としては `Context` 型のパラメータを受け取るクラスを書いて、マクロ実装をそのクラス内のメソッドに分けるという方法がある。これは一見自然でシンプルにみえるが、実は正しく書くのは難しい。以下に典型的なコンパイルエラーを示す。 - - scala> class Helper(val c: Context) { - | def generate: c.Tree = ??? - | } - defined class Helper - - scala> def impl(c: Context): c.Expr[Unit] = { - | val helper = new Helper(c) - | c.Expr(helper.generate) - | } - :32: error: type mismatch; - found : helper.c.Tree - (which expands to) helper.c.universe.Tree - required: c.Tree - (which expands to) c.universe.Tree - c.Expr(helper.generate) - ^ - -このコードの問題はパス依存型のミスマッチだ。同じ `c` を使って helper を構築したにもかかわらず、Scala コンパイラは `impl` の `c` が `Helper` の `c` と同じものであることが分からない。 - -幸いなことに、少し助けてやるだけでコンパイラは何が起こっているのか気付くことができる。様々ある解法の1つは細別型 (refinement type) を使うことだ。以下の例はそのアイディアの最も簡単な例だ。例えば、`Context` から `Helper` への暗黙の変換を書いてやることで明示的なインスタンス化を回避して呼び出しを単純化することができる。 - - scala> abstract class Helper { - | val c: Context - | def generate: c.Tree = ??? - | } - defined class Helper - - scala> def impl(c1: Context): c1.Expr[Unit] = { - | val helper = new { val c: c1.type = c1 } with Helper - | c1.Expr(helper.generate) - | } - impl: (c1: scala.reflect.macros.Context)c1.Expr[Unit] - -もう1つの方法はコンテキストのアイデンティティを明示的な型パラメータとして渡す方法だ。`Helper` のコンストラクタが `c.type` を用いて `Helper.c` と元の `c` が同じであることを表していることに注目してほしい。Scala の型推論は単独ではこれを解くことができないため、手伝ってあげているわけだ。 - - scala> class Helper[C <: Context](val c: C) { - | def generate: c.Tree = ??? - | } - defined class Helper - - scala> def impl(c: Context): c.Expr[Unit] = { - | val helper = new Helper[c.type](c) - | c.Expr(helper.generate) - | } - impl: (c: scala.reflect.macros.Context)c.Expr[Unit] diff --git a/ja/overviews/macros/paradise.md b/ja/overviews/macros/paradise.md deleted file mode 100644 index f8389e4761..0000000000 --- a/ja/overviews/macros/paradise.md +++ /dev/null @@ -1,61 +0,0 @@ ---- -layout: overview-large -language: ja - -discourse: false - -partof: macros -num: 10 -outof: 11 - -title: マクロパラダイス ---- -NEW - -**Eugene Burmako 著**
        -**Eugene Yokota 訳** - -> I have always imagined that paradise will be a kind of library. -> Jorge Luis Borges, "Poem of the Gifts" - -マクロパラダイス (Macro paradise) とは Scala の複数のバージョンをサポートするコンパイラプラグインで、一般向けにリリースされている scalac と共に正しく動作するように設計されている。 -これによって、将来の Scala に取り込まれるよりもいち早く最新のマクロ機能を使えるようになっている。 -[サポートされている機能とバージョンの一覧](/ja/overviews/macros/roadmap.html))に関してはロードマップページを、 -動作の保証に関しては[マクロパラダイスのアナウンスメント](http://scalamacros.org/news/2013/08/07/roadmap-for-macro-paradise.html)を参照してほしい。 - - ~/210x $ scalac -Xplugin:paradise_*.jar -Xshow-phases - phase name id description - ---------- -- ----------- - parser 1 parse source into ASTs, perform simple desugaring - macroparadise 2 let our powers combine - namer 3 resolve names, attach symbols to trees in paradise - packageobjects 4 load package objects in paradise - typer 5 the meat and potatoes: type the trees in paradise - ... - -マクロパラダイスプラグインに対してコンパイル時の依存性を導入するマクロパラダイスの機能と、コンパイル時の依存性を導入しない機能があるが、どの機能も実行時にはマクロパラダイスを必要としない。 -詳細は[機能の一覧を](/ja/overviews/macros/roadmap.html)参照。 - -具体例に関しては [https://github.com/scalamacros/sbt-example-paradise](https://github.com/scalamacros/sbt-example-paradise) を参照してほしいが、要点をまとめると、マクロパラダイスを使うには以下の二行をビルド定義に加えるだけでいい -(すでに[sbt を使っている](/ja/overviews/macros/overview.html#using_macros_with_maven_or_sbt)ことが前提だが): - - resolvers += Resolver.sonatypeRepo("releases") - - addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.0-M4" cross CrossVersion.full) - -マクロパラダイスを Maven から利用するには、Stack Overflow の [Enabling the macro-paradise Scala compiler plugin in Maven projects](http://stackoverflow.com/questions/19086241/enabling-the-macro-paradise-scala-compiler-plugin-in-maven-projects) に書かれた手順に従ってほしい。 -(Sonatype snapshots と `scala-reflect.jar` への依存性を追加することにも注意) - - - - org.scalamacros - paradise_ - 2.1.0-M4 - - - -マクロパラダイスのソースは [https://github.com/scalamacros/paradise](https://github.com/scalamacros/paradise) から入手できる。 -最新の 2.10.x リリース版、 -最新の 2.11.0 リリース版、 -Scala 2.10.x、2.11.x、2.12.x 系列のスナップショット版、 -および Scala virtualized に対してそれぞれブランチがある。 diff --git a/ja/overviews/macros/quasiquotes.md b/ja/overviews/macros/quasiquotes.md deleted file mode 100644 index 0d2a31aa9c..0000000000 --- a/ja/overviews/macros/quasiquotes.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -layout: overview-large -language: ja - -discourse: false - -partof: macros -num: 8 -outof: 11 - -title: 準クォート ---- - -準クォートのガイドは [/overviews/quasiquotes/intro.html](/overviews/quasiquotes/intro.html) に移動した。 diff --git a/ja/overviews/macros/roadmap.md b/ja/overviews/macros/roadmap.md deleted file mode 100644 index b1821eb21c..0000000000 --- a/ja/overviews/macros/roadmap.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -layout: overview-large -language: ja - -discourse: false - -partof: macros -num: 11 -outof: 11 - -title: ロードマップ ---- - -EXPERIMENTAL - -**Eugene Burmako 著**
        -**Eugene Yokota 訳** - -今の所、Scala 2.12 におけるリフレクションおよびマクロ関連の新機能の導入の予定は無いため、 -バグ修正や安定化を除けば Scala 2.12 とパラダイス 2.12 の機能は Scala 2.11 とパラダイス 2.11 と同じものとなる。 - -機能的には、目下労力をさいているのは [scala.meta](http://scalameta.org) だ。 -これは Scala のメタプログラミングの新しい土台となるもので、現行の `scala.reflect` ベースのものに比べて、よりシンプルに、堅固になり、移植性にも適したものとなる予定だ。 -いつの日か scala.meta が scala.reflect を置き換えて、Scala におけるメタプログラミングの新標準となることを目指している。 - -| 機能 | Scala 2.10 | [パラダイス 2.10](/ja/overviews/macros/paradise.html) | Scala 2.11 | [パラダイス 2.11](/ja/overviews/macros/paradise.html) | Scala 2.12 | [パラダイス 2.12](/ja/overviews/macros/paradise.html) | -|-----------------------------------------------------------------------------------|----------------------------|--------------------------------------------------|----------------------------|--------------------------------------------------|-----------------------------|--------------------------------------------------| -| [blackbox と whitebox の分化](/ja/overviews/macros/blackbox-whitebox.html) | No | No 1 | Yes | Yes 1 | Yes | Yes 1 | -| [def マクロ](/ja/overviews/macros/overview.html) | Yes | Yes 1 | Yes | Yes 1 | Yes | Yes 1 | -| [マクロバンドル](/ja/overviews/macros/bundles.html) | No | No 1 | Yes | Yes 1 | Yes | Yes 1 | -| [implicit マクロ](/ja/overviews/macros/implicits.html) | Yes (since 2.10.2) | Yes 1 | Yes | Yes 1 | Yes | Yes 1 | -| [関数従属性の具現化](/ja/overviews/macros/implicits.html#fundep_materialization) | No | Yes 2 | Yes | Yes 1 | Yes | Yes 1 | -| [型プロバイダ](/ja/overviews/macros/typeproviders.html) | 一部サポート (see docs) | Yes 2 | 一部サポート (see docs) | Yes 2 | 一部サポート (see docs) | Yes 2 | -| [準クォート](/ja/overviews/quasiquotes/intro.html) | No | Yes 1 | Yes | Yes 1 | Yes | Yes 1 | -| [型マクロ](/ja/overviews/macros/typemacros.html) | No | No | No | No | No | No | -| [型指定の無いマクロ](/ja/overviews/macros/untypedmacros.html) | No | No | No | No | No | No | -| [マクロアノテーション](/ja/overviews/macros/annotations.html) | No | Yes 2 | No | Yes 2 | No | Yes 2 | - -

        1 この機能は、マクロパラダイスに対してコンパイル時でも実行時でもライブラリ依存性を導入しない。そのため、出てきたバイトコードを使ったコンパイルにも、このバイトコードの実行にもマクロパラダイスをクラスパスに通すことを必要としない。

        -

        2 この機能は、マクロパラダイスに対してコンパイル時のライブラリ依存性を導入するが、実行時には必要ない。そのため、出てきたバイトコードを使ったコンパイルをするのにユーザ側のビルドにもプラグインを追加する必要があるが、このバイトコードやこのバイトコードによってマクロ展開されたものを実行するのにクラスパスに追加されるものはない。

        -

        3 -Yfundep-materialization フラグが有効になっている場合のみ動作する。

        diff --git a/ja/overviews/macros/typemacros.md b/ja/overviews/macros/typemacros.md deleted file mode 100644 index dfdff25776..0000000000 --- a/ja/overviews/macros/typemacros.md +++ /dev/null @@ -1,102 +0,0 @@ ---- -layout: overview-large -language: ja - -discourse: false - -title: 型マクロ ---- -OBSOLETE - -**Eugene Burmako 著**
        -**Eugene Yokota 訳** - -型マクロ (type macro) は[マクロパラダイス](/ja/overviews/macros/paradise.html)の以前のバージョンから利用可能だったが、マクロパラダイス 2.0 ではサポートされなくなった。 -[the paradise 2.0 announcement](http://scalamacros.org/news/2013/08/05/macro-paradise-2.0.0-snapshot.html) に説明と移行のための戦略が書かれている。 - -## 直観 - -def マクロがコンパイラが特定をメソッドの呼び出しを見つけた時にカスタム関数を実行させることができるように、型マクロは特定の型が使われた時にコンパイラにフックできる。以下のコードの抜粋は、データベースのテーブルから簡単な CRUD 機能を持ったケースクラスを生成する `H2Db` マクロの定義と使用例を示す。 - - type H2Db(url: String) = macro impl - - object Db extends H2Db("coffees") - - val brazilian = Db.Coffees.insert("Brazilian", 99, 0) - Db.Coffees.update(brazilian.copy(price = 10)) - println(Db.Coffees.all) - -`H2Db` マクロの完全なソースコードは [GitHub にて](https://github.com/xeno-by/typemacros-h2db)提供して、本稿では重要な点だけをかいつまんで説明する。まず、マクロは、コンパイル時にデータベースに接続することで静的に型付けされたデータベースのラッパーを生成する。(構文木の生成に関しては[リフレクションの概要](http://docs.scala-lang.org/ja/overviews/reflection/overview.html)にて説明する) 次に、NEW `c.introduceTopLevel` API ([Scaladoc](https://scala-webapps.epfl.ch/jenkins/view/misc/job/macro-paradise211-nightly-main/ws/dists/latest/doc/scala-devel-docs/api/index.html#scala.reflect.macros.Synthetics)) を用いて生成されたラッパーをコンパイラによって管理されているトップレベル定義のリストに挿入する。最後に、マクロは生成されたクラスのスーパーコンストラクタを呼び出す `Apply` ノードを返す。注意 `c.Expr[T]` に展開される def マクロとちがって型マクロは `c.Tree` に展開されることに注意してほしい。これは、`Expr` が値を表すのに対して、型マクロは型に展開することによる。 - - type H2Db(url: String) = macro impl - - def impl(c: Context)(url: c.Expr[String]): c.Tree = { - val name = c.freshName(c.enclosingImpl.name).toTypeName - val clazz = ClassDef(..., Template(..., generateCode())) - c.introduceTopLevel(c.enclosingPackage.pid.toString, clazz) - val classRef = Select(c.enclosingPackage.pid, name) - Apply(classRef, List(Literal(Constant(c.eval(url))))) - } - - object Db extends H2Db("coffees") - // equivalent to: object Db extends Db$1("coffees") - -合成クラスを生成してその参照へと展開するかわりに、型マクロは `Template` 構文木を返すことでそのホストを変換することもできる。scalac 内部ではクラス定義とオブジェクト定義の両方とも `Template` 構文木の簡単なラッパーとして表現されているため、テンプレートへと展開することで型マクロはクラスやオブジェクトの本文全体を書き換えることができるようになる。このテクニックを活用した例も [GitHub で](https://github.com/xeno-by/typemacros-lifter)みることができる。 - - type H2Db(url: String) = macro impl - - def impl(c: Context)(url: c.Expr[String]): c.Tree = { - val Template(_, _, existingCode) = c.enclosingTemplate - Template(..., existingCode ++ generateCode()) - } - - object Db extends H2Db("coffees") - // equivalent to: object Db { - // - // - // } - -## 詳細 - -型マクロは def マクロと型メンバのハイブリッドを表す。ある一面では、型マクロはメソッドのように定義される (例えば、値の引数を取ったり、context bound な型パラメータを受け取ったりできる)。一方で、型マクロは型と同じ名前空間に属し、そのため型が期待される位置においてのみ使うことができるため、型や型マクロなどのみをオーバーライドすることができる。(より網羅的な例は [GitHub](https://github.com/scalamacros/kepler/blob/paradise/macros211/test/files/run/macro-typemacros-used-in-funny-places-a/Test_2.scala) を参照してほしい) - - - - - - - - - - - - - - - -
        機能def マクロ型マクロ型メンバ
        定義と実装に分かれているYesYesNo
        値パラメータを取ることができるYesYesNo
        型パラメータを取ることができるYesYesYes
        変位指定付きの 〃NoNoYes
        context bounds 付きの 〃YesYesNo
        オーバーロードすることができるYesYesNo
        継承することができるYesYesYes
        オーバーライドしたりされたりできるYesYesYes
        - -Scala のプログラムにおいて型マクロは、type、applied type、parent type、new、そして annotation という 5つ役割 (role) のうちの 1つとして登場する。マクロが使われた役割によって許される展開は異なっている。また、役割は NEW `c.macroRole` API ([Scaladoc](https://scala-webapps.epfl.ch/jenkins/view/misc/job/macro-paradise211-nightly-main/ws/dists/latest/doc/scala-devel-docs/api/index.html#scala.reflect.macros.Enclosures)) によって検査することができる。 - - - - - - - - - - - - -
        役割使用例クラス非クラス?Apply?Template?
        type def x: TM(2)(3) = ???YesYesNoNo
        applied type class C[T: TM(2)(3)]YesYesNoNo
        parent type class C extends TM(2)(3)
        new TM(2)(3){}
        YesNoYesYes
        new new TM(2)(3)YesNoYesNo
        annotation @TM(2)(3) class CYesNoYesNo
        - -要点をまとめると、展開された型マクロは型マクロの使用をそれが返す構文木に置き換える。ある展開が理にかなっているかどうかを考えるには、頭の中でマクロの使用例を展開される構文木で置き換えてみて結果のプログラムが正しいか確かめてみればいい。 - -例えば、 `class C extends TM(2)(3)` の中で `TM(2)(3)` のように使われている型マクロは `class C extends B(2)` となるように `Apply(Ident(TypeName("B")), List(Literal(Constant(2))))` と展開することができる。しかし、同じ展開は `TM(2)(3)` が `def x: TM(2)(3) = ???` の中の型として使われた場合は `def x: B(2) = ???` となるため、意味を成さない。(ただし、`B` そのものが型マクロではないとする。その場合は再帰的に展開され、その展開の結果がプログラムの妥当性を決定する。) - -## コツとトリック - -### クラスやオブジェクトの生成 - -[StackOverflow](http://stackoverflow.com/questions/13795490/how-to-use-type-calculated-in-scala-macro-in-a-reify-clause) でも説明したが、型マクロを作っていると `reify` がどんどん役に立たなくなっていくことに気付くだろう。その場合は、手で構文木を構築するだけではなく、マクロパラダイスにあるもう1つの実験的機能である[準クォート](/ja/overviews/quasiquotes/intro.html)を使うことも検討してみてほしい。 diff --git a/ja/overviews/macros/typeproviders.md b/ja/overviews/macros/typeproviders.md deleted file mode 100644 index b9328be305..0000000000 --- a/ja/overviews/macros/typeproviders.md +++ /dev/null @@ -1,130 +0,0 @@ ---- -layout: overview-large -language: ja - -discourse: false - -partof: macros -num: 7 -outof: 11 - -title: 型プロバイダ ---- -EXPERIMENTAL - -**Eugene Burmako 著**
        -**Eugene Yokota 訳** - -型プロバイダ (type provider) はそれ専用にマクロの種類があるわけではなくて、すでに Scala マクロが提供する機能の上に成り立っている。 - -型プロバイダをエミュレートするのは 2通りの方法があって、構造的部分型に基づいたもの (これは「匿名型プロバイダ」と呼ぶ; anonymous type provider) と、 -マクロアノテーションに基づいたもの (これは「public 型プロバイダ」と呼ぶ; public type provider) がある。前者は 2.10.x、2.11.x、2.12.x 系列から既に使える機能を用いて実現されるが、 -後者はマクロパラダイスを必要とする。両方の方法とも消去型のプロバイダを実装するのに使うことができる。 - -マクロアノテーションのコンパイルと展開の両方にマクロパラダイスが必要なため、public 型プロバイダの作者とユーザの両方がマクロパラダイスを -ビルドに含める必要があることに注意してほしい。 -しかし、マクロアノテーションを展開した後は、その結果のコードにはマクロパラダイスへの参照は残らないため、コンパイル時にも実行時にもマクロパラダイスは必要ない。 - -## 導入 - -型プロバイダは強く型付けされた型ブリッジング機構で、F# 3.0 においてインフォメーションリッチプログラミング (information-rich programming) を可能とする。 -型プロバイダは、静的に受け取ったデータソースを元に定義群とその実装を生成するコンパイル時の仕組みだ。 -型プロバイダには、非消去 (non-erased) と消去 (erased) という 2つのモードがある。 -前者はテキストを用いたコード生成と似ていて、全ての生成された型はバイトコードになるが、後者は生成された型は型検査にだけ現れて、 -バイトコード生成が行われる前に消去されてプログラマ側で予め提供した上限境界 (upper bound) になる。 - -Scala では、マクロ展開は `ClassDef`、`ModuleDef`、`DefDef`、その他の定義ノードを含むプログラマが好きなコードを生成できるため、 -コード生成という型プロバイダの側面はすでにカバーされている。これを念頭において、型プロバイダをエミュレートするにはあと 2つの課題が残っている。 - -1. 生成された定義群を公開する (Scala 2.10.x、2.11.x、2.12.x 系列から使うことができる唯一の種類のマクロ、def マクロは展開されるスコープが制限されるという意味で局所的なものだ: [https://groups.google.com/d/msg/scala-user/97ARwwoaq2U/kIGWeiqSGzcJ](https://groups.google.com/d/msg/scala-user/97ARwwoaq2U/kIGWeiqSGzcJ)) -2. 生成された定義群を任意に消去可能とする (Scala は、抽象型メンバや値クラスといった多くの言語機構について型消去をサポートしているが、その機構は拡張可能ではないためマクロ作者がカスタマイズすることはできない。) - -## 匿名型プロバイダ - -def マクロによって展開された定義群のスコープは展開されたコードに制限されるが、これらの定義群はスコープを構造的部分型にすることで脱出可能だ。 -例えば、接続文字列を受け取って、渡されたデータベースをカプセル化したモジュールを生成する `h2db` マクロは以下のように展開する。 - - def h2db(connString: String): Any = macro ... - - // an invocation of the `h2db` macro - val db = h2db("jdbc:h2:coffees.h2.db") - - // expands into the following code - val db = { - trait Db { - case class Coffee(...) - val Coffees: Table[Coffee] = ... - } - new Db {} - } - -確かに、このままだとマクロ展開されたブロックの外部からは誰も `Coffee` クラスを直接見ることができないが、 -`db` の型を調べてみると、面白いことが分かる。 - - scala> val db = h2db("jdbc:h2:coffees.h2.db") - db: AnyRef { - type Coffee { val name: String; val price: Int; ... } - val Coffees: Table[this.Coffee] - } = $anon$1... - -見ての通り、タイプチェッカが `db` の型を推論しようとしたときに、ローカルで宣言されたクラスへの参照全てを元のクラスの公開されたメンバを全て含む構造的部分型に置き換えたみたいだ。 -こうしてできた型は生成された型の本質を表したものとなっており、それらメンバの静的型付けされたインターフェイスを提供する。 - - scala> db.Coffees.all - res1: List[Db$1.this.Coffee] = List(Coffee(Brazilian,99,0)) - -これはプロダクションで使えるバージョンの Scala で実現できるため、便利な型プロバイダの方法だと言えるが、 -構造的部分型のメンバにアクセスするのに Scala はリフレクションをつかった呼び出しを生成するので、性能に問題がある。 -これにもいくつかの対策があるが、この余白はそれを書くには狭すぎるので Travis Brown 氏の驚くべきブログシリーズを紹介する: -[その1](http://meta.plasm.us/posts/2013/06/19/macro-supported-dsls-for-schema-bindings/)、[その2](http://meta.plasm.us/posts/2013/07/11/fake-type-providers-part-2/)、 -[その3](http://meta.plasm.us/posts/2013/07/12/vampire-methods-for-structural-types/)。 - -## public 型プロバイダ - -[マクロパラダイス](/ja/overviews/macros/paradise.html)と[マクロアノテーション](/ja/overviews/macros/annotations.html)を使うことで -構造的部分型を使った回避策を使わなくても簡単に外部から見えるクラスを生成できるようになった。 -アノテーションを使った方法は率直なものなので、ここではあまり解説しない。 - - class H2Db(connString: String) extends StaticAnnotation { - def macroTransform(annottees: Any*) = macro ... - } - - @H2Db("jdbc:h2:coffees.h2.db") object Db - println(Db.Coffees.all) - Db.Coffees.insert("Brazilian", 99, 0) - -### 型消去の対策 - -これはまだ深くは研究していないけども、型メンバとシングルトン型を使えば F# 同様の消去型プロバイダを提供できるのではないかという仮説がある。 -具体的には、消去したくない型は普通に今までどおり宣言して、任意の上限境界に消去したいクラスは一意に定まる識別子を持ったシングルトン型によってパラメータ化された上限境界の型エイリアスとして提供すればいい。 -この方法を用いても全ての新しい型に対して型エイリアスのメタデータのための余計なバイトコードというオーバヘッドが発生するけども、このバイトコードは普通のクラスのバイトコードに比べると非常に小さいものとなる。 -このテクニックは匿名と public の両方の型プロバイダにあてはまる。 - - object Netflix { - type Title = XmlEntity["http://.../Title".type] - def Titles: List[Title] = ... - type Director = XmlEntity["http://.../Director".type] - def Directors: List[Director] = ... - ... - } - - class XmlEntity[Url] extends Dynamic { - def selectDynamic(field: String) = macro XmlEntity.impl - } - - object XmlEntity { - def impl(c: Context)(field: c.Tree) = { - import c.universe._ - val TypeRef(_, _, tUrl) = c.prefix.tpe - val ConstantType(Constant(sUrl: String)) = tUrl - val schema = loadSchema(sUrl) - val Literal(Constant(sField: String)) = field - if (schema.contains(sField)) q"${c.prefix}($sField)" - else c.abort(s"value $sField is not a member of $sUrl") - } - } - -## blackbox vs whitebox - -匿名と public の両方の型プロバイダとも [whitebox](/ja/overviews/macros/blackbox-whitebox.html) である必要がある。 -型プロバイダマクロを [blackbox](/ja/overviews/macros/blackbox-whitebox.html) だと宣言すると正しく動作しない。 diff --git a/ja/overviews/macros/untypedmacros.md b/ja/overviews/macros/untypedmacros.md deleted file mode 100644 index 80402c0862..0000000000 --- a/ja/overviews/macros/untypedmacros.md +++ /dev/null @@ -1,65 +0,0 @@ ---- -layout: overview-large -language: ja - -discourse: false - -title: 型指定の無いマクロ ---- -OBSOLETE - -**Eugene Burmako 著**
        -**Eugene Yokota 訳** - -型指定の無いマクロ (untyped macro) は[マクロパラダイス](/ja/overviews/macros/paradise.html)の以前のバージョンから利用可能だったが、マクロパラダイス 2.0 ではサポートされなくなった。 -[the paradise 2.0 announcement](http://scalamacros.org/news/2013/08/05/macro-paradise-2.0.0-snapshot.html) に説明と移行のための戦略が書かれている。 - -## 直観 - -静的に型付けされることは素晴らしいが、それは時として重荷ともなりうる。例えば、Alois Cochard 氏の型マクロを使った列挙型の実装の実験、いわゆる [Enum Paradise](https://github.com/aloiscochard/enum-paradise) をみてみよう。Alois は以下のように、ライトウェイトなスペックから列挙型モジュールを生成する型マクロを書かなければいけない: - - object Days extends Enum('Monday, 'Tuesday, 'Wednesday...) - -`Monday` や `Friday` のようにクリーンな識別子名を使う代わりに、タイプチェッカーが存在しない識別子に関して怒らないようにこれらの名前をクォートする必要がある。`Enum` マクロでは既存のバインディングを参照しているのではなく、新しいものを導入したいわけだが、コンパイラはマクロに渡されるものを全て型検査しようとするためこれを許さない。 - -`Enum` マクロがどのように実装されているかをマクロ定義と実装のシグネチャを読むことでみていこう。マクロ定義のシグネチャに `symbol: Symbol*` と書いてあることが分かる。これで、対応する引数の型検査をコンパイラに強制している: - - type Enum(symbol: Symbol*) = macro Macros.enum - object Macros { - def enum(c: Context)(symbol: c.Expr[Symbol]*): c.Tree = ... - } - -型指定の無いマクロはマクロに渡された引数の型検査をコンパイラの代わりに実行すると伝える記法と機構を提供する。 -そのためには、単にマクロ定義のパラメータ型をアンダースコアで置き換えマクロ定義のパラメータ型を `c.Tree` とする: - - type Enum(symbol: _*) = macro Macros.enum - object Macros { - def enum(c: Context)(symbol: c.Tree*): c.Tree = ... - } - -## 詳細 - -型検査を停止するアンダースコアは Scala プログラムの中で以下の 3ヶ所において使うことができる: - -
          -
        1. マクロへのパラメータ型
        2. -
        3. マクロへの可変長パラメータ型
        4. -
        5. マクロの戻り値型
        6. -
        - -マクロ以外や複合型の一部としてのアンダースコアの使用は期待通り動作しない。 -前者はコンパイルエラーに、後者、例えば `List[_]` は通常通り存在型を返すだろう。 - -型指定の無いマクロは抽出子マクロを可能とすることに注意してほしい: [SI-5903](https://issues.scala-lang.org/browse/SI-5903)。 -Scala 2.10.x においても `unapply` や `unaoolySeq` をマクロとして宣言することは可能だが、リンクした JIRA ケースに記述されているとおり、使い勝手は非常に制限されたものとなっている。パターンマッチング内におけるテキスト抽象化の全力は型指定の無いマクロによって発揮できるようになる。 -詳細は単体テストの [test/files/run/macro-expand-unapply-c](https://github.com/scalamacros/kepler/tree/paradise/macros/test/files/run/macro-expand-unapply-c) を参照。 - -もしマクロに型指定の無いパラメータがあった場合、マクロ展開を型付けする際にタイプチェッカーは引数に関しては何もせずに型指定の無いままマクロに渡す。もしいくつかのパラメータが型アノテーションを持っていたとしても、現行では無視される。これは将来改善される予定だ: [SI-6971](https://issues.scala-lang.org/browse/SI-6971)。引数が型検査されていないため、implicit の解決や型引数の推論は実行されない (しかし、両者ともそれぞれ `c.typeCheck` と `c.inferImplicitValue` として実行できる)。 - -明示的に渡された型引数はそのままマクロに渡される。もし型引数が渡されなかった場合は、値引数を型検査しない範囲で可能な限り型引数を推論してマクロに渡す。つまり、型引数は型検査されるということだが、この制約は将来無くなるかもしれない: [SI-6972](https://issues.scala-lang.org/browse/SI-6972)。 - -もし、def マクロが型指定の無い戻り値を持つ場合、マクロ展開後に実行される 2つの型検査のうち最初のものが省略される。ここで復習しておくと、def マクロが展開されるとまずその定義の戻り値の型に対して型検査され、次に展開されたものに期待される型に対して型検査が実行される。これに関しては Stack Overflow の [Static return type of Scala macros](http://stackoverflow.com/questions/13669974/static-return-type-of-scala-macros) を参照してほしい。型マクロは最初の型検査が行われないため、何も変わらない (そもそも型マクロに戻り値の型は指定できないからだ)。 - -最後に、型指定のないマクロのパッチは `c.Expr[T]` の代わりにマクロ実装のシグネチャのどこでも `c.Tree` を使うことを可能とする。 -マクロ定義の型指定なし/型付きと、構文木/式によるマクロ実装の 4通りの組み合わせ全てがパラメータと戻り値の型の両方においてサポートされている。 -さらに詳しいことはユニットテストを参照してほしい: [test/files/run/macro-untyped-conformance](https://github.com/scalamacros/kepler/blob/b55bda4860a205c88e9ae27015cf2d6563cc241d/test/files/run/macro-untyped-conformance/Impls_Macros_1.scala)。 diff --git a/ja/overviews/macros/usecases.md b/ja/overviews/macros/usecases.md deleted file mode 100644 index 4f9e6d7d15..0000000000 --- a/ja/overviews/macros/usecases.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -layout: overview-large - -language: ja -discourse: false - -partof: macros -num: 1 -outof: 11 - -title: ユースケース ---- - -EXPERIMENTAL - -**Eugene Burmako 著**
        -**Eugene Yokota 訳** - -Scala 2.10 の実験的機能としてリリースされて以来、マクロは以前なら不可能もしくは法外に複雑だった多くのことを実現可能な領域に持ち込むことに成功した。 -Scala の商用ユーザと研究ユーザの両方がマクロを利用して様々なアイディアを形にしている。 -ここ EPFL においても我々はマクロを活用して研究を行っている。Lightbend 社もマクロを数々のプロジェクトに採用している。 -マクロはコミュニティー内でも人気があり、既にいくつかの興味深い応用が現れている。 - -最近行われた講演の ["What Are Macros Good For?"](http://scalamacros.org/paperstalks/2013-07-17-WhatAreMacrosGoodFor.pdf) では Scala 2.10 ユーザのマクロの利用方法を説明し、システム化した。講演の大筋はマクロはコード生成、静的な検査、および DSL に有効であるということで、これを研究や産業からの例を交えながら説明した。 - -Scala'13 ワークショップにおいて ["Scala Macros: Let Our Powers Combine!"](http://scalamacros.org/paperstalks/2013-04-22-LetOurPowersCombine.pdf) という論文を発表した。これは Scala 2.10 における最先端のマクロ論をより学問的な視点から説明した。 -この論文では Scala のリッチな構文と静的な型がマクロと相乗することを示し、また既存の言語機能をマクロによって新しい方法で活用できることを考察する。 diff --git a/ja/overviews/parallel-collections/architecture.md b/ja/overviews/parallel-collections/architecture.md deleted file mode 100644 index 6a4c02eda5..0000000000 --- a/ja/overviews/parallel-collections/architecture.md +++ /dev/null @@ -1,78 +0,0 @@ ---- -layout: overview-large -title: 並列コレクションライブラリのアーキテクチャ - -discourse: false - -partof: parallel-collections -num: 5 -language: ja ---- - -通常の順次コレクションライブラリと同様に、Scala の並列コレクションライブラリは異なる並列コレクションの型に共通して存在する多くのコレクション演算を含む。 -これも順次コレクションで使われた手法だが、並列コレクションライブラリはほとんどの演算をいくつかある並列コレクション「テンプレート」の中で一度だけ実装することでコードの重複を回避することを目指している。これらの「テンプレート」は多くの異なる並列コレクションの実装により柔軟に継承されている。 - -この方法の利点はより簡略化された**メンテナンス性**と**拡張性**にある。 -メンテナンスに着目すると、並列コレクションの演算がそれぞれ単一の実装を持ち、全ての並列コレクションによって継承されることで、メンテナンスはより簡単にそして強固になる。なぜなら、バグフィクスはいちいち実装を重複させずに、クラスの継承を通して伝搬するからだ。 -同じ理由で、ライブラリ全体が拡張しやすくなる。新たなコレクションのクラスはその演算の大部分を単に継承すればいいからだ。 - -## 中心概念 - -前述の「テンプレート」となるトレイト群は、ほとんどの並列演算をスプリッタ (`Splitter`) とコンバイナ (`Combiner`) という二つの中心概念に基づいて実装する。 - -### スプリッタ - -その名前が示すように、スプリッタ (`Splitter`) の役割は並列コレクションを何らかの有意な方法で要素を分割することだ。 -基本的な考えとしては、コレクションを小さい部分にどんどん分割していき、逐次的に処理できるまで小さくしていくことだ。 - - trait Splitter[T] extends Iterator[T] { - def split: Seq[Splitter[T]] - } - -興味深いことに、`Splitter` は `Iterator` として実装されているため(`Iterator` の標準メソッドである `next` や `hasNext` を継承している)、フレームワークはスプリッタを分割だけではなく並列コレクションの走査にも利用できる。 -この「分割イテレータ」の特徴は、`split` メソッドが `this`(`Iterator` の一種である `Splitter`)を分割して、並列コレクション全体の要素に関する**交わりを持たない** (disjoint) の部分集合を走査する別の `Splitter` を生成することだ。 -通常のイテレータ同様に、`Splitter` は一度 `split` を呼び出すと無効になる。 - -一般的には、コレクションは `Splitter` を用いてだいたい同じサイズの部分集合に分割される。 -特に並列列などにおいて、任意のサイズの区分が必要な場合は、より正確な分割メソッドである `psplit` を実装する `PreceiseSplitter` という `Splitter` のサブタイプが用いられる。 - -### コンバイナ - -コンバイナ (`Combiner`) は、Scala の順次コレクションライブラリのビルダ (`Builder`) をより一般化したものだと考えることができる。 -それぞれの順次コレクションが `Builder` を提供するように、それぞれの並列コレクションは各自 `Combiner` を提供する。 - -順次コレクションの場合は、`Builder` に要素を追加して、`result` メソッドを呼び出すことでコレクションを生成することができた。 -並列コレクションの場合は、`Combiner` は `combine` と呼ばれるメソッドを持ち、これは別の `Combiner` を受け取り両方の要素の和集合を含む新たな `Combiner` を生成する。`combine` が呼び出されると、両方の `Combiner` とも無効化される。 - - trait Combiner[Elem, To] extends Builder[Elem, To] { - def combine(other: Combiner[Elem, To]): Combiner[Elem, To] - } - -上のコードで、二つの型パラメータ `Elem` と `To` はそれぞれ要素型と戻り値のコレクションの型を表す。 - -**注意:** `c1 eq c2` が `true` である二つの `Combiner` (つまり、同一の `Combiner` という意味だ)があるとき、`c1.combine(c2)` は常に何もせずに呼ばれた側の `Combiner` である `c1` を返す。 - -## 継承関係 - -Scala の並列コレクションは、Scala の(順次)コレクションライブラリの設計から多大な影響を受けている。 -以下に示すよう、トレイト群は通常のコレクションフレームワーク内のトレイト群を鏡写しのように対応している。 - -[]({{ site.baseurl }}/resources/images/parallel-collections-hierarchy.png) - -
        Scala のコレクションと並列コレクションライブラリの継承関係
        -
        - -並列コレクションが順次コレクションに緊密に統合することで、順次コレクションと並列コレクションの単純な置換えが可能となることを目標としている。 - -順次か並列かのどちらでもありうる(`par` と `seq` を呼び出すことで並列コレクションと順次コレクションを切り替えられるような)コレクションへの参照を持つためには、両方のコレクション型に共通するスーパー型の存在が必要となる。 -これが、上の図に出てくる `GenTraversable`、`GenIterable`、`GenSeq`、`GenMap`、`GenSet` という「一般」(general) トレイト群で、これらは順番通り (in-order) の走査も、逐次的 (one-at-a-time) な走査も保証しない。 -対応する順次トレイトや並列トレイトはこれらを継承する。 -例えば、`ParSeq` と `Seq` は両方とも一般列 `GenSeq` のサブタイプだが、お互いは継承関係に無い。 - -順次コレクションと並列コレクションの継承関係に関するより詳しい議論は、このテクニカルリポートを参照してほしい。 \[[1][1]\] - -## 参照 - -1. [On a Generic Parallel Collection Framework, Aleksandar Prokopec, Phil Bawgell, Tiark Rompf, Martin Odersky, June 2011][1] - -[1]: http://infoscience.epfl.ch/record/165523/files/techrep.pdf "flawed-benchmark" diff --git a/ja/overviews/parallel-collections/concrete-parallel-collections.md b/ja/overviews/parallel-collections/concrete-parallel-collections.md deleted file mode 100644 index 2f1dc79140..0000000000 --- a/ja/overviews/parallel-collections/concrete-parallel-collections.md +++ /dev/null @@ -1,164 +0,0 @@ ---- -layout: overview-large -title: 具象並列コレクションクラス - -discourse: false - -partof: parallel-collections -num: 2 -language: ja ---- - -## 並列配列 - -並列配列 ([`ParArray`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/parallel/mutable/ParArray.html)) は、線形で連続的な要素の配列を保持する列だ。 -そのため、内部の配列を変更することで効率的な要素の読み込みや更新ができるようになる。 -また、要素の走査も非常に効率的だ。 -並列配列は、サイズが一定であるという意味で配列と似ている。 - - scala> val pa = scala.collection.parallel.mutable.ParArray.tabulate(1000)(x => 2 * x + 1) - pa: scala.collection.parallel.mutable.ParArray[Int] = ParArray(1, 3, 5, 7, 9, 11, 13,... - - scala> pa reduce (_ + _) - res0: Int = 1000000 - - scala> pa map (x => (x - 1) / 2) - res1: scala.collection.parallel.mutable.ParArray[Int] = ParArray(0, 1, 2, 3, 4, 5, 6, 7,... - -内部的には、並列配列の[「スプリッタ」 (splitter)](architecture.html#id1) の分割は走査用の添字を更新した二つの新たなスプリッタを作る事に結局なる。 -[「コンバイナ」 (combiner)](architecture.html#id1) はより複雑だ。多くの変換メソッドの多く(例えば、`flatMap`、`filter`、`takeWhile` など)は、事前に結果の要素数(そのため、配列のサイズ)が分からないため、それぞれのコンバイナはならし定数時間 (amortized constant time) の - `+=` 演算を持つ配列バッファの変種だ。 -異なるプロセッサがそれぞれの並列配列コンバイナに要素を追加し、後で内部の配列を連結することで合成が行われる。 -要素の総数が分かった後になってから、内部の配列が割り当てられ、並列に書き込まれる。そのため、変換メソッドは読み込みメソッドに比べて少し高価だ。また、最後の配列の割り当ては JVM上で逐次的に実行されるため、map 演算そのものが非常に安価な場合は、配列の割り当てが逐次的ボトルネックとなりうる。 - -`seq` メソッドを呼び出すことで並列配列はその順次版である `ArraySeq` に変換される。 -`ArraySeq` は元の並列配列の内部構造と同じ配列を内部で使うためこの変換は効率的だ。 - -## 並列ベクトル - -並列ベクトル ([`ParVector`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/parallel/immutable/ParVector.html)) -は、低い定数係数の対数時間で読み込みと書き込みを行う不変列だ。 - - scala> val pv = scala.collection.parallel.immutable.ParVector.tabulate(1000)(x => x) - pv: scala.collection.parallel.immutable.ParVector[Int] = ParVector(0, 1, 2, 3, 4, 5, 6, 7, 8, 9,... - - scala> pv filter (_ % 2 == 0) - res0: scala.collection.parallel.immutable.ParVector[Int] = ParVector(0, 2, 4, 6, 8, 10, 12, 14, 16, 18,... - -不変ベクトルは 32分木として表されるため、スプリッタはそれぞれのサブツリーをスプリッタに割り当てることで分割される。 -現行のコンバイナの実装はベクトルとして要素を保持し、遅延評価で要素をコピーすることで合成する。 -このため、変換メソッドは並列配列のそれに比べてスケーラビリティが低い。 -ベクトルの連結が将来の Scala リリースで提供されるようになれば、コンバイナは連結を用いて合成できるようになり、変換メソッドはより効率的になる。 - -並列ベクトルは、順次[ベクトル](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Vector.html)の並列版で、定数時間で一方から他方へと変換できる。 - -## 並列範囲 - -並列範囲 ([`ParRange`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/parallel/immutable/ParRange.html)) -は、順序付けされた等間隔の要素の列だ。 -並列範囲は、逐次版の [Range](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Range.html) と同様に作成される: - - scala> 1 to 3 par - res0: scala.collection.parallel.immutable.ParRange = ParRange(1, 2, 3) - - scala> 15 to 5 by -2 par - res1: scala.collection.parallel.immutable.ParRange = ParRange(15, 13, 11, 9, 7, 5) - -順次範囲にビルダが無いのと同様に、並列範囲にはコンバイナが無い。 -並列範囲に対する `map` 演算は並列ベクトルを生成する。 -順次範囲と並列範囲は、一方から他方へと効率的に `seq` と `par` メソッドを用いて変換できる。 - -## 並列ハッシュテーブル - -並列ハッシュテーブルは要素を内部の配列に格納し、各要素のハッシュコードにより格納する位置を決定する。 -並列可変ハッシュ集合 ( -[mutable.ParHashSet](http://www.scala-lang.org/api/{{ site.scala-version}}/scala/collection/parallel/mutable/ParHashSet.html)) -と並列可変ハッシュマップ ([mutable.ParHashMap](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/parallel/mutable/ParHashMap.html)) -はハッシュテーブルに基づいている。 - - scala> val phs = scala.collection.parallel.mutable.ParHashSet(1 until 2000: _*) - phs: scala.collection.parallel.mutable.ParHashSet[Int] = ParHashSet(18, 327, 736, 1045, 773, 1082,... - - scala> phs map (x => x * x) - res0: scala.collection.parallel.mutable.ParHashSet[Int] = ParHashSet(2181529, 2446096, 99225, 2585664,... - -並列ハッシュテーブルのコンバイナは、要素をハッシュコードの最初の文字に応じてバケットに振り分ける。 -これらは単にバケットを連結することで合成される。 -最終的なハッシュテーブルが構築されると(つまりコンバイナの `result` メソッドが呼ばれると)、 -内部の配列が割り当てられ、異なるバケットからハッシュテーブル配列の別々の連続したセグメントへ並列して要素が書き込まれる。 - -順次ハッシュマップとハッシュ集合は `par` メソッドを用いて並列のものに変換できる。 -並列ハッシュテーブルは、その内部ではいくつかの区分に分けて要素を保持しているが、それぞれの要素数を管理するサイズマップを必要とする。 -そのため、順次ハッシュテーブルが並列テーブルに最初に変換されるときにはサイズマップが作成されなければいけない。 -ここで発生するテーブルの走査により最初の `par` の呼び出しはハッシュテーブルのサイズに対して線形の時間がかかる。 -それ以降のハッシュテーブルの変更はサイズマップの状態も更新するため、以降の `par` や `seq` を用いた変換は定数時間で実行される。 -サイズマップの更新は `useSizeMap` メソッドを用いることで開始したり、中止したりできる。 -重要なのは、順次ハッシュテーブルの変更は並列ハッシュテーブルにも影響があり、またその逆も真であることだ。 - -## 並列ハッシュトライ - -並列ハッシュトライは、不変集合と不変マップを効率的に表す不変ハッシュトライの並列版だ。 -これらは、[immutable.ParHashSet](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/parallel/immutable/ParHashSet.html) クラスと -[immutable.ParHashMap](http://www.scala-lang.org/api/{{ site.scala-version}}/scala/collection/parallel/immutable/ParHashMap.html) クラスにより提供される。 - - scala> val phs = scala.collection.parallel.immutable.ParHashSet(1 until 1000: _*) - phs: scala.collection.parallel.immutable.ParHashSet[Int] = ParSet(645, 892, 69, 809, 629, 365, 138, 760, 101, 479,... - - scala> phs map { x => x * x } sum - res0: Int = 332833500 - -並列ハッシュテーブル同様に、並列ハッシュトライのコンバイナは事前に要素をバケットにソートしておき、それぞれのバケットを別のプロセッサに割り当て、それぞれがサブトライを構築することで、結果のハッシュトライを並列に構築する。 - -並列ハッシュトライは `seq` と `par` メソッドを用いることで順次ハッシュトライと定数時間で相互に変換できる。 - -## 並列並行トライ - -[concurrent.TrieMap](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/concurrent/TrieMap.html) -は、複数のスレッドから同時にアクセスできる (concurrent thread-safe) マップだが、 -[mutable.ParTrieMap](http://www.scala-lang.org/api/{{ site.scala-version}}/scala/collection/parallel/mutable/ParTrieMap.html) -は、その並列版だ。 -並列データ構造の多くは、走査時にデータ構造が変更された場合に一貫性のある走査を保証しないが、並行トライは更新が次回の走査まで見えないことを保証する。 -つまり以下の 1 から 99 の数の平方根を出力する例のように、並行トライを走査中に変更できるようになる: - - scala> val numbers = scala.collection.parallel.mutable.ParTrieMap((1 until 100) zip (1 until 100): _*) map { case (k, v) => (k.toDouble, v.toDouble) } - numbers: scala.collection.parallel.mutable.ParTrieMap[Double,Double] = ParTrieMap(0.0 -> 0.0, 42.0 -> 42.0, 70.0 -> 70.0, 2.0 -> 2.0,... - - scala> while (numbers.nonEmpty) { - | numbers foreach { case (num, sqrt) => - | val nsqrt = 0.5 * (sqrt + num / sqrt) - | numbers(num) = nsqrt - | if (math.abs(nsqrt - sqrt) < 0.01) { - | println(num, nsqrt) - | numbers.remove(num) - | } - | } - | } - (1.0,1.0) - (2.0,1.4142156862745097) - (7.0,2.64576704419029) - (4.0,2.0000000929222947) - ... - -コンバイナは、内部的には `TrieMap` として実装されている。 -これは、並行なデータ構造であるため、変換メソッドの呼び出しに対して全体で一つのコンバイナのみが作成され、全てのプロセッサによって共有される。 -他の並列可変コレクションと同様に、`TrieMap` とその並列版である `ParTrieMap` は `seq` と `par` メソッドにより取得でき、これらは同じ内部構造にデータを格納してあるため一方で行われた変更は他方にも影響がある。変換は定数時間で行われる。 - -## 性能特性 - -列型の性能特性: - -| | head | tail | apply | 更新 | 先頭に
        追加 | 最後に
        追加 | 挿入 | -| -------- | ------ | -------- | ------ | ------ | --------- | -------- | ---- | -| `ParArray` | 定数 | 線形 | 定数 | 定数 | 線形 | 線形 | 線形 | -| `ParVector` | 実質定数 | 実質定数 | 実質定数 | 実質定数 | 実質定数 | 実質定数 | - | -| `ParRange` | 定数 | 定数 | 定数 | - | - | - | - | - -集合とマップ型の性能特性: - -| | 検索 | 追加 | 削除 | -| -------- | ------ | ------- | ------ | -| **不変** | | | | -| `ParHashSet`/`ParHashMap`| 実質定数 | 実質定数 | 実質定数 | -| **可変** | | | | -| `ParHashSet`/`ParHashMap`| 定数 | 定数 | 定数 | -| `ParTrieMap` | 実質定数 | 実質定数 | 実質定数 | diff --git a/ja/overviews/parallel-collections/configuration.md b/ja/overviews/parallel-collections/configuration.md deleted file mode 100644 index 285193da22..0000000000 --- a/ja/overviews/parallel-collections/configuration.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -layout: overview-large -title: 並列コレクションの設定 - -discourse: false - -partof: parallel-collections -num: 7 -language: ja ---- - -## タスクサポート - -並列コレクションは、演算のスケジューリングに関してモジュール化されている。 -全ての並列コレクションはタスクサポートというオブジェクトによりパラメータ化されており、これがタスクのスケジューリングとプロセッサへの負荷分散 (load balancing) を担当する。 - -タスクサポートは内部にスレッドプールの実装への参照を持っており、タスクをより小さいタスクにいつどのように分割するかを決定している。 -この内部の振る舞いに関してより詳しく知りたい場合はこのテクノロジーレポートを参照してほしい \[[1][1]\]。 - -現行の並列コレクションにはいくつかのタスクサポートの実装がある。 -JVM 1.6 以上でデフォルトで使われるのは、`ForkJoinTaskSupport` で、これは内部でフォーク/ジョインプールを使う。 -JVM 1.5 とその他のフォーク/ジョインプールをサポートしない JVM はより効率の劣る `ThreadPoolTaskSupport` を使う。 -また、`ExecutionContextTaskSupport` は `scala.conccurent` にあるデフォルトの実行コンテクスト (execution context) の実装を使い、`scala.concurrent` で使われるスレッドプールを再利用する(これは JVM のバージョンによってフォーク/ジョインプールか `ThreadPoolExecutor` が使われる)。それぞれの並列コレクションは、デフォルトで実行コンテクストのタスクサポートに設定されているため、並列コレクションは、Future API で使われるのと同じフォーク/ジョインプールが再利用されている。 - -以下に並列コレクションのタスクサポートを変更する具体例をみてみよう: - - scala> import scala.collection.parallel._ - import scala.collection.parallel._ - - scala> val pc = mutable.ParArray(1, 2, 3) - pc: scala.collection.parallel.mutable.ParArray[Int] = ParArray(1, 2, 3) - - scala> pc.tasksupport = new ForkJoinTaskSupport(new scala.concurrent.forkjoin.ForkJoinPool(2)) - pc.tasksupport: scala.collection.parallel.TaskSupport = scala.collection.parallel.ForkJoinTaskSupport@4a5d484a - - scala> pc map { _ + 1 } - res0: scala.collection.parallel.mutable.ParArray[Int] = ParArray(2, 3, 4) - -上の例では、並列コレクションに対して並列度2のフォーク/ジョインプールを使うように設定した。 -並列コレクションを `ThreadPoolExecutor` を使うように設定する場合は: - - scala> pc.tasksupport = new ThreadPoolTaskSupport() - pc.tasksupport: scala.collection.parallel.TaskSupport = scala.collection.parallel.ThreadPoolTaskSupport@1d914a39 - - scala> pc map { _ + 1 } - res1: scala.collection.parallel.mutable.ParArray[Int] = ParArray(2, 3, 4) - -並列コレクションがシリアライズされるとき、タスクサポートフィールドはシリアライゼーションから省かれる。 -並列コレクションがデシリアライズされるとき、タスクサポートはデフォルトの値である実行コンテクストタスクサポートに設定される。 - -カスタムのタスクサポートを実装するには、`TaskSupport` トレイトを拡張して以下のメソッドを実装する: - - def execute[R, Tp](task: Task[R, Tp]): () => R - - def executeAndWaitResult[R, Tp](task: Task[R, Tp]): R - - def parallelismLevel: Int - -`execute` メソッドはタスクを非同期的にスケジューリングし、計算の結果をフューチャー値として返す。 -`executeAndWait` メソッドは同じことを行うがタスクが完了してから結果を返す。 -`parallelismLevel` はタスクサポートがタスクのスケジューリングをするのに用いる対象コア数を返す。 - -## 参照 - -1. [On a Generic Parallel Collection Framework, June 2011][1] - - [1]: http://infoscience.epfl.ch/record/165523/files/techrep.pdf "parallel-collections" diff --git a/ja/overviews/parallel-collections/conversions.md b/ja/overviews/parallel-collections/conversions.md deleted file mode 100644 index 220c0d2eeb..0000000000 --- a/ja/overviews/parallel-collections/conversions.md +++ /dev/null @@ -1,60 +0,0 @@ ---- -layout: overview-large -title: 並列コレクションへの変換 - -discourse: false - -partof: parallel-collections -num: 3 -language: ja ---- - -## 順次と並列コレクション間での変換 - -全ての順次コレクションは、`par` メソッドを用いて何らかの並列コレクションへと変換できる。 -順次コレクションのいくつかには、直接対応する並列版を持つものがあり、それらのコレクションの変換は効率的だ。 -順次と並列コレクションが同じデータ構造で表現されているため変換は定数時間で実行される(唯一の例外は初回の `par` が少し高価である可変ハッシュマップと可変ハッシュ集合だが、二回目以降の `par` の呼び出しは定数時間で行われる)。 -また、可変コレクションに関しては、同じ内部構造を共有する場合、順次コレクションで行われた更新はそれに対応する並列版からも見えていることに注意して欲しい。 - -| 順次 | 並列 | -| ------------- | -------------- | -| **可変** | | -| `Array` | `ParArray` | -| `HashMap` | `ParHashMap` | -| `HashSet` | `ParHashSet` | -| `TrieMap` | `ParTrieMap` | -| **不変** | | -| `Vector` | `ParVector` | -| `Range` | `ParRange` | -| `HashMap` | `ParHashMap` | -| `HashSet` | `ParHashSet` | - -リスト、キュー、ストリーム等、その他のコレクションは、要素を順番にアクセスしなければいけないという意味で本質的に逐次的だ。それらのコレクションは、似ている並列コレクションに要素をコピーすることで、並列版に変換される。 -例えば、リストは標準の並列不変列である並列ベクトルに変換される。 - -全ての並列コレクションは、`seq` メソッドを用いて何らかの順次コレクションに変換できる。 -並列コレクションから順次コレクションへの変換は常に効率的で、定数時間で実行される。 -並列可変コレクションに対して `seq` を呼び出すと、同じ内部構造にデータを格納した順次コレクションを返す。 -そのため、コレクションの変更は、他方にも見えることになる。 - -## 異なるコレクション型の間での変換 - -順次と並列コレクション間での変換とは直交して、コレクションは別のコレクション型でも変換できる。 -例えば、`toSeq` を呼び出すことで順次集合を順次列に変換できるが、`toSeq` を呼び出して並列集合を並列列に変換することもできる。 -一般的なルールとしては、`X` に並列版があれば、`toX` メソッドは、そのコレクションを `ParX` コレクションに変換する。 - -以下の表に全ての変換をまとめる: - -| メソッド | 戻り値の型 | -| -------------- | -------------- | -| `toArray` | `Array` | -| `toList` | `List` | -| `toIndexedSeq` | `IndexedSeq` | -| `toStream` | `Stream` | -| `toIterator` | `Iterator` | -| `toBuffer` | `Buffer` | -| `toTraversable`| `GenTraverable`| -| `toIterable` | `ParIterable` | -| `toSeq` | `ParSeq` | -| `toSet` | `ParSet` | -| `toMap` | `ParMap` | diff --git a/ja/overviews/parallel-collections/ctries.md b/ja/overviews/parallel-collections/ctries.md deleted file mode 100644 index 11a5a4e273..0000000000 --- a/ja/overviews/parallel-collections/ctries.md +++ /dev/null @@ -1,143 +0,0 @@ ---- -layout: overview-large -title: 並行トライ - -discourse: false - -partof: parallel-collections -num: 4 -language: ja ---- - -並列データ構造の多くは、走査時にデータ構造が変更された場合に一貫性のある走査を保証しない。 -これは、ほとんどの可変コレクションにも当てはまることだ。 -並行トライ (concurrent trie) は、走査中に自身の更新を許すという意味で特殊なものだと言える。 -変更は、次回以降の走査のみで見えるようになる。 -これは、順次並行トライと並列並行トライの両方に当てはまることだ。 -唯一の違いは、前者は要素を逐次的に走査するのに対して、後者は並列に走査するということだけだ。 - -この便利な特性を活かして簡単に書くことができるアルゴリズムがいくつかある。 -これらのアルゴリズムは、 要素のデータセットを反復処理するが、要素によって異なる回数繰り返す必要のあるアルゴリズムであることが多い。 - -与えられた数の集合の平方根を計算する例を以下に示す。 -繰り返すたびに平方根の値が更新される。 -平方根の値が収束すると、その数は集合から外される。 - - case class Entry(num: Double) { - var sqrt = num - } - - val length = 50000 - - // リストを準備する - val entries = (1 until length) map { num => Entry(num.toDouble) } - val results = ParTrieMap() - for (e <- entries) results += ((e.num, e)) - - // 平方根を計算する - while (results.nonEmpty) { - for ((num, e) <- results) { - val nsqrt = 0.5 * (e.sqrt + e.num / e.sqrt) - if (math.abs(nsqrt - e.sqrt) < 0.01) { - results.remove(num) - } else e.sqrt = nsqrt - } - } - -上のバビロニア法による平方根の計算 (\[[3][3]\]) は、数によっては他よりも早く収束することに注意してほしい。 -そのため、それらの数を `result` から削除して処理が必要な要素だけが走査されるようにする必要がある。 - -もう一つの例としては、幅優先探索 (breadth-first search) アルゴリズムがある。 -これは、対象となるノードが見つかるか他に見るノードが無くなるまで反復的に前線ノードを広げていく。 -ここでは、二次元の地図上のノードを `Int` のタプルとして定義する。 -`map` はブーリアン型の二次元配列として定義し、これはその位置が占有されているかを示す。 -次に、今後拡張予定の全てのノード(ノード前線)を含む `open` と、拡張済みの全てのノードを含む `closed` という二つの平行トライマップを宣言する。 -地図の四隅から探索を始めて、地図の中心までの経路を見つけたいため、`open` マップを適切なノードに初期化する。 -次に、`open` マップ内の全てのノードを拡張するノードが無くなるまで反復的に拡張する。 -ノードが拡張されるたびに、`open` マップから削除され、`closed` マップに追加される。 -完了したら、対象ノードから初期ノードまでの経路を表示する。 - - val length = 1000 - - // Node 型を定義する - type Node = (Int, Int); - type Parent = (Int, Int); - - // Node 型の演算 - def up(n: Node) = (n._1, n._2 - 1); - def down(n: Node) = (n._1, n._2 + 1); - def left(n: Node) = (n._1 - 1, n._2); - def right(n: Node) = (n._1 + 1, n._2); - - // map と target を作る - val target = (length / 2, length / 2); - val map = Array.tabulate(length, length)((x, y) => (x % 3) != 0 || (y % 3) != 0 || (x, y) == target) - def onMap(n: Node) = n._1 >= 0 && n._1 < length && n._2 >= 0 && n._2 < length - - // open マップ - ノード前線 - // closed マップ - 滞在済みのノード - val open = ParTrieMap[Node, Parent]() - val closed = ParTrieMap[Node, Parent]() - - // 初期位置をいくつか追加する - open((0, 0)) = null - open((length - 1, length - 1)) = null - open((0, length - 1)) = null - open((length - 1, 0)) = null - - // 貪欲法による幅優先探索 - while (open.nonEmpty && !open.contains(target)) { - for ((node, parent) <- open) { - def expand(next: Node) { - if (onMap(next) && map(next._1)(next._2) && !closed.contains(next) && !open.contains(next)) { - open(next) = node - } - } - expand(up(node)) - expand(down(node)) - expand(left(node)) - expand(right(node)) - closed(node) = parent - open.remove(node) - } - } - - // 経路の表示 - var pathnode = open(target) - while (closed.contains(pathnode)) { - print(pathnode + "->") - pathnode = closed(pathnode) - } - println() - -並行トライはまた、逐次化可能 (linearizable) 、ロックフリー (lock-free)、かつ計算量が定数時間の `snapshot` 演算をサポートする。 -この演算は、ある特定の時点における全ての要素を含んだ新たな並列トライを作成する。 -これは、実質的にはそのトライのその時点での状態を捕捉したことと変わらない。 -`snapshot` 演算は、並列トライの新しいルートを作成するだけだ。 -後続の更新は並列トライのうち更新に関わる部分だけを遅延評価で再構築し他の部分には手を付けない。 -まず、これはスナップショット演算に要素のコピーを伴わないため、演算が高価ではないということだ。 -次に、コピーオンライト (copy-on-write) の最適化は平行トライの一部だけをコピーするため、後続の更新は水平にスケールする。 -`readOnlySnapshot` メソッドは、`snapshot` メソッドよりも少しだけ効率が良いが、更新のできないリードオンリーなマップを返す。 -このスナップショット機構を用いて、並行トライは逐次化可能で計算量が定数時間の、`clear` メソッドも提供する。 -並行トライとスナップショットの仕組みに関してさらに知りたい場合は、\[[1][1]\] と \[[2][2]\] を参照してほしい。 - -並行トライのイテレータはスナップショットに基づいている。 -イテレータオブジェクトが作成される前に並行トライのスナップショットが取られるため、イテレータはスナップショットが作成された時点での要素のみを走査する。 -当然、イテレータはリードオンリーなスナップショットを用いる。 - -`size` 演算もスナップショットに基づいている。 -率直に実装すると、`size` を呼び出した時点でイテレータ(つまり、スナップショット)が作り、全ての要素を走査しながら数えていくというふうになる。 -そのため、`size` を呼び出すたびに要素数に対して線形の時間を要することになる。 -しかし、並行トライは異なる部分のサイズをキャッシュするように最適化されているため、`size` の計算量はならし対数時間まで減少している。 -実質的には、一度 `size` を呼び出すと二回目以降の `size` の呼び出しは、典型的には最後に `size` が呼ばれた時点より更新された枝のサイズのみを再計算するというように、最小限の仕事のみを要するようになる。 -さらに、並列並行トライのサイズ計算は並列的に実行される。 - -## 参照 - -1. [Cache-Aware Lock-Free Concurrent Hash Tries][1] -2. [Concurrent Tries with Efficient Non-Blocking Snapshots][2] -3. [Methods of computing square roots][3] - - [1]: http://infoscience.epfl.ch/record/166908/files/ctries-techreport.pdf "Ctries-techreport" - [2]: http://lampwww.epfl.ch/~prokopec/ctries-snapshot.pdf "Ctries-snapshot" - [3]: http://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method "babylonian-method" diff --git a/ja/overviews/parallel-collections/custom-parallel-collections.md b/ja/overviews/parallel-collections/custom-parallel-collections.md deleted file mode 100644 index 499e582c6c..0000000000 --- a/ja/overviews/parallel-collections/custom-parallel-collections.md +++ /dev/null @@ -1,247 +0,0 @@ ---- -layout: overview-large -title: カスタム並列コレクションの作成 - -discourse: false - -partof: parallel-collections -num: 6 -language: ja ---- - -## コンバイナを持たない並列コレクション - -ビルダ無しでもカスタム順次コレクションを定義できるように、コンバイナ無しで並列コレクションを定義することが可能だ。 -コンバイナを持たなければ、(例えば `map`、`flatMap`、`collect`、`filter`、などの)変換メソッドはデフォルトでは、継承関係で一番近い標準コレクションの型を返すことになる。 -例えば、範囲はビルダを持たないため、その要素を写像 (`map`) するとベクトルが作られる。 - -以下に具体例として、並列の文字列コレクションを定義する。 -文字列は論理的には不変列なので、並列文字列は `immutable.ParSeq[Char]` を継承することにする: - - class ParString(val str: String) - extends immutable.ParSeq[Char] { - -次に、全ての不変列にあるメソッドを定義する: - - def apply(i: Int) = str.charAt(i) - - def length = str.length - -この並列コレクションの直列版も定義しなければならない。 -ここでは `WrappedString` クラスを返す: - - def seq = new collection.immutable.WrappedString(str) - -最後に、この並列文字列コレクションのスプリッタを定義しなければならない。 -このスプリッタは `ParStringSplitter` と名づけ、列スプリッタの `SeqSplitter[Char]` を継承することにする: - - def splitter = new ParStringSplitter(str, 0, str.length) - - class ParStringSplitter(private var s: String, private var i: Int, private val ntl: Int) - extends SeqSplitter[Char] { - - final def hasNext = i < ntl - - final def next = { - val r = s.charAt(i) - i += 1 - r - } - -上のコードでは、`ntl` は文字列の長さの合計、`i` は現在の位置、`s` は文字列自身を表す。 - -並列コレクションのイテレータ(別名スプリッタ)は、順次コレクションのイテレータにある `next` と `hasNext` の他にもいくつかのメソッドを必要とする。 -第一に、スプリッタがまだ走査していない要素の数を返す `remaining` というメソッドがある。 -次に、現在のスプリッタを複製する `dup` というメソッドがある。 - - def remaining = ntl - i - - def dup = new ParStringSplitter(s, i, ntl) - -最後に、現在のスプリッタの要素の部分集合を走査する別のスプリッタを作成するのに使われる `split` と `psplit` メソッドがある。 -`split` メソッドは、現在のスプリッタが操作する要素の、交わらなく (disjoint) 、空でもない、部分集合の列を返すことを約束する。 -現在のスプリッタが一つ以下の要素を持つ場合、`split` は自分自身だけが入った列を返す。 -`psplit` メソッドは、`sizes` パラメータが指定する数どおりの要素を走査するスプリッタの列を返す。 -もし `sizes` パラメータが現在のスプリッタよりも少ない要素を指定した場合は、残りの要素は追加のスプリッタに入れられ、それは列の最後に追加される。もし `sizes` パラメータが今ある要素よりも多くの要素を必要とした場合は、それぞれのサイズに空のスプリッタを追加して補う。 -また、`split` か `psplit` のどちらかを呼び出しても現在のスプリッタを無効化する。 - - def split = { - val rem = remaining - if (rem >= 2) psplit(rem / 2, rem - rem / 2) - else Seq(this) - } - - def psplit(sizes: Int*): Seq[ParStringSplitter] = { - val splitted = new ArrayBuffer[ParStringSplitter] - for (sz <- sizes) { - val next = (i + sz) min ntl - splitted += new ParStringSplitter(s, i, next) - i = next - } - if (remaining > 0) splitted += new ParStringSplitter(s, i, ntl) - splitted - } - } - } - -上のコードでは、`split` は `psplit` に基づいて実装されているが、これは並列の列ではよくあることだ。 -並列マップ、集合、`Iterable` のスプリッタを実装する方が `psplit` を必要としないため簡単であることが多い。 - -これで、並列文字列クラスができた。唯一の短所は `filter` のような変換メソッドを呼び出すと並列文字列の代わりに並列ベクトルが返ってくる点だ。 -フィルタをかけた後でベクトルから文字列を再び生成するのは高価であるかもしれず、これは望ましいとは言えない。 - -## コンバイナを持つ並列コレクション - -例えばコンマを除外するなどして、並列文字列内の文字を `filter` したいとする。 -前述のとおり、`filter` の呼び出しは並列ベクトルを返すが、(API のインターフェイスによっては並列文字列が必要なため)どうしても並列文字列が欲しい。 - -これを回避するには並列文字列コレクションのコンバイナを書かなくてはならない。 -今度は `ParSeq[Char]` の代わりに `ParSeqLike` を継承することで `filter` の戻り値の型がより特定のものであることを保証する(`ParSeq[Char]` ではなく、`ParString` を返す)。 -(二つの型パラメータを取る順次 `*Like` トレイト群とは異なり)`ParSeqLike` は第三の型パラメータを取り、これは並列コレクションに対応する順次版の型を指定する。 - - class ParString(val str: String) - extends immutable.ParSeq[Char] - with ParSeqLike[Char, ParString, collection.immutable.WrappedString] - -前に定義したメソッドはそのまま使えるが、`filter` の内部で使われる protected なメソッドである `newCombiner` を追加する。 - - protected[this] override def newCombiner: Combiner[Char, ParString] = new ParStringCombiner - -次に `ParStringCombiner` クラスを実装する。 -コンバイナは ビルダのサブタイプだが `combine` というメソッドを導入する。 -`combine` メソッドは、別のコンバイナを引数に取り現在のコンバイナと引数のコンバイナの両方の要素を含んだ新しいコンバイナを返す。 -`combine` を呼び出すと、現在のコンバイナと引数のコンバイナは無効化される。 -もし引数が現在のコンバイナと同じオブジェクトである場合は、`combine` は現在のコンバイナを返す。 -このメソッドは並列計算の中で複数回呼び出されるので、最悪でも要素数に対して対数時間で実行するなど、効率的であることが期待されている。 - -`ParStringCombiner` は内部に `StringBuilder` の列を管理することにする。 -これで列の最後の `StringBuilder` に要素を追加することで `+=` を実装し、現在のコンバイナと引数のコンバイナの `StringBuilder` のリストを連結することで `combine` を実装できるようになる。 -並列計算の最後に呼び出される `result` メソッドは、全ての `StringBuilder` を追加することで並列文字列を生成する。 -これにより、要素のコピーは、毎回 `combine` を呼ぶたびに行われるのではなく、最後に一度だけ行われる。 -理想的には、この処理を並列化してコピーも並列に実行したい(並列配列ではそうなっている)が、文字列の内部表現にまで踏み込まない限りはこれが限界だ。そのため、この逐次的ボトルネックを受け入れなければいけない。 - - private class ParStringCombiner extends Combiner[Char, ParString] { - var sz = 0 - val chunks = new ArrayBuffer[StringBuilder] += new StringBuilder - var lastc = chunks.last - - def size: Int = sz - - def +=(elem: Char): this.type = { - lastc += elem - sz += 1 - this - } - - def clear = { - chunks.clear - chunks += new StringBuilder - lastc = chunks.last - sz = 0 - } - - def result: ParString = { - val rsb = new StringBuilder - for (sb <- chunks) rsb.append(sb) - new ParString(rsb.toString) - } - - def combine[U <: Char, NewTo >: ParString](other: Combiner[U, NewTo]) = if (other eq this) this else { - val that = other.asInstanceOf[ParStringCombiner] - sz += that.sz - chunks ++= that.chunks - lastc = chunks.last - this - } - } - - -## どうやってコンバイナを実装すればいい? - -これには定義済みのレシピは無い。 -扱っているデータ構造に依存するし、実装者による創意工夫が必要なことも多い。 -しかし、通常用いられれるいくつかの方法がある: - -
          -
        1. 連結とマージ。 -これらの演算に対して効率的な(通常、対数時間の)実装を持つデータ構造がある。 -もし、扱っているコレクションがそのようなデータ構造を用いてるならば、コレクションそのものをコンバイナとして使える。 -フィンガーツリー、ロープ、そしてヒープの多くが特にこの方法に向いている。
        2. -
        3. 二段階評価。 -並列配列と並列ハッシュテーブルで用いられてる方法で、要素が効率良く連結可能なバケットに部分ソート可能で、バケットから最終的なデータ構造が並列に構築可能なことを前提とする。 -まず、第一段階では別々のプロセッサが独立して要素をバケットに書き込んでいき、最後にバケットが連結される。 -第二段階では、データ構造が割り当てられ、別々のプロセッサがそれぞれデータ構造の異なる部分に交わらないバケットから要素を書き込んでいく。 -異なるプロセッサが絶対にデータ構造の同じ部分を変更しないように注意しないと、微妙な並行エラーが発生する可能性がある。 -前の節で示したように、この方法はランダムアクセス可能な列にも応用できる。
        4. -
        5. 並行データ構造 (concurrent data structure)。 -上の二つの方法はデータ構造そのものには同期機構を必要としないが、二つの異なるプロセッサが絶対に同じメモリの位置を更新しないような方法で並行して構築可能であることを前提とする。 -並行スキップリスト、並行ハッシュテーブル、split-ordered list、並行AVLツリーなど、複数のプロセッサから安全に更新することが可能な並行データ構造が数多く存在する。 -考慮すべき重要な点は、並行データ構造は水平にスケーラブルな挿入方法を持っていることだ。 -並行な並列コレクションは、コンバイナはコレクション自身であることが可能で、単一のコンバイナのインスタンスを並列演算を実行する全てのプロセッサによって共有できる。
        6. -
        - -## コレクションフレームワークとの統合 - -`ParString` はまだ完成していない。 -`filter`、`partition`、`takeWhile`、や `span` などのメソッドで使われるカスタムのコンバイナを実装したが、ほとんどの変換メソッドは暗黙の `CanBuildFrom` のエビデンスを必要とする(完全な説明に関しては、Scala コレクションのガイドを参照)。 -これを提供して `ParString` をコレクションフレームワークの一部として完全に統合するには、`GenericParTemplate` というもう一つのトレイトをミックスインして `ParString` のコンパニオンオブジェクトを定義する必要がある。 - - class ParString(val str: String) - extends immutable.ParSeq[Char] - with GenericParTemplate[Char, ParString] - with ParSeqLike[Char, ParString, collection.immutable.WrappedString] { - - def companion = ParString - -コンパニオンオブジェクトの中で `CanBuildFrom` パラメータのための暗黙の値を提供する。 - - object ParString { - implicit def canBuildFrom: CanCombineFrom[ParString, Char, ParString] = - new CanCombinerFrom[ParString, Char, ParString] { - def apply(from: ParString) = newCombiner - def apply() = newCombiner - } - - def newBuilder: Combiner[Char, ParString] = newCombiner - - def newCombiner: Combiner[Char, ParString] = new ParStringCombiner - - def apply(elems: Char*): ParString = { - val cb = newCombiner - cb ++= elems - cb.result - } - } - -## 更なるカスタム化 -- 並行とその他のコレクション - -並行コレクションの実装は一筋縄ではいかない(並列コレクションと違って、並行コレクションは `collection.concurrent.TriMap` のように並行して更新可能なもの)。 -コンバイナは特に頭をひねるところだ。 -これまで見てきた多くの**並列** (parallel) コレクションでは、コンバイナは二段階評価を行った。 -第一段階では、異なるプロセッサによって要素はコンバイナに加えられ、コンバイナは一つに組み合わされる。 -第二段階で、全ての要素がそろった時点で結果のコレクションが構築される。 - -コンバイナのもう一つの方法としては、結果と成るコレクションを要素を使って構築してしまう方法がある。 -これは、そのコレクションがスレッドセーフであることを必要とし、コンバイナは**並行** (concurrent) な要素の挿入を可能とする必要がある。 -この場合、単一のコンバイナが全てのプロセッサにより共有される。 - -並行コレクションを並列化するには、コンバイナは `canBeShared` メソッドをオーバーライドして `true` を返す必要がある。 -これで並列演算が呼び出される時に単一のコンバイナのみが作成されることが保証される。 -次に `+=` メソッドがスレッドセーフである必要がある。 -最後に `combine` メソッドは現在のコンバイナと引数のコンバイナが同一である場合は現在のコンバイナを返す必要があるが、それ以外の場合は例外を投げてもいい。 - -スプリッタは負荷分散のために小さいスプリッタへと分割される。 -デフォルトでは、`remaining` メソッドで得られる情報によってスプリッタの分割をいつ止めるか決定する。 -コレクションによっては `remaining` の呼び出しは高価で、スプリッタの分割を決定するのに他の方法を使ったほうが望ましい場合もある。 -その場合は、スプリッタの `shouldSplitFurther` メソッドをオーバーライドする。 - -デフォルトの実装では、残りの要素数がコレクションのサイズを並列度の8倍で割った数より多い場合に分割される。 - - def shouldSplitFurther[S](coll: ParIterable[S], parallelismLevel: Int) = - remaining > thresholdFromSize(coll.size, parallelismLevel) - -同値の実装として、スプリッタに何回分割されたかを格納するカウンタを持たせ、分割回数が `3 + log(parallelismLevel)` よりも多い場合だけ `shouldSplitFurther` が `true` を返すようにできるが、これは `remaining` の呼び出しを回避する。 - -さらに、ある特定のコレクションに対して `remaining` を呼び出すのが安価な演算ではない場合(つまり、コレクション内の要素数を求めなければいけない場合)、スプリッタの `isRemainingCheap` メソッドをオーバーライドして `false` を返すべきだ。 - -最後に、スプリッタの `remaining` メソッドの実装が非常に厄介な場合は、コレクションの `isStrictSplitterCollection` メソッドをオーバーライドして `false` を返すことができる。そのようなコレクションは、スプリッタが厳格である(つまり、`remaining` メソッドが正しい値を返す)ことが必要であるメソッドが失敗するようになる。大切なのは、これは for-展開で使われるメソッドには影響しないことだ。 diff --git a/ja/overviews/parallel-collections/overview.md b/ja/overviews/parallel-collections/overview.md deleted file mode 100644 index fcf5c32968..0000000000 --- a/ja/overviews/parallel-collections/overview.md +++ /dev/null @@ -1,167 +0,0 @@ ---- -layout: overview-large -title: 概要 - -discourse: false - -partof: parallel-collections -num: 1 -language: ja ---- - -**Aleksandar Prokopec, Heather Miller 著**
        -**Eugene Yokota 訳** - -## 動機 - -近年におけるプロセッサ製造業者のシングルコアからマルチコアへの移行のまっただ中で、産学ともに認めざるをえないのは「大衆的並列プログラミング」が大きな難題であり続けていることだ。 - -並列コレクション (parallel collection) は、並列プログラミングを容易にすることを目指して Scala 標準ライブラリに取り込まれた。 -ユーザは低レベルな並列化に関する詳細を気にせず、親しみやすいコレクションという高レベルの抽象概念 (abstraction) を利用できる。 -この概念が隠蔽する並列性によって、信頼性のある並列実行が開発者にとってより身近なものになると願っている。 - -アイディアは簡単だ -- コレクションはよく理解されており、よく使われているプログラミングの抽象概念だ。 -さらに、その規則性により、コレクションは効率良く、ユーザが意識すること無く、並列化することができる。 -ユーザが順次コレクション (sequential collection) を並列に計算するものに「置き換える」ことを可能にすることで、Scala の並列コレクションは、より多くのコードに並列性をもたらす方向に大きな一歩を踏み出したと言える。 - -例えば、大きなコレクションに対してモナディックな演算を行なっている逐次的 (sequential) な例をみてほしい: - - val list = (1 to 10000).toList - list.map(_ + 42) - -同じ演算を並列に実行するには、単に順次コレクションである `list` に対して `par` メソッドを呼び出すだけでいい。後は、順次コレクションを普通に使うのと同じように並列コレクションを利用できる。上記の例は以下のように並列化できる: - - list.par.map(_ + 42) - -Scala の並列コレクションの設計は、2.8 で導入された Scala の(順次)コレクションライブラリに影響を受けており、またその一部となるように深く統合されている。 -Scala の(順次)コレクションライブラリの重要なデータ構造の多くに対して対応する並列のコレクションを提供する: - -* `ParArray` -* `ParVector` -* `mutable.ParHashMap` -* `mutable.ParHashSet` -* `immutable.ParHashMap` -* `immutable.ParHashSet` -* `ParRange` -* `ParTrieMap` (`collection.concurrent.TrieMap` は 2.10 より追加された) - -Scala の並列コレクションライブラリは、順次ライブラリコレクションと共通のアーキテクチャを持つだけでなく、その**拡張性**も共有している。 -つまり、普通の順次コレクションと同様に、ユーザは独自のコレクション型を統合して、標準ライブラリにある他の並列コレクションにあるものと同じ(並列の)演算を自動的に継承できる。 - -## 例をいくつか - -並列コレクションの一般性と利便性を例示するために、いくつかの簡単な具体例を用いて説明しよう。全ての例において、ユーザが意識すること無く演算は並列に実行されている。 - -**注意:** 以下の例ではサイズの小さいコレクションを並列化しているが、これはあくまで説明のための例で、実用では推奨されない。 -一般的な指標として、コレクションのサイズが数千要素など、サイズが大きいほど並列化による高速化が顕著であることが多い。(並列コレクションのサイズと性能に関する詳細に関しては、このガイドの[性能](performance.html)に関する節の[該当する項](performance.html#id18)を参照してほしい) - -#### map - -並列 `map` を使って `String` のコレクションを大文字に変換する: - - scala> val lastNames = List("Smith","Jones","Frankenstein","Bach","Jackson","Rodin").par - lastNames: scala.collection.parallel.immutable.ParSeq[String] = ParVector(Smith, Jones, Frankenstein, Bach, Jackson, Rodin) - - scala> lastNames.map(_.toUpperCase) - res0: scala.collection.parallel.immutable.ParSeq[String] = ParVector(SMITH, JONES, FRANKENSTEIN, BACH, JACKSON, RODIN) - -#### fold - -`ParArray` の `fold` を使った合計: - - scala> val parArray = (1 to 10000).toArray.par - parArray: scala.collection.parallel.mutable.ParArray[Int] = ParArray(1, 2, 3, ... - - scala> parArray.fold(0)(_ + _) - res0: Int = 50005000 - -#### filter - -並列 `filter` を使ってラストネームがアルファベットの "J" 以降のから始まるものを選択する: - - scala> val lastNames = List("Smith","Jones","Frankenstein","Bach","Jackson","Rodin").par - lastNames: scala.collection.parallel.immutable.ParSeq[String] = ParVector(Smith, Jones, Frankenstein, Bach, Jackson, Rodin) - - scala> lastNames.filter(_.head >= 'J') - res0: scala.collection.parallel.immutable.ParSeq[String] = ParVector(Smith, Jones, Jackson, Rodin) - -## 並列コレクションの意味論 - -並列コレクションのインターフェイスは普通の順次コレクションと同じ感覚で使うことができるが、特に副作用を伴う演算と結合則が成立しない演算においては、演算の意味するものが異なることに注意する必要がある。 - -これを理解するためには、まず、演算が**どのように**並列実行されているのかをイメージする必要がある。 -概念的には、Scala の並列コレクションフレームワークは、ある並列コレクションにおける演算を並列化するために、再帰的にコレクションを「分割」(split) し、並列にそれぞれの部分に演算を適用し、並列に完了した全ての結果を再び「合成」(combine) することで行う。 - -このような並列コレクションの並行 (concurrent) で、「アウト・オブ・オーダー」("out-of-order"、訳注: 記述された順序以外で演算が実行されること) な意味論から以下の二つの結果が導き出される: - -1. **副作用を伴う演算は非決定性につながる可能性がある** - -2. **結合則が成立しない演算は非決定性につながる可能性がある** - -### 副作用を伴う演算 - -並列コレクションフレームワークの**並行**実行の意味論を考慮すると、計算の決定性 (determinism) を維持するためには、コレクションに対して副作用 (side-effect) を伴う演算は一般的に避けるべきだ。具体例としては、`foreach` のようなアクセスメソッドを用いる場合に、渡されるクロージャ中から外の `var` を増加することが挙げられる。 - - scala> var sum = 0 - sum: Int = 0 - - scala> val list = (1 to 1000).toList.par - list: scala.collection.parallel.immutable.ParSeq[Int] = ParVector(1, 2, 3,… - - scala> list.foreach(sum += _); sum - res01: Int = 467766 - - scala> var sum = 0 - sum: Int = 0 - - scala> list.foreach(sum += _); sum - res02: Int = 457073 - - scala> var sum = 0 - sum: Int = 0 - - scala> list.foreach(sum += _); sum - res03: Int = 468520 - -ここでは、`sum` が 0 に初期化されて、`list` に対して `foreach` が呼び出されるたびに `sum` が異なる値を持っていることが分かる。この非決定性の原因は**データ競合** (data race; 同一の可変変数に対する並行した読み書き) だ。 - -上の例だと、二つのスレッドが `sum` の**同じ**値を読み込んで、その `sum` の値に対して何らかの演算を実行した後で、`sum` に新しい値を書きこもうとするかもしれない。以下に示すように、その場合は、大切な結果の上書き(つまり、損失)が起きる可能性がある: - - ThreadA: sum の値を読み込む、sum = 0 sum の値: 0 - ThreadB: sum の値を読み込む、sum = 0 sum の値: 0 - ThreadA: sum を 760 増加する、sum = 760 を書き込む sum の値: 760 - ThreadB: sum を 12 増加する、sum = 12 を書き込む sum の値: 12 - -上の例は、どちらか一方のスレッドが `0` に各自の並列コレクションの担当部分からの要素を加算する前に、二つのスレッドが同じ値 `0` を読み込むシナリオを示している。この場合、`ThreadA` は `0` を読み込み、`0+760` を合計し、`ThreadB` も `0` に自分の要素を加算し、`0+12` となった。各自の合計を計算した後、それぞれが経験結果を `sum` に書き込む。`ThreadA` が `ThreadB` よりも先に書き込むが、直後に `ThreadB` がそれを上書きするため、実質 `760` という値が上書きされ、失われることになった。 - -### 結合則が成立しない演算 - -**「アウト・オブ・オーダー」**実行の意味論を考慮すると、非決定性を避けるためには、慎重に、結合則が成立する演算のみを実行するべきだ。つまり、並列コレクション `pcoll` に対して `pcoll.reduce(func)` のように高階関数を呼び出すとき、`func` が要素に適用される順序が任意でも大丈夫であるように気をつけるべきだということだ。単純で、かつ明快な結合則が成立しない演算の例は減算だ: - - scala> val list = (1 to 1000).toList.par - list: scala.collection.parallel.immutable.ParSeq[Int] = ParVector(1, 2, 3,… - - scala> list.reduce(_-_) - res01: Int = -228888 - - scala> list.reduce(_-_) - res02: Int = -61000 - - scala> list.reduce(_-_) - res03: Int = -331818 - -上の例では、`ParVector[Int]` の `reduce` メソッドが `_-_` と共に呼び出されている。 -これは二つの不特定の要素を取り出し、前者から後者を引く。 -並列コレクションフレームワークはスレッドを呼び出し、それぞれが実質的に、独自にコレクションから異なる部位を取り出し `reduce(_-_)` を実行するため、同じコレクションに `reduce(_-_)` を実行するたびに毎回異なった結果が得られることとなる。 - -**注意:** 結合則が成立しない演算と同様に、交換則が成立しない演算も並列コレクションの高階関数に渡されると非決定的な振る舞いをみせると思われがちだが、それは間違っている。単純な例としては、文字列の連結がある。結合則は成立するが、交換則は成立しない演算だ: - - scala> val strings = List("abc","def","ghi","jk","lmnop","qrs","tuv","wx","yz").par - strings: scala.collection.parallel.immutable.ParSeq[java.lang.String] = ParVector(abc, def, ghi, jk, lmnop, qrs, tuv, wx, yz) - - scala> val alphabet = strings.reduce(_++_) - alphabet: java.lang.String = abcdefghijklmnopqrstuvwxyz - -並列コレクションにおける**「アウト・オブ・オーダー」**の意味論は、演算が(**時間的**な意味で、つまり非逐次的に)バラバラの順序で実行されるという意味であって、結果が(**空間的**に)バラバラに**「再合成」**されるという意味ではない。結果は、一般的に**順序どおり** (in-order) に再合成される。つまり、A、B、C の順番に分割された並列コレクションは、再び A、B、C の順番に再合成される。任意の B、C、A というような順序にはならない。 - -異なる並列コレクションの型における、分割と再合成の詳細ついてはこのガイドの[アーキテクチャ](architecture.html)の節を参照してほしい。 diff --git a/ja/overviews/parallel-collections/performance.md b/ja/overviews/parallel-collections/performance.md deleted file mode 100644 index 91c5d79f82..0000000000 --- a/ja/overviews/parallel-collections/performance.md +++ /dev/null @@ -1,227 +0,0 @@ ---- -layout: overview-large -title: 性能の測定 - -discourse: false - -partof: parallel-collections -num: 8 -outof: 8 -language: ja ---- - -## JVM における性能 - -JVM における性能モデルは論評こそは色々あるが、それに巻き込まれて結局よく理解されてないと言える。 -様々な理由から、あるコードは期待されているよりも性能が悪かったり、スケーラブルではなかったりする。 -以下にいくつかの理由をみていく。 - -一つは JVM 上のアプリケーションのコンパイル工程が静的にコンパイルされた言語のそれとは同じではないということが挙げられる(\[[2][2]\] 参照)。 -Java と Scala のコンパイラはソースコードを JVM バイトコードに変換するだけで、最適化はほとんど行わない。 -現代的な JVM の多くではプログラムのバイトコードが実行されると、それは実行しているマシンのコンピュータアーキテクチャのマシンコードに変換する。 -これはジャストインタイムコンパイラ (just-in-time compiler、JITコンパイラ) と呼ばれる。 -しかし、JITコンパイラは速度を優先するため、最適化のレベルは低いといえる。 -再コンパイルを避けるため、いわゆる HotSpot コンパイラは頻繁に実行される部分だけを最適化する。 -これがベンチマーク作者へ及ぼす影響は、プログラムを実行するたびにそれらが異なる性能を持つことだ。 -(例えば、ある特定のメソッドのような)同じコードを同じ JVM インスタンス上で複数回実行したとしても、そのコードが間に最適化された場合は全く異なる性能を示す可能性がある。 -さらに、コードのある部分の実行時間を測定することは JITコンパイラが最適化を実行している時間を含む可能性があり、一貫性に欠ける結果となることもある。 - -JVM において隠れて実行されるものの一つに自動メモリ管理がある。 -ときどきプログラムの実行が停止され、ガベージコレクタが実行されるのだ。 -もしベンチマーク測定されるプログラムがヒープメモリを少しでも使ったならば(ほとんどの JVM プログラムは使用する)、ガベージコレクタが実行されなければならず、測定を歪めることになる。測定するプログラムを多くの回数実行することでガベージコレクションも何回も発生させガベージコレクションの影響を償却 (amortize) する必要がある。 - -性能劣化の原因の一つとして、ジェネリックなメソッドにプリミティブ型を渡すことによって暗黙に発生するボクシングとアンボクシングが挙げられる。 -実行時にプリミティブ型は、ジェネリックな型パラメータに渡せるように、それらを表すオブジェクトに変換される。 -これは余計なメモリ割り当てを発生させ、遅い上に、ヒープにはいらないゴミができる。 - -並列的な性能に関して言えば、プログラマがオブジェクトがどこに割り当てられるかの明示的なコントールを持たないためメモリ輻輳 (memory contention) がよく問題となる。 -実際、GC効果により、オブジェクトがメモリの中を動きまわった後である、アプリケーションライフタイムにおける後期のステージでもメモリ輻輳が発生しうる。 -ベンチマークを書くときにはこのような効果も考慮する必要がある。 - -## マイクロベンチマークの例 - -コードの性能を計測するにあたり、上に挙げた効果を回避する方法がいくつかある。 -第一に、対象となるマイクロベンチマークを十分な回数実行することで JITコンパイラがマシンコードにコンパイルし、また最適化されたことを確実にしなければいけない。これはウォームアップ段階 (warm-up phase) と呼ばれる。 - -マイクロベンチマークそのものは、独立した JVM インスタンスで実行することでプログラムの別の部分により割り当てられたオブジェクトのガベージコレクションや無関係な JITコンパイルによるノイズを低減すべきだ。 - -より積極的な最適化を行うサーバーバージョンの HotSpot JVM を用いて実行するべきだ。 - -最後に、ベンチマークの最中にガベージコレクションが発生する可能性を低減するために、理想的にはベンチマークの実行の前にガベージコレクションを行い、次のサイクルを可能な限り延期すべきだ。 - -Scala の標準ライブラリには `scala.testing.Benchmark` トレイトが定義されており、上記のことを考えて設計されている。 -並行トライの `map` 演算をベンチマークする具体例を以下に示す: - - import collection.parallel.mutable.ParTrieMap - import collection.parallel.ForkJoinTaskSupport - - object Map extends testing.Benchmark { - val length = sys.props("length").toInt - val par = sys.props("par").toInt - val partrie = ParTrieMap((0 until length) zip (0 until length): _*) - - partrie.tasksupport = new ForkJoinTaskSupport(new scala.concurrent.forkjoin.ForkJoinPool(par)) - - def run = { - partrie map { - kv => kv - } - } - } - -`run` メソッドはマイクロベンチマークの本体で、これが何度も実行され実行時間が計測される。 -上のコードでの `Map` オブジェクトは `scala.testing.Benchmark` トレイトを拡張しシステム指定されたいくつかのパラメータを読み込む。 -`par` は並列度、`length` はトライの要素数をそれぞれ表す。 - -このプログラムをコンパイルした後、このように実行する: - - java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=1 -Dlength=300000 Map 10 - -`server` フラグはサーバ VM が使われることを指定する。 -`cp` はクラスパスを指定し、現ディレクトリと Scala ライブラリの jar を含む。 -`-Dpar` と `-Dlength` は並列度と要素数を指定する。 -最後に、`10` はベンチマークが同じ JVM 内で実行される回数を指定する。 - -以下はハイパースレッディング付きクアッドコアの i7 で `par` を `1`、`2`、`4` と `8` に設定して得られた実行時間だ: - - Map$ 126 57 56 57 54 54 54 53 53 53 - Map$ 90 99 28 28 26 26 26 26 26 26 - Map$ 201 17 17 16 15 15 16 14 18 15 - Map$ 182 12 13 17 16 14 14 12 12 12 - -初期の試行の実行時間が高めであるが、コードが最適化されると低減することが上のデータから分かる。 -さらに、`4` スレッドに対して `8` スレッドの性能向上が少ししかみられないことからハイパースレッディングによる利益があまりないことも分かる。 - -## コレクションがどれだけ大きければ並列化するべきか? - -これはよく問われる質問だが、これには少し込み入った答が必要になる。 - -並列化の採算が取れる(つまり、並列化による高速化が並列化することに伴うオーバーヘッドを上回る)コレクションのサイズは多くの要素に依存するからだ。 -全ては書ききれないが、いくつかを挙げる: - -
          -
        • マシンのアーキテクチャ。 -異なる CPU の種類はそれぞれ異なる性能特性やスケーラビリティ特性を持つ。 -それとは別に、マシンがマルチコアなのかマザーボード経由で通信するマルチプロセッサなのかにもよる。
        • -
        • JVM のベンダとバージョン。 -異なる VM はそれぞれコードに対して異なる最適化を実行時に行う。 -異なるメモリ管理や同期のテクニックを実装する。 -ForkJoinPool をサポートしないものもあるので、その場合はよりオーバーヘッドのかかる ThreadPoolExecutor が補欠で使われる。
        • -
        • 要素あたりの負荷。 -並列演算の関数や条件関数が要素あたりの負荷を決定する。 -負荷が軽ければ軽いほど、並列化による高速化を得るのに必要な要素数は多くなる。
        • -
        • 特定のコレクション。 -例えば、ParArrayParTrieMap のスプリッタではコレクションの走査するスピードが異なり、これは走査だけをみても要素あたりの負荷があるということだ。
        • -
        • 特定の演算。 -例えば、ParVector の(filter のような)変換メソッドは(foreach のような)アクセスメソッドにくらべてかなり遅い。
        • -
        • 副作用。 -メモリ領域を並行で変更したり、foreachmap その他に渡されるクロージャから同期機構を用いると輻輳が発生する可能性がある。
        • -
        • メモリ管理。 -大量にオブジェクトを割り当てるとガベージコレクションサイクルが誘発される。 -新しいオブジェクトへの参照がどのように取り回されるかによって GC サイクルにかかる時間が異なる。
        • -
        - -上に挙げたものを単独でみても、それらを論理的に論じてコレクションの採算が取れるサイズの正確な答を出すのは簡単なことではない。 -サイズがいくつであるかを大まかに示すために、以下に安価で副作用を伴わないベクトルの集約演算(この場合、sum)をクアッドコアの i7(ハイパースレッディング無し)で JDK7 上で実行した具体例を示す: - - import collection.parallel.immutable.ParVector - - object Reduce extends testing.Benchmark { - val length = sys.props("length").toInt - val par = sys.props("par").toInt - val parvector = ParVector((0 until length): _*) - - parvector.tasksupport = new collection.parallel.ForkJoinTaskSupport(new scala.concurrent.forkjoin.ForkJoinPool(par)) - - def run = { - parvector reduce { - (a, b) => a + b - } - } - } - - object ReduceSeq extends testing.Benchmark { - val length = sys.props("length").toInt - val vector = collection.immutable.Vector((0 until length): _*) - - def run = { - vector reduce { - (a, b) => a + b - } - } - } - -まずこのベンチマークを `250000` 要素で実行して `1`、`2`、`4` スレッドに関して以下の結果を得た: - - java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=1 -Dlength=250000 Reduce 10 10 - Reduce$ 54 24 18 18 18 19 19 18 19 19 - java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=2 -Dlength=250000 Reduce 10 10 - Reduce$ 60 19 17 13 13 13 13 14 12 13 - java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=4 -Dlength=250000 Reduce 10 10 - Reduce$ 62 17 15 14 13 11 11 11 11 9 - -次に、順次ベクトルの集約と実行時間を比較するために要素数を `120000` まで減らして `4` スレッドを使った: - - java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=4 -Dlength=120000 Reduce 10 10 - Reduce$ 54 10 8 8 8 7 8 7 6 5 - java -server -cp .:../../build/pack/lib/scala-library.jar -Dlength=120000 ReduceSeq 10 10 - ReduceSeq$ 31 7 8 8 7 7 7 8 7 8 - -この場合は、`120000` 要素近辺が閾値のようだ。 - -もう一つの具体例として、`mutable.ParHashMap` と(変換メソッドである)`map` メソッドに注目して同じ環境で以下のベンチマークを実行する: - - import collection.parallel.mutable.ParHashMap - - object Map extends testing.Benchmark { - val length = sys.props("length").toInt - val par = sys.props("par").toInt - val phm = ParHashMap((0 until length) zip (0 until length): _*) - - phm.tasksupport = new collection.parallel.ForkJoinTaskSupport(new scala.concurrent.forkjoin.ForkJoinPool(par)) - - def run = { - phm map { - kv => kv - } - } - } - - object MapSeq extends testing.Benchmark { - val length = sys.props("length").toInt - val hm = collection.mutable.HashMap((0 until length) zip (0 until length): _*) - - def run = { - hm map { - kv => kv - } - } - } - -`120000` 要素だとスレッド数を `1` から `4` に変化させていくと以下の実行時間が得られる: - - java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=1 -Dlength=120000 Map 10 10 - Map$ 187 108 97 96 96 95 95 95 96 95 - java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=2 -Dlength=120000 Map 10 10 - Map$ 138 68 57 56 57 56 56 55 54 55 - java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=4 -Dlength=120000 Map 10 10 - Map$ 124 54 42 40 38 41 40 40 39 39 - -ここで要素数を `15000` まで減らして順次ハッシュマップと比較する: - - java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=1 -Dlength=15000 Map 10 10 - Map$ 41 13 10 10 10 9 9 9 10 9 - java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=2 -Dlength=15000 Map 10 10 - Map$ 48 15 9 8 7 7 6 7 8 6 - java -server -cp .:../../build/pack/lib/scala-library.jar -Dlength=15000 MapSeq 10 10 - MapSeq$ 39 9 9 9 8 9 9 9 9 9 - -このコレクションのこの演算に関しては、`15000` 要素以上あるときには並列化の高速化による採算が取れる(一般に、配列やベクトルに比べてハッシュマップやハッシュ集合の方が少ない要素で並列化の効果を得ることができる)。 - -## 参照 - -1. [Anatomy of a flawed microbenchmark, Brian Goetz][1] -2. [Dynamic compilation and performance measurement, Brian Goetz][2] - - [1]: http://www.ibm.com/developerworks/java/library/j-jtp02225/index.html "flawed-benchmark" - [2]: http://www.ibm.com/developerworks/library/j-jtp12214/ "dynamic-compilation" diff --git a/ja/overviews/reflection/annotations-names-scopes.md b/ja/overviews/reflection/annotations-names-scopes.md deleted file mode 100644 index e2967599a0..0000000000 --- a/ja/overviews/reflection/annotations-names-scopes.md +++ /dev/null @@ -1,410 +0,0 @@ ---- -layout: overview-large - -discourse: false - -partof: reflection -num: 4 -outof: 6 -language: ja -title: アノテーション、名前、スコープ、その他 ---- - -EXPERIMENTAL - -## アノテーション - -Scala において宣言は `scala.annotation.Annotation` のサブタイプを用いて注釈を付けることができる。 -さらに、Scala は [Java のアノテーションシステム](http://docs.oracle.com/javase/7/docs/technotes/guides/language/annotations.html#_top)に統合するため、標準 Java コンパイラによって生成されたアノテーションを取り扱うこともできる。 - -アノテーションは、それが永続化されていればリフレクションを使ってインスペクトすることができるため、アノテーション付きの宣言を含むクラスファイルから読み込むことができる。カスタムアノテーション型は -`scala.annotation.StaticAnnotation` か -`scala.annotation.ClassfileAnnotation` を継承することで永続化することができる。 -その結果、アノテーション型のインスタンスはクラスファイル内の特別な属性として保存される。 -実行時リフレクションに必要なメタデータを永続化するには -`scala.annotation.Annotation` を継承するだけでは不十分であることに注意してほしい。さらに、 -`scala.annotation.ClassfileAnnotation` を継承しても実行時には Java -アノテーションとしては認識されないことに注意してほしい。そのためには、Java でアノテーションを書く必要がある。 - -API は 2種類のアノテーションを区別する: - -- **Java アノテーション**: Java コンパイラによって生成された定義に付加されたアノテーション、つまりプログラムの定義に付けられた `java.lang.annotation.Annotation` のサブタイプ。Scala リフレクションによって読み込まれると `scala.annotation.ClassfileAnnotation` トレイトが自動的に全ての Java アノテーションに追加される。 -- **Scala アノテーション**: Scala コンパイラによって生成された定義や型に付加されたアノテーション。 - -Java と Scala のアノテーションの違いは -`scala.reflect.api.Annotations#Annotation` に顕著に現れており、これは -`scalaArgs` と `javaArgs` 両方を公開する。 -`scala.annotation.ClassfileAnnotation` を継承する -Scala または Java アノテーションに対しては `scalaArgs` は空で -(もしあれば) 引数は `javaArgs` に保持される。他の全ての Scala -アノテーションの場合は、引数は `scalaArgs` に保持され、`javaArgs` は空となる。 - -`scalaArgs` 内の引数は型付けされた構文木として表される。 -これらの構文木はタイプチェッカより後のどのフェーズにおいても変換されないことに注意する必要がある。 -`javaArgs` 内の引数は `scala.reflect.api.Names#Name` から -`scala.reflect.api.Annotations#JavaArgument` へのマップとして表現される。 -`JavaArgument` のインスタンスは Java アノテーションの引数の様々な型を表現する: - -
          -
        • リテラル (プリミティブ型と文字列の定数)
        • -
        • 配列
        • -
        • 入れ子になったアノテーション
        • -
        - -## 名前 - -**名前** (name) は文字列の簡単なラッパーだ。 -[`Name`](http://www.scala-lang.org/api/current/index.html#scala.reflect.api.Names$NameApi) -には 2つのサブタイプ `TermName` と `TypeName` があり (オブジェクトやメンバーのような) 項の名前と -(クラス、トレイト、型メンバのような) 型の名前を区別する。同じオブジェクト内に同名の項と型が共存することができる。別の言い方をすると、型と項は別の名前空間を持つ。 - -これらの名前はユニバースに関連付けられている。具体例を使って説明しよう。 - - scala> import scala.reflect.runtime.universe._ - import scala.reflect.runtime.universe._ - - scala> val mapName = TermName("map") - mapName: scala.reflect.runtime.universe.TermName = map - -上のコードでは、実行時リフレクション・ユニバースに関連付けられた `Name` を作成している。 -(これはパス依存型である `reflect.runtime.universe.TermName` が表示されていることからも分かる。) - -名前は型のメンバの照会に用いられる。例えば、`List` クラス内で宣言されている (項である) `map` メソッドを検索するには以下のようにする: - - scala> val listTpe = typeOf[List[Int]] - listTpe: scala.reflect.runtime.universe.Type = scala.List[Int] - - scala> listTpe.member(mapName) - res1: scala.reflect.runtime.universe.Symbol = method map - -型メンバを検索するには `TypeName` を代わりに使って `member` を呼び出す。 -暗黙の変換を使って文字列から項もしくは型の名前に変換することもできる: - - scala> listTpe.member("map": TermName) - res2: scala.reflect.runtime.universe.Symbol = method map - -### 標準名 - -Scala のプログラムにおいて、「`_root_`」のような特定の名前は特殊な意味を持つ。 -そのため、それらは Scala の構造物をリフレクションを用いてアクセスするのに欠かすことができない。 -例えば、リフレクションを用いてコンストラクタを呼び出すには**標準名** (standard name) -`universe.nme.CONSTRUCTOR` を用いる。これは、JVM 上でのコンストラクタ名である項名「``」を指す。 - -- 「``」、「`package`」、「`_root_`」のような**標準項名** (standard term names) と -- 「``」、「`_`」、「`_*`」のような**標準型名** (standard type names) - -の両方が存在する。 - -「`package`」のようないくつかの名前は型名と項名の両方が存在する。 -標準名は `Universe` クラスの `nme` と `tpnme` というメンバとして公開されている。 -全ての標準名の仕様は [API doc](http://www.scala-lang.org/api/current/index.html#scala.reflect.api.StandardNames) を参照。 - -## スコープ - -**スコープ** (scope) は一般にある構文スコープ内の名前をシンボルに関連付ける。 -スコープは入れ子にすることもできる。リフレクション API -で公開されているスコープの基底型は [Symbol](http://www.scala-lang.org/api/current/index.html#scala.reflect.api.Symbols$Symbol) の iterable という最小限のインターフェイスのみを公開する。 - -追加機能は -[scala.reflect.api.Types#TypeApi](http://www.scala-lang.org/api/current/index.html#scala.reflect.api.Types$TypeApi) -内で定義されている `member` と `declarations` -が返す**メンバスコープ** (member scope) にて公開される。 -[scala.reflect.api.Scopes#MemberScope](http://www.scala-lang.org/api/current/index.html#scala.reflect.api.Scopes$MemberScope) -は `sorted` メソッドをサポートしており、これはメンバを**宣言順に**ソートする。 - -以下に `List` クラスでオーバーライドされている全てのシンボルのリストを宣言順に返す具体例をみてみよう: - - scala> val overridden = listTpe.declarations.sorted.filter(_.isOverride) - overridden: List[scala.reflect.runtime.universe.Symbol] = List(method companion, method ++, method +:, method toList, method take, method drop, method slice, method takeRight, method splitAt, method takeWhile, method dropWhile, method span, method reverse, method stringPrefix, method toStream, method foreach) - -## Expr - -構文木の基底型である `scala.reflect.api.Trees#Tree` の他に、型付けされた構文木は -[`scala.reflect.api.Exprs#Expr`](http://www.scala-lang.org/api/current/index.html#scala.reflect.api.Exprs$Expr) 型によっても表すことができる。 -`Expr` は構文木と、その構文木の型に対するアクセスを提供するための型タグをラッピングする。 -`Expr` は主にマクロのために便宜的に型付けられた構文木を作るために使われる。多くの場合、これは -`reify` と `splice` メソッドが関わってくる。 -(詳細は[マクロ](http://docs.scala-lang.org/ja/overviews/macros/overview.html)を参照) - -## フラグとフラグ集合 - -**フラグ** (flag) は -`scala.reflect.api.Trees#Modifiers` である `flags` を用いて定義を表す構文木に修飾子を与えるのに使われる。 -以下に修飾子を受け付ける構文木を挙げる: - -- `scala.reflect.api.Trees#ClassDef`。クラスとトレイト。 -- `scala.reflect.api.Trees#ModuleDef`。オブジェクト。 -- `scala.reflect.api.Trees#ValDef`。`val`、`var`、パラメータ、自分型注釈。 -- `scala.reflect.api.Trees#DefDef`。メソッドとコンストラクタ。 -- `scala.reflect.api.Trees#TypeDef`。型エイリアス、抽象型メンバ、型パラメータ。 - -例えば、`C` という名前のクラスを作るには以下のように書く: - - ClassDef(Modifiers(NoFlags), TypeName("C"), Nil, ...) - -ここでフラグ集合は空だ。`C` を private にするには、以下のようにする: - - ClassDef(Modifiers(PRIVATE), TypeName("C"), Nil, ...) - -垂直バー演算子 (`|`) を使って組み合わせることができる。例えば、private final -クラスは以下のように書く: - - ClassDef(Modifiers(PRIVATE | FINAL), TypeName("C"), Nil, ...) - -全てのフラグのリストは -`scala.reflect.api.FlagSets#FlagValues` にて定義されており、 -`scala.reflect.api.FlagSets#Flag` から公開されている。 -(一般的には、これを -`import scala.reflect.runtime.universe.Flag._` のようにワイルドカードインポートする。) - -定義の構文木はコンパイル後にはシンボルとなるため、これらの構文木の修飾子のフラグは結果となるシンボルのフラグへと変換される。 -構文木と違ってシンボルはフラグを公開しないが、`isXXX` -というパターンのテストメソッドを提供する -(例えば `isFinal` は final かどうかをテストする)。 -特定のフラグはある種類のシンボルでしか使われないため、場合によってはシンボルを -`asTerm`、`asType`、`asClass` といったメソッドを使って変換する必要がある。 - -**注意:** リフレクションAPI のこの部分は再設計の候補に挙がっている。リフレクションAPI -の将来のリリースにおいてフラグ集合が他のものと置き換わる可能性がある。 - -## 定数 - -Scala の仕様において**定数式** (constant expression) と呼ばれる式は -Scala コンパイラによってコンパイル時に評価することができる。 -以下に挙げる式の種類はコンパイル時定数だ。 -([Scala 言語仕様 の 6.24](http://scala-lang.org/files/archive/spec/2.11/06-expressions.html#constant-expressions) 参照): - -1. プリミティブ値クラスのリテラル ([Byte](http://www.scala-lang.org/api/current/index.html#scala.Byte)、 [Short](http://www.scala-lang.org/api/current/index.html#scala.Short)、 [Int](http://www.scala-lang.org/api/current/index.html#scala.Int)、 [Long](http://www.scala-lang.org/api/current/index.html#scala.Long)、 [Float](http://www.scala-lang.org/api/current/index.html#scala.Float)、 [Double](http://www.scala-lang.org/api/current/index.html#scala.Double)、 [Char](http://www.scala-lang.org/api/current/index.html#scala.Char)、 [Boolean](http://www.scala-lang.org/api/current/index.html#scala.Boolean) および [Unit](http://www.scala-lang.org/api/current/index.html#scala.Unit))。これは直接対応する型で表される。 -2. 文字列リテラル。これは文字列のインスタンスとして表される。 -3. 一般に [scala.Predef#classOf](http://www.scala-lang.org/api/current/index.html#scala.Predef$@classOf[T]:Class[T]) で構築されるクラスへの参照。[型](http://www.scala-lang.org/api/current/index.html#scala.reflect.api.Types$Type)として表される。 -4. Java の列挙要素。[シンボル](http://www.scala-lang.org/api/current/index.html#scala.reflect.api.Symbols$Symbol)として表される。 - -定数式の用例としては - -- 構文木内のリテラル (`scala.reflect.api.Trees#Literal` 参照) -- Java のクラスファイルアノテーションへ渡されるリテラル (`scala.reflect.api.Annotations#LiteralArgument` 参照) - -などがある。具体例をみてみよう。 - - Literal(Constant(5)) - -上の式は Scala ソース内での整数リテラル `5` を表す AST を構築する。 - -`Constant` は「仮想ケースクラス」の一例で、普通のクラスなのだが、あたかもケースクラスであるかのうように構築したりパターンマッチしたりすることができる。 -`Literal` と `LiteralArgument` の両方ともがリテラルのコンパイル時定数を返す -`value` メソッドを公開する。 - -具体例で説明しよう: - - Constant(true) match { - case Constant(s: String) => println("A string: " + s) - case Constant(b: Boolean) => println("A Boolean value: " + b) - case Constant(x) => println("Something else: " + x) - } - assert(Constant(true).value == true) - -クラス参照は `scala.reflect.api.Types#Type` のインスタンスを用いて表される。 -この参照は、`scala.reflect.runtime.currentMirror` のような -`RuntimeMirror` の `runtimeClass` メソッドを用いてランタイムクラスへと変換することができる。 -(Scala コンパイラがクラス参照の処理を行なっている段階においては、その参照が指すランタイムクラスがまだコンパイルされていない可能性があるため、このように型からランタイムクラスへと変換することが必要となる。) - -Java の列挙要素への参照はシンボル (`scala.reflect.api.Symbols#Symbol`) -として表され、対応する列挙要素を JVM 上で返すことができるメソッドを持つ。 -`RuntimeMirror` を使って対応する列挙型や列挙値への参照の実行時の値をインスペクトすることができる。 - -具体例をみてこう: - - // Java ソース: - enum JavaSimpleEnumeration { FOO, BAR } - - import java.lang.annotation.*; - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.TYPE}) - public @interface JavaSimpleAnnotation { - Class classRef(); - JavaSimpleEnumeration enumRef(); - } - - @JavaSimpleAnnotation( - classRef = JavaAnnottee.class, - enumRef = JavaSimpleEnumeration.BAR - ) - public class JavaAnnottee {} - - // Scala ソース: - import scala.reflect.runtime.universe._ - import scala.reflect.runtime.{currentMirror => cm} - - object Test extends App { - val jann = typeOf[JavaAnnottee].typeSymbol.annotations(0).javaArgs - - def jarg(name: String) = jann(TermName(name)) match { - // Constant is always wrapped in a Literal or LiteralArgument tree node - case LiteralArgument(ct: Constant) => value - case _ => sys.error("Not a constant") - } - - val classRef = jarg("classRef").value.asInstanceOf[Type] - println(showRaw(classRef)) // TypeRef(ThisType(), JavaAnnottee, List()) - println(cm.runtimeClass(classRef)) // class JavaAnnottee - - val enumRef = jarg("enumRef").value.asInstanceOf[Symbol] - println(enumRef) // value BAR - - val siblings = enumRef.owner.typeSignature.declarations - val enumValues = siblings.filter(sym => sym.isVal && sym.isPublic) - println(enumValues) // Scope { - // final val FOO: JavaSimpleEnumeration; - // final val BAR: JavaSimpleEnumeration - // } - - val enumClass = cm.runtimeClass(enumRef.owner.asClass) - val enumValue = enumClass.getDeclaredField(enumRef.name.toString).get(null) - println(enumValue) // BAR - } - -## プリティプリンタ - -[`Trees`](http://www.scala-lang.org/api/current/index.html#scala.reflect.api.Trees) と -[`Types`](http://www.scala-lang.org/api/current/index.html#scala.reflect.api.Types) -を整形して表示するユーティリティを説明しよう。 - -### 構文木の表示 - -`show` メソッドは、リフレクションオブジェクトを整形して表示する。 -この形式は Scala コードの糖衣構文を展開して Java のようしたものを提供する。具体例をみていこう: - - scala> import scala.reflect.runtime.universe._ - import scala.reflect.runtime.universe._ - - scala> def tree = reify { final class C { def x = 2 } }.tree - tree: scala.reflect.runtime.universe.Tree - - scala> show(tree) - res0: String = - { - final class C extends AnyRef { - def () = { - super.(); - () - }; - def x = 2 - }; - () - } - -`showRaw` メソッドは、Scala の構文木 (AST) のようなリフレクションオブジェクトの内部構造を表示する。 -これは Scala のタイプチェッカが見るものと同じものだ。 - -ここで注意すべきなのは、この形式どおりに構文木を構築すればマクロの実装でも使えるのじゃないかと思うかもしれないが、うまくいかないことが多いということだ。これはシンボルについての情報などが完全には表示されていないためだ (名前だけが表示される)。 -そのため、妥当な Scala コードがあるときにその AST をインスペクトするのに向いていると言える。 - - scala> showRaw(tree) - res1: String = Block(List( - ClassDef(Modifiers(FINAL), TypeName("C"), List(), Template( - List(Ident(TypeName("AnyRef"))), - emptyValDef, - List( - DefDef(Modifiers(), nme.CONSTRUCTOR, List(), List(List()), TypeTree(), - Block(List( - Apply(Select(Super(This(tpnme.EMPTY), tpnme.EMPTY), nme.CONSTRUCTOR), List())), - Literal(Constant(())))), - DefDef(Modifiers(), TermName("x"), List(), List(), TypeTree(), - Literal(Constant(2))))))), - Literal(Constant(()))) - -`showRaw` はインスペクトしたものの `scala.reflect.api.Types` を併記することができる。 - - scala> import scala.tools.reflect.ToolBox // requires scala-compiler.jar - import scala.tools.reflect.ToolBox - - scala> import scala.reflect.runtime.{currentMirror => cm} - import scala.reflect.runtime.{currentMirror=>cm} - - scala> showRaw(cm.mkToolBox().typeCheck(tree), printTypes = true) - res2: String = Block[1](List( - ClassDef[2](Modifiers(FINAL), TypeName("C"), List(), Template[3]( - List(Ident[4](TypeName("AnyRef"))), - emptyValDef, - List( - DefDef[2](Modifiers(), nme.CONSTRUCTOR, List(), List(List()), TypeTree[3](), - Block[1](List( - Apply[4](Select[5](Super[6](This[3](TypeName("C")), tpnme.EMPTY), ...))), - Literal[1](Constant(())))), - DefDef[2](Modifiers(), TermName("x"), List(), List(), TypeTree[7](), - Literal[8](Constant(2))))))), - Literal[1](Constant(()))) - [1] TypeRef(ThisType(scala), scala.Unit, List()) - [2] NoType - [3] TypeRef(NoPrefix, TypeName("C"), List()) - [4] TypeRef(ThisType(java.lang), java.lang.Object, List()) - [5] MethodType(List(), TypeRef(ThisType(java.lang), java.lang.Object, List())) - [6] SuperType(ThisType(TypeName("C")), TypeRef(... java.lang.Object ...)) - [7] TypeRef(ThisType(scala), scala.Int, List()) - [8] ConstantType(Constant(2)) - -### 型の表示 - -`show` メソッドは型を**可読な**文字列形式で表示することができる: - - scala> import scala.reflect.runtime.universe._ - import scala.reflect.runtime.universe._ - - scala> def tpe = typeOf[{ def x: Int; val y: List[Int] }] - tpe: scala.reflect.runtime.universe.Type - - scala> show(tpe) - res0: String = scala.AnyRef{def x: Int; val y: scala.List[Int]} - -`scala.reflect.api.Trees` のための `showRaw` 同様に、 -`scala.reflect.api.Types` のための `showRaw` -は Scala タイプチェッカが使う Scala AST を表示する。 - - scala> showRaw(tpe) - res1: String = RefinedType( - List(TypeRef(ThisType(scala), TypeName("AnyRef"), List())), - Scope( - TermName("x"), - TermName("y"))) - -この `showRaw` メソッドにはデフォルトでは `false` -になっている名前付きパラメータ `printIds` と `printKinds` を持つ。 -`true` を渡すことで `showRaw` はシンボルのユニークID -とシンボルの種類 (パッケージ、型、メソッド、getter その他) を表示することができる。 - - scala> showRaw(tpe, printIds = true, printKinds = true) - res2: String = RefinedType( - List(TypeRef(ThisType(scala#2043#PK), TypeName("AnyRef")#691#TPE, List())), - Scope( - TermName("x")#2540#METH, - TermName("y")#2541#GET)) - -## 位置情報 - -**位置情報** ([`Position`](http://www.scala-lang.org/api/current/index.html#scala.reflect.api.Position)) -はシンボルや構文木のノードの出処を追跡するのに使われる。警告やエラーの表示でよく使われ、プログラムのどこが間違ったのかを正確に表示することができる。位置情報はソースファイルの列と行を表す。 -(ソースファイルの初めからのオフセットは「ポイント」と呼ばれるが、これは便利ではないことがある) -位置情報はそれが指す行の内容も保持する。全ての構文木やシンボルが位置情報を持つわけではなく、ない場合は -`NoPosition` オブジェクトで表される。 - -位置情報はソースファイルの 1文字を指すこともできれば、文字の範囲を指すこともできる。 -前者の場合は**オフセット位置情報** (offset position)、後者の場合は**範囲位置情報** -(range position) が使われる。範囲位置情報は `start` と `end` オフセットを保持する。 - `start` と `end` オフセットは `focusStart` と `focusEnd` -メソッドを用いて「フォーカス」することができ、これは位置情報を返す -(範囲位置情報では無い位置情報に対して呼ばれた場合は `this` を返す) 。 - -位置情報はいくつかのメソッドを使って比較することができる。 -`precedes` メソッドは、2つの位置情報が定義済みであり (つまり `NoPosition` ではない)、かつ -`this` の位置情報の終点が与えられた位置情報の始点を超えない場合に真を返す。 -他にも、範囲位置情報は (`includes` メソッドを用いて) 包含関係を調べたり、 -(`overlaps` メソッドを用いて) 交差関係を調べることができる。 - -範囲位置情報は**透明** (transparent) か**非透明** (opaque) だ。 -範囲位置情報を持つ構文木は以下の不変条件を満たす必要があるため、範囲位置情報が透明か非透明であるかは許可される用法に関わってくる: - -- オフセット位置情報を持つ構文木は範囲位置情報を持つ部分木を持ってはいけない。 -- 範囲位置情報を持つ構文木が範囲位置情報を持つ部分木を持つ場合、部分木の範囲は親の範囲に包含されなくてはいけない。 -- 同じノードの部分木の非透明な範囲位置情報同士は交差してはいけない。 (このため、交差は最大で単一の点となる) - -`makeTransparent` メソッドを使って非透明な範囲位置情報を透明な他は何も変わらないものに変換することができる。 diff --git a/ja/overviews/reflection/environment-universes-mirrors.md b/ja/overviews/reflection/environment-universes-mirrors.md deleted file mode 100644 index 68a6824e25..0000000000 --- a/ja/overviews/reflection/environment-universes-mirrors.md +++ /dev/null @@ -1,204 +0,0 @@ ---- -layout: overview-large - -discourse: false - -partof: reflection -num: 2 -outof: 6 -language: ja -title: 環境、ユニバース、ミラー ---- - -EXPERIMENTAL - -## 環境 - -リフレクションの環境は、リフレクションを用いたタスクが実行時に実行されたのかコンパイル時に実行されたのかによって変わる。 -この環境が実行時かコンパイル時かという違いは**ユニバース**と呼ばれるものによってカプセル化されている。 -リフレクション環境におけるもう 1つの重要なものにリフレクションを用いてアクセスが可能な実体の集合がある。 -この実体の集合は**ミラー**と呼ばれているものによって決定される。 - -例えば、実行時リフレクションによってアクセス可能な実体は `ClassloaderMirror` によって公開されている。 -このミラーは特定のクラスローダによって読み込まれた実体 (パッケージ、型、メンバ) のみへのアクセスを提供する。 - -ミラーはリフレクションを用いてアクセスすることができる実体の集合を決定するだけではなく、 -それらの実体に対するリフレクションを用いた演算を提供する。 -例えば、実行時リフレクションにおいて **invoker ミラー**を使うことで任意のクラスのメソッドやコンストラクタを呼び出すことができる。 - -## ユニバース - -実行時とコンパイル時という 2つの主要なリフレクション機能があるため、ユニバースにも 2つのタイプがある。 -その時のタスクに応じて適切なユニバースを選ぶ必要がある。 - -- **実行時リフレクション** のためには `scala.reflect.runtime.universe` -- **コンパイル時リフレクション**のためには `scala.reflect.macros.Universe` - -を選ぶ。 - -ユニバースは、型 (`Type`)、構文木 (`Tree`)、アノテーション (`Annotation`) -といったリフレクションで使われる主要な概念に対するインターフェイスを提供する。 - -## ミラー - -リフレクションによって提供される全ての情報は**ミラー** (mirror) を通して公開されている。 -型情報の種類やリフレクションを用いたタスクの種類によって異なるミラーを使う必要がある。 -**クラスローダミラー**を使うことで型情報やそのメンバを取得することができる。 -クラスローダミラーから、より特殊化された (最も広く使われている) **invoker ミラー** -を取得してリフレクションを使ったメソッドやコンストラクタ呼び出しやフィールドへのアクセスを行うことができる。 - -要約すると: - -- **クラスローダミラー** これらのミラーは (`staticClass`/`staticModule`/`staticPackage` メソッドを使って) 名前をシンボルへと翻訳する。 -- **invoker ミラー** これらのミラーは (`MethodMirror.apply` や `FieldMirror.get` といったメソッドを使って) リフレクションを用いた呼び出しを実装する。これらの invoker ミラーは最も広く使われているミラーだ。 - -### 実行時のミラー - -実行時におけるミラーの作り方は `ru.runtimeMirror()` だ (ただし、`ru` は `scala.reflect.runtime.universe`)。 - -`scala.reflect.api.JavaMirrors#runtimeMirror` の戻り値は -`scala.reflect.api.Mirrors#ReflectiveMirror` 型のクラスローダミラーで、名前 (`name`) からシンボル (`symbol`) を読み込むことができる。 - -クラスローダミラーから -(`scala.reflect.api.Mirrors#InstanceMirror`、 `scala.reflect.api.Mirrors#MethodMirror`、 `scala.reflect.api.Mirrors#FieldMirror`、`scala.reflect.api.Mirrors#ClassMirror`、そして `scala.reflect.api.Mirrors#ModuleMirror` を含む) -invoker ミラーを作成することができる。 - -以下に具体例を用いてこれら 2つのタイプのミラーがどう関わっているのかを説明する。 - -### ミラーの型とその用例 - -`ReflectiveMirror` は名前を用いてシンボルを読み込むのと、invoker ミラーを作るのに使われる。作り方: `val m = ru.runtimeMirror()`。 -具体例: - - scala> val ru = scala.reflect.runtime.universe - ru: scala.reflect.api.JavaUniverse = ... - - scala> val m = ru.runtimeMirror(getClass.getClassLoader) - m: scala.reflect.runtime.universe.Mirror = JavaMirror ... - -`InstanceMirror` はメソッド、フィールド、内部クラス、および内部オブジェクトの invoker ミラーを作成するのに使われる。作り方: `val im = m.reflect()`。 -具体例: - - scala> class C { def x = 2 } - defined class C - - scala> val im = m.reflect(new C) - im: scala.reflect.runtime.universe.InstanceMirror = instance mirror for C@3442299e - -`MethodMirror` はインスタンス・メソッド (Scala にはインスタンス・メソッドのみがある。オブジェクトのメソッドは `ModuleMirror.instance` から取得されるオブジェクト・インスタンスのインスタンス・メソッドだ。) の呼び出しに使われる。作り方: `val mm = im.reflectMethod()`。 -具体例: - - scala> val methodX = ru.typeOf[C].declaration(ru.TermName("x")).asMethod - methodX: scala.reflect.runtime.universe.MethodSymbol = method x - - scala> val mm = im.reflectMethod(methodX) - mm: scala.reflect.runtime.universe.MethodMirror = method mirror for C.x: scala.Int (bound to C@3442299e) - - scala> mm() - res0: Any = 2 - -`FieldMirror` はインスタンス・フィールドの get と set を行うのに使われる (メソッド同様に Scala はインスタンス・フィールドのみがある。)。作り方: `val fm = im.reflectField()`。 -具体例: - - scala> class C { val x = 2; var y = 3 } - defined class C - - scala> val m = ru.runtimeMirror(getClass.getClassLoader) - m: scala.reflect.runtime.universe.Mirror = JavaMirror ... - - scala> val im = m.reflect(new C) - im: scala.reflect.runtime.universe.InstanceMirror = instance mirror for C@5f0c8ac1 - - scala> val fieldX = ru.typeOf[C].declaration(ru.TermName("x")).asTerm.accessed.asTerm - fieldX: scala.reflect.runtime.universe.TermSymbol = value x - - scala> val fmX = im.reflectField(fieldX) - fmX: scala.reflect.runtime.universe.FieldMirror = field mirror for C.x (bound to C@5f0c8ac1) - - scala> fmX.get - res0: Any = 2 - - scala> fmX.set(3) - - scala> val fieldY = ru.typeOf[C].declaration(ru.TermName("y")).asTerm.accessed.asTerm - fieldY: scala.reflect.runtime.universe.TermSymbol = variable y - - scala> val fmY = im.reflectField(fieldY) - fmY: scala.reflect.runtime.universe.FieldMirror = field mirror for C.y (bound to C@5f0c8ac1) - - scala> fmY.get - res1: Any = 3 - - scala> fmY.set(4) - - scala> fmY.get - res2: Any = 4 - -`ClassMirror` はコンストラクタの invoker ミラーを作成するのに使われる。作り方: 静的クラスは `val cm1 = m.reflectClass()`、内部クラスは `val mm2 = im.reflectClass()`。 -具体例: - - scala> case class C(x: Int) - defined class C - - scala> val m = ru.runtimeMirror(getClass.getClassLoader) - m: scala.reflect.runtime.universe.Mirror = JavaMirror ... - - scala> val classC = ru.typeOf[C].typeSymbol.asClass - classC: scala.reflect.runtime.universe.Symbol = class C - - scala> val cm = m.reflectClass(classC) - cm: scala.reflect.runtime.universe.ClassMirror = class mirror for C (bound to null) - - scala> val ctorC = ru.typeOf[C].declaration(ru.nme.CONSTRUCTOR).asMethod - ctorC: scala.reflect.runtime.universe.MethodSymbol = constructor C - - scala> val ctorm = cm.reflectConstructor(ctorC) - ctorm: scala.reflect.runtime.universe.MethodMirror = constructor mirror for C.(x: scala.Int): C (bound to null) - - scala> ctorm(2) - res0: Any = C(2) - -`ModuleMirror` はシングルトン・オブジェクトのインスタンスにアクセスするのに使われる。作り方: 静的なオブジェクトは `val mm1 = m.reflectModule()`、内部オブジェクトは `val mm2 = im.reflectModule()`。 -具体例: - - scala> object C { def x = 2 } - defined module C - - scala> val m = ru.runtimeMirror(getClass.getClassLoader) - m: scala.reflect.runtime.universe.Mirror = JavaMirror ... - - scala> val objectC = ru.typeOf[C.type].termSymbol.asModule - objectC: scala.reflect.runtime.universe.ModuleSymbol = object C - - scala> val mm = m.reflectModule(objectC) - mm: scala.reflect.runtime.universe.ModuleMirror = module mirror for C (bound to null) - - scala> val obj = mm.instance - obj: Any = C$@1005ec04 - -### コンパイル時ミラー - -コンパイル時ミラーは名前からシンボルを読み込むクラスローダミラーだけが使われる。 - -クラスローダミラーは `scala.reflect.macros.Context#mirror` を用いて作る。 -クラスローダミラーを使う典型的なメソッドには `scala.reflect.api.Mirror#staticClass`、 -`scala.reflect.api.Mirror#staticModule`、 -そして `scala.reflect.api.Mirror#staticPackage` がある。具体例で説明すると: - - import scala.reflect.macros.Context - - case class Location(filename: String, line: Int, column: Int) - - object Macros { - def currentLocation: Location = macro impl - - def impl(c: Context): c.Expr[Location] = { - import c.universe._ - val pos = c.macroApplication.pos - val clsLocation = c.mirror.staticModule("Location") // get symbol of "Location" object - c.Expr(Apply(Ident(clsLocation), List(Literal(Constant(pos.source.path)), Literal(Constant(pos.line)), Literal(Constant(pos.column))))) - } - } - -**注意**: 手動でシンボルを照会する代わりに他の高レベルな方法もある。例えば、文字列を使わなくてもよいため型安全な -`typeOf[Location.type].termSymbol` (もしくは `ClassSymbol` が必要ならば `typeOf[Location].typeSymbol`) がある。 diff --git a/ja/overviews/reflection/overview.md b/ja/overviews/reflection/overview.md deleted file mode 100644 index a9a295cbd0..0000000000 --- a/ja/overviews/reflection/overview.md +++ /dev/null @@ -1,301 +0,0 @@ ---- -layout: overview-large - -partof: reflection -num: 1 -outof: 6 -language: ja -title: 概要 - -discourse: false ---- - -EXPERIMENTAL - -**Heather Miller、Eugene Burmako、Philipp Haller 著**
        -**Eugene Yokota 訳** - -**リフレクション** (reflection) とは、プログラムが実行時において自身をインスペクトしたり、変更したりできる能力のことだ。それはオブジェクト指向、関数型、論理プログラミングなど様々なプログラミングのパラダイムに渡って長い歴史を持つ。 -それぞれのパラダイムが、時として顕著に異なる方向性に向けて**現在の**リフレクションを進化させてきた。 -LISP/Scheme のような関数型の言語が動的なインタープリタを可能とすることに比重を置いてきたのに対し、Java のようなオブジェクト指向言語は実行時におけるクラスメンバのインスペクションや呼び出しを実現するための実行時リフレクションに主な比重を置いてきた。 - -複数の言語やパラダイムに渡る主要なリフレクションの用例を以下に 3つ挙げる: - -
          -
        1. 実行時リフレクション。実行時にランタイム型 (runtime type) やそのメンバをインスペクトしたり呼び出す能力。
        2. -
        3. コンパイル時リフレクション。コンパイル時に抽象構文木にアクセスしたり、それを操作する能力。
        4. -
        5. レイフィケーション (reification)。(1) の場合は実行時に、(2) の場合はコンパイル時に抽象構文木を生成すること。
        6. -
        - -Scala 2.10 までは Scala は独自のリフレクション機能を持っていなかった。 -代わりに、Java リフレクションを使って (1) の実行時リフレクションのうちの非常に限定的な一部の機能のみを使うことができた。 -しかし、存在型、高カインド型、パス依存型、抽象型など多くの Scala 独自の型の情報はそのままの Java リフレクションのもとでは実行時に復元不可能だった。 -これらの Scala 独自の型に加え、Java リフレクションはコンパイル時にジェネリックである Java 型の実行時型情報も復元できない。 -この制約は Scala のジェネリック型の実行時リフレクションも受け継いでいる。 - -Scala 2.10 は、Scala 独自型とジェネリック型に対する Java の実行時リフレクションの欠点に対処するためだけではなく、 -汎用リフレクション機能を持ったより強力なツールボックスを追加するために新しいリフレクションのライブラリを導入する。 -Scala 型とジェネリックスに対する完全な実行時リフレクション (1) の他に、 -Scala 2.10 は[マクロ]({{site.baseurl}}/ja/overviews/macros/overview.html) という形でコンパイル時リフレクション機能 (2) と、 -Scala の式を抽象構文木へと**レイファイ** (reify) する機能 (3) も提供する。 - -## 実行時リフレクション - -実行時リフレクション (runtime reflection) とは何だろう? -**実行時**に何らかの型もしくはオブジェクトが渡されたとき、リフレクションは以下のことができる: - -
          -
        • ジェネリック型を含め、そのオブジェクトがどの型かをインスペクトでき、
        • -
        • 新しいオブジェクトを作成することができ、
        • -
        • そのオブジェクトのメンバにアクセスしたり、呼び出したりできる。
        • -
        - -それぞれの能力をいくつかの具体例とともにみていこう。 - -### 具体例 - -  -#### ランタイム型のインスペクション (実行時におけるジェネリック型も含む) - -他の JVM言語同様に、Scala の型はコンパイル時に**消去** (erase) される。 -これは、何らかのインスタンスのランタイム型をインスペクトしてもコンパイル時に -Scala コンパイラが持つ型情報を全ては入手できない可能性があることを意味する。 - -**型タグ** (`TypeTag`) は、コンパイル時に入手可能な全ての型情報を実行時に持ち込むためのオブジェクトだと考えることができる。 -しかし、型タグは常にコンパイラによって生成されなくてはいけないことに注意してほしい。 -この生成は暗黙のパラメータか context bound によって型タグが必要とされた時にトリガーされる。 -そのため、通常は、型タグは暗黙のパラメータか context bound によってのみ取得できる。 - -例えば、context bound を使ってみよう: - - scala> import scala.reflect.runtime.{universe => ru} - import scala.reflect.runtime.{universe=>ru} - - scala> val l = List(1,2,3) - l: List[Int] = List(1, 2, 3) - - scala> def getTypeTag[T: ru.TypeTag](obj: T) = ru.typeTag[T] - getTypeTag: [T](obj: T)(implicit evidence$1: ru.TypeTag[T])ru.TypeTag[T] - - scala> val theType = getTypeTag(l).tpe - theType: ru.Type = List[Int] - -上の例では、まず `scala.reflect.runtime.universe` をインポートして -(型タグを使うためには必ずインポートされる必要がある)、`l` という名前の `List[Int]` を作る。 -次に、context bound を持った型パラメータ `T` を持つ `getTypeTag` というメソッドは定義する -(REPL が示すとおり、これは暗黙の evidence パラメータを定義することに等価であり、コンパイラは `T` に対する型タグを生成する)。 -最後に、このメソッドに `l` を渡して呼び出し、`TypeTag` に格納される型を返す `tpe` を呼び出す。 -見ての通り、正しい完全な型 (つまり、`List` の具象型引数を含むということ) である `List[Int]` が返ってきた。 - -目的の `Type` のインスタンスが得られれば、これをインスペクトすることもできる。以下に具体例で説明しよう: - - scala> val decls = theType.declarations.take(10) - decls: Iterable[ru.Symbol] = List(constructor List, method companion, method isEmpty, method head, method tail, method ::, method :::, method reverse_:::, method mapConserve, method ++) - -#### ランタイム型のインスタンス化 - -リフレクションによって得られた型は適当な invoker ミラーを使ってコンストラクタを呼び出すことでインスタンス化することができる -(ミラーに関しては[後ほど]({{ site.baseurl }}/ja/overviews/reflection/overview.html#mirrors)説明する)。 -以下に REPL を使った具体例を用いて説明しよう: - - scala> case class Person(name: String) - defined class Person - - scala> val m = ru.runtimeMirror(getClass.getClassLoader) - m: scala.reflect.runtime.universe.Mirror = JavaMirror with ... - -最初のステップとして現在のクラスローダで読み込まれた (`Person` クラスを含む) -全てのクラスや型をアクセス可能とするミラー `m` を取得する。 - - scala> val classPerson = ru.typeOf[Person].typeSymbol.asClass - classPerson: scala.reflect.runtime.universe.ClassSymbol = class Person - - scala> val cm = m.reflectClass(classPerson) - cm: scala.reflect.runtime.universe.ClassMirror = class mirror for Person (bound to null) - -次に、`reflectClass` メソッドを使って `Person` クラスの `ClassMirror` を取得する。 -`ClassMirror` は `Person` クラスのコンストラクタへのアクセスを提供する。 - - scala> val ctor = ru.typeOf[Person].declaration(ru.nme.CONSTRUCTOR).asMethod - ctor: scala.reflect.runtime.universe.MethodSymbol = constructor Person - -`Person` のコンストラクタのシンボルは実行時ユニバース `ru` を用いて `Person` 型の宣言から照会することによってのみ得られる。 - - scala> val ctorm = cm.reflectConstructor(ctor) - ctorm: scala.reflect.runtime.universe.MethodMirror = constructor mirror for Person.(name: String): Person (bound to null) - - scala> val p = ctorm("Mike") - p: Any = Person(Mike) - -#### ランタイム型のメンバへのアクセスと呼び出し - -一般的に、ランタイム型のメンバは適当な invoker ミラーを使ってコンストラクタを呼び出すことでインスタンス化することができる -(ミラーに関しては[後ほど]({{ site.baseurl }}/ja/overviews/reflection/overview.html#mirrors)説明する)。 -以下に REPL を使った具体例を用いて説明しよう: - - scala> case class Purchase(name: String, orderNumber: Int, var shipped: Boolean) - defined class Purchase - - scala> val p = Purchase("Jeff Lebowski", 23819, false) - p: Purchase = Purchase(Jeff Lebowski,23819,false) - -この例では `Purchase` `p` の `shipped` フィールドをリフレクションを使って get/set を行う: - - scala> import scala.reflect.runtime.{universe => ru} - import scala.reflect.runtime.{universe=>ru} - - scala> val m = ru.runtimeMirror(p.getClass.getClassLoader) - m: scala.reflect.runtime.universe.Mirror = JavaMirror with ... - -`shipped` メンバにアクセスするには、前の例と同じく、`p` のクラス (`Purchase`) を含むクラスローダが読み込んだ全てのクラスを入手可能とするミラー `m` -を取得することから始める。 - - scala> val shippingTermSymb = ru.typeOf[Purchase].declaration(ru.TermName("shipped")).asTerm - shippingTermSymb: scala.reflect.runtime.universe.TermSymbol = method shipped - -次に、`shipped` フィールドの宣言を照会して `TermSymbol` (`Symbol` 型の 1つ) を得る。 -この `Symbol` は後で (何からのオブジェクトの) このフィールドの値にアクセスするのに必要なミラーを得るのに使う。 - - scala> val im = m.reflect(p) - im: scala.reflect.runtime.universe.InstanceMirror = instance mirror for Purchase(Jeff Lebowski,23819,false) - - scala> val shippingFieldMirror = im.reflectField(shippingTermSymb) - shippingFieldMirror: scala.reflect.runtime.universe.FieldMirror = field mirror for Purchase.shipped (bound to Purchase(Jeff Lebowski,23819,false)) - -ある特定のインスタンスの `shipped` メンバにアクセスするためには、その特定のインスタンス `p` -のためのミラー `im` を必要とする。 -このインスタンスミラーから `p` の型のフィールドを表す `TermSymbol` に対して `FieldMirror` を得ることができる。 - -特定のフィールドに対して `FieldMirror` が得られたところで、`get` と `set` メソッドを使って特定のインスタンスの -`shipped` メンバを get/set できる。 -`shipped` の状態を `true` に変更してみよう。 - - scala> shippingFieldMirror.get - res7: Any = false - - scala> shippingFieldMirror.set(true) - - scala> shippingFieldMirror.get - res9: Any = true - -### Java のランタイムクラス と Scala のランタイム型の比較 - -Java のリフレクションを使って実行時に Java の **Class** -のインスタンスを取得したことのある読者は、Scala ではランタイム**型**を取得することに気付いただろう。 - -以下の REPL の実行結果は Scala のクラスに対して Java -リフレクションを使った場合に予想外もしくは間違った結果が返ってくることがあることを示す。 - -まず、抽象型メンバ `T` を持つ基底クラス `E` を定義して、それから 2つの派生クラス基底 -`C` と `D` を派生する。 - - scala> class E { - | type T - | val x: Option[T] = None - | } - defined class E - - scala> class C extends E - defined class C - - scala> class D extends C - defined class D - -次に具象型メンバ `T` (この場合 `String`) を使う `C` と `D` のインスタンスを作成する。 - - scala> val c = new C { type T = String } - c: C{type T = String} = $anon$1@7113bc51 - - scala> val d = new D { type T = String } - d: D{type T = String} = $anon$1@46364879 - -ここで Java リフレクションの `getClass` と `isAssignableFrom` メソッドを使って -`c` と `d` のランタイムクラスを表す `java.lang.Class` のインスタンスを取得して、 -`d` のランタイムクラスが `c` のランタイムクラスのサブクラスであるかを検証する。 - - scala> c.getClass.isAssignableFrom(d.getClass) - res6: Boolean = false - -`D` が `C` を継承することは上のコードにより明らかなので、この結果は意外なものかもしれない。 -この「`d` のクラスは `c` のクラスのサブクラスであるか?」 -というような簡単な実行時型検査において期待される答は `true` だと思う。 -しかし、上の例で気付いたかもしれないが、`c` と `d` がインスタンス化されるとき -Scala コンパイラは実はそれぞれに `C` と `D` の匿名のサブクラスを作成している。 -これは Scala コンパイラが Scala 特定の (つまり、非 Java の) 言語機能を -JVM 上で実行させるために等価な Java バイトコードに翻訳する必要があるからだ。 -そのため、Scala コンパイラは往々にしてユーザが定義したクラスの代わりに合成クラス (つまり、自動的に生成されたクラス) -を作成してそれを実行時に使用する。これは Scala -では日常茶飯事と言ってもいいぐらいで、クロージャ、型メンバ、型の細別、ローカルクラスなど多くの -Scala 機能に対して Java リフレクションを使う事で観測することができる。 - -このような状況においては、これらの Scala オブジェクトに対して -Scala リフレクションを使うことで正確なランタイム型を得ることができる。 -Scala のランタイム型は全てのコンパイル時の型情報を保持することでコンパイル時と実行時の型のミスマッチを回避している。 - -以下に Scala リフレクションを使って渡された 2つの引数のランタイム型を取得して両者のサブタイプ関係をチェックするメソッドを定義する。 -もしも、第1引数の型が第2引数の型のサブタイプである場合は `true` を返す。 - - scala> import scala.reflect.runtime.{universe => ru} - import scala.reflect.runtime.{universe=>ru} - - scala> def m[T: ru.TypeTag, S: ru.TypeTag](x: T, y: S): Boolean = { - | val leftTag = ru.typeTag[T] - | val rightTag = ru.typeTag[S] - | leftTag.tpe <:< rightTag.tpe - | } - m: [T, S](x: T, y: S)(implicit evidence$1: scala.reflect.runtime.universe.TypeTag[T], implicit evidence$2: scala.reflect.runtime.universe.TypeTag[S])Boolean - - scala> m(d, c) - res9: Boolean = true - -以上に示した通り、これは期待される結果を返す。`d` のランタイム型は確かに `c` のランタイム型のサブタイプだ。 - -## コンパイル時リフレクション - -Scala リフレクションは、プログラムがコンパイル時に**自身**を変更するという**メタプログラミング**の一種を可能とする。 -このコンパイル時リフレクションはマクロという形で実現されており、抽象構文木を操作するメソッドをコンパイル時に実行できる能力として提供される。 - -マクロの特に興味深い側面の1つは `scala.reflect.api` で提供される Scala の実行時リフレクションの基となっている -API に基づいていることだ。これにより、マクロと実行時リフレクションを利用した実装の間で汎用コードを共有することが可能となっている。 - -[マクロのガイド]({{ site.baseurl }}/ja/overviews/macros/overview.html)はマクロ固有のことに焦点を絞っているのに対し、 -本稿ではリフレクション API 全般を取り扱っていることに注意してほしい。 -しかし、[シンボル、構文木、型]({{site.baseurl }}/ja/overviews/reflection/symbols-trees-types.html)の節で詳しく説明される抽象構文木のように多くの概念は直接マクロにも応用することができる。 - -## 環境 - -全てのリフレクションを用いたタスクは適切な環境設定を必要とする。 -この環境はリフレクションを用いたタスクが実行時に行われるのかコンパイル時に行われるのかによって異なる。 -実行時とコンパイル時における環境の違いは**ユニバース**と呼ばれているものによってカプセル化されている。 -リフレクション環境におけるもう 1つの重要なものにリフレクションを用いてアクセスが可能な実体の集合がある。 -この実体の集合は**ミラー**と呼ばれているものによって決定される。 - -ミラーはリフレクションを用いてアクセスすることができる実体の集合を決定するだけではなく、 -それらの実体に対するリフレクションを用いた演算を提供する。 -例えば、実行時リフレクションにおいて **invoker ミラー**を使うことで任意のクラスのメソッドやコンストラクタを呼び出すことができる。 - -### ユニバース - -ユニバース (`Universe`) は Scala リフレクションへの入り口だ。 -ユニバースは、型 (`Type`)、構文木 (`Tree`)、アノテーション (`Annotation`) -といったリフレクションで使われる主要な概念に対するインターフェイスを提供する。 -詳細はこのガイドの[ユニバース]({{ site.baseurl}}/ja/overviews/reflection/environment-universes-mirrors.html)の節か、 -`scala.reflect.api` パッケージの[ユニバースの API doc](http://www.scala-lang.org/api/{{ site.scala-version}}/scala/reflect/api/Universe.html) -を参考にしてほしい。 - -このガイドにおける多くの例を含め、Scala リフレクションを利用するには何らかの -`Universe` もしくはその `Universe` のメンバをインポートする必要がある。 -典型的には実行時リフレクションを利用するには -`scala.reflect.runtime.universe` の全てのメンバをワイルドカードインポートを用いてインポートする: - - import scala.reflect.runtime.universe._ - -### ミラー - -ミラー (`Mirror`) は Scala リフレクションの中心部を構成する。 -リフレクションによって提供される全ての情報はこのミラーと呼ばれるものを通して公開されている。 -型情報の種類やリフレクションを用いたタスクの種類によって異なるミラーを使う必要がある。 - -詳細はこのガイドの[ミラー]({{ site.baseurl}}/ja/overviews/reflection/environment-universes-mirrors.html)の節か、 -`scala.reflect.api` パッケージの[ミラーの API doc](http://www.scala-lang.org/api/{{ site.scala-version}}/scala/reflect/api/Mirrors.html) -を参考にしてほしい。 \ No newline at end of file diff --git a/ja/overviews/reflection/symbols-trees-types.md b/ja/overviews/reflection/symbols-trees-types.md deleted file mode 100644 index aedd4bc361..0000000000 --- a/ja/overviews/reflection/symbols-trees-types.md +++ /dev/null @@ -1,696 +0,0 @@ ---- -layout: overview-large - -discourse: false - -partof: reflection -num: 3 -outof: 6 -language: ja -title: シンボル、構文木、型 ---- - -EXPERIMENTAL - -## シンボル - -**シンボル** (symbol) は名前 (name) とその名前が参照するクラスやメソッドのような実体 -(entity) の間のバインディングを作るのに用いられる。Scala において定義され名前を付けられるものは全て関連付けられたシンボルを持つ。 - -シンボルは実体 (`class`、`object`、`trait` など) もしくはメンバ (`val`、`var`、`def` など) -の宣言に関する全ての情報を格納するため、実行時リフレクションとコンパイル時リフレクション -(マクロ) の両方において中心的な役割を果たす抽象体だ。 - -全てのシンボルにある基本的な `name` メソッドをはじめ、より複雑で込み入った概念である -`ClassSymbol` に定義される `baseClasses` を取得するメソッドなど、シンボルは幅広い情報を提供する。 -もう一つの一般的なシンボルの利用方法としてはメンバのシグネチャのインスペクトや、 -クラスの型パラメータの取得、メソッドのパラメータ型の取得、フィールドの型の取得などが挙げられる。 - -### シンボルのオーナーの階層 - -シンボルは階層化されている。 -例えば、メソッドのパラメータを表すシンボルはそのメソッドのシンボルに**所有**されており、 -メソッドのシンボルはそれを内包するクラス、トレイト、もしくはオブジェクトに**所有**されており、 -クラスはそれを含むパッケージに**所有**されいてるという具合だ。 - -例えばトップレベルのパッケージのようなトップレベルの実体であるためにシンボルにオーナーが無い場合は、 -`NoSymbol` という特殊なシングルトン・オブジェクトのオーナーが用いられる。 -シンボルが無いことを表す `NoSymbol` は空を表わしたり、デフォルトの値として API の中で多用されている。 -`NoSymbol` の `owner` にアクセスすると例外が発生する。 -[`Symbol`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/reflect/api/Symbols$SymbolApi.html) -型によって提供される一般インターフェイスに関しては API doc を参照してほしい。 - -### 型シンボル (`TypeSymbol`) - -型シンボル (`TypeSymbol`) は型、クラス、トレイトの宣言そして、型パラメータを表す。 -より特定の `ClassSymbol` には当てはまらないメンバとして `isAbstractType`、 -`isContravariant`、`isCovariant` といったメソッドを持つ。 - -- `ClassSymbol`: クラスやトレイトの宣言に格納される全ての情報へのアクセスを提供する。具体的には、`name`、修飾子 (`isFinal`、 `isPrivate`、 `isProtected`、 `isAbstractClass` など)、 `baseClasses`、 `typeParams` など。 - -### 項シンボル (`TermSymbol`) - -項シンボル (`TermSymbol`) は `val`、`var`、`def`、そしてオブジェクトの宣言、パッケージや値のパラメータを表す。 - -- メソッド・シンボル (`MethodSymbol`) は `def` の宣言を表す (`TermSymbol` のサブクラスだ)。メソッドが(基本)コンストラクタであるか、可変長引数をサポートするかなどの問い合わせを行うことができる。 -- モジュール・シンボル (`ModuleSymbol`) はオブジェクトの宣言を表す。`moduleClass` メンバを用いてオブジェクトに暗黙的に関連付けられているクラスを照会することができる。逆の照会も可能だ。モジュール・クラスから `selfType.termSymbol` によって関連付けられるモジュール・シンボルを得られる。 - -### シンボル変換 - -状況によっては汎用の `Symbol` 型を返すメソッドを使う場面があるかもしれない。 -その場合、汎用の `Symbol` 型をより特定の特殊化されたシンボル型へと変換することができる。 -例えば `MethodSymbol` のインターフェイスを使いたいといった状況に合わせて、`asMethod` や `asClass` のようなシンボル変換を用いると特殊化した -`Symbol` のサブタイプに変換することができる。 - -具体例を用いて説明しよう。 - - scala> import scala.reflect.runtime.universe._ - import scala.reflect.runtime.universe._ - - scala> class C[T] { def test[U](x: T)(y: U): Int = ??? } - defined class C - - scala> val testMember = typeOf[C[Int]].member(TermName("test")) - testMember: scala.reflect.runtime.universe.Symbol = method test - -この場合、`member` は期待される `MethodSymbol` ではなく `Symbol` のインスタンスを返す。 -このため、`asMethod` を使って `MethodSymbol` が返されたことを保証する必要がある。 - - scala> testMember.asMethod - res0: scala.reflect.runtime.universe.MethodSymbol = method test - -### 自由シンボル - -2つのシンボル型 `FreeTermSymbol` と `FreeTypeSymbol` -は入手可能な情報が不完全であるという特殊なステータスを持つシンボルだ。 -これらのシンボルはレイフィケーションの過程において生成される -(詳しくは構文木のレイフィケーションの節を参照)。 -レイフィケーションがシンボルを特定できない場合 -(例えば、ローカルクラスを参照しているため、あるシンボルが対応するクラスファイルから見つけることができない場合) -元の名前とオーナー、そして元の型シグネチャに似た代理シグネチャを持った合成のダミーシンボルへとレイファイする。このシンボルは自由型 -(free type) と呼ばれる。 -あるシンボルが自由型かどうかは `sym.isFreeType` を呼ぶことで確かめることができる。 -また、`tree.freeTypes` を呼ぶことで特定の構文木とその部分木から参照されている全ての自由型のリストを取得することができる。 -最後に、`-Xlog-free-types` を用いることでレイフィケーションが自由型を生成したときに警告を得ることができる。 - -## 型 - -名前が示すとおり、`Type` のインスタンスは対応するシンボルの型情報を表す。 -これは、直接宣言もしくは継承されたメンバ (メソッド、フィールド、型エイリアス、抽象型、内部クラス、トレイトなど)、 -基底型、型消去などを含む。他にも、型は型の適合性 (conformance) や等価性 (equivalence) を検査することができる。 - -### 型のインスタンス化 - -一般的には以下の 3通りの方法で `Type` を得ることができる。 - -1. `Universe` にミックスインされている `scala.reflect.api.TypeTags` の `typeOf` メソッド経由。(最も簡単で、一般的な方法) -2. `Int`、`Boolean`、`Any`、や `Unit` のような標準型はユニバースからアクセス可能だ。 -3. `scala.reflect.api.Types` の `typeRef` や `polyType` といったメソッドを使った手動のインスタンス化。(非推奨) - -#### `typeOf` を用いた型のインスタンス化 - -多くの場合、型をインスタンス化するのには -`scala.reflect.api.TypeTags#typeOf` メソッドを使うことができる。 -これは型引数を受け取り、その引数を表す `Type` のインスタンスを返す。 -具体例で説明すると、 - - scala> import scala.reflect.runtime.universe._ - import scala.reflect.runtime.universe._ - - scala> typeOf[List[Int]] - res0: scala.reflect.runtime.universe.Type = scala.List[Int] - -この例では、型コンストラクタ `List` に型引数 `Int` が適用された -[`scala.reflect.api.Types$TypeRef`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/reflect/api/Types$TypeRef.html) -が返っている。 - -しかし、この方法はインスタンス化しようとしている型を手動で指定する必要があることに注意してほしい。 -もし任意のインスタンスに対応する `Type` のインスタンスを取得しようとしてる場合はどうすればいいだろう? -型パラメータに context bound を付けたメソッドを定義すればいいだけだ。これは特殊な `TypeTag` -を生成し、そこから任意のインスタンスに対する型を取得することができる: - - scala> def getType[T: TypeTag](obj: T) = typeOf[T] - getType: [T](obj: T)(implicit evidence$1: scala.reflect.runtime.universe.TypeTag[T])scala.reflect.runtime.universe.Type - - scala> getType(List(1,2,3)) - res1: scala.reflect.runtime.universe.Type = List[Int] - - scala> class Animal; class Cat extends Animal - defined class Animal - defined class Cat - - scala> val a = new Animal - a: Animal = Animal@21c17f5a - - scala> getType(a) - res2: scala.reflect.runtime.universe.Type = Animal - - scala> val c = new Cat - c: Cat = Cat@2302d72d - - scala> getType(c) - res3: scala.reflect.runtime.universe.Type = Cat - -**注意**: `typeOf` メソッドは、型パラメータを受け取る型 -(例えば、`A` が型パラメータであるとき `typeOf[List[A]]`) では動作しない。 -その場合は、代わりに `scala.reflect.api.TypeTags#weakTypeOf` を使うことができる。 -これに関する詳細はこのガイドの [TypeTags]({{ site.baseurl }}/ja/overviews/reflection/typetags-manifests.html) -に関する節を参照。 - -#### 標準型 - -`Int`、`Boolean`、`Any`、や `Unit` のような標準型はユニバースの `definitions` メンバからアクセス可能だ。 -具体的には、 - - scala> import scala.reflect.runtime.universe - import scala.reflect.runtime.universe - - scala> val intTpe = universe.definitions.IntTpe - intTpe: scala.reflect.runtime.universe.Type = Int - -標準型のリストは [`scala.reflect.api.StandardDefinitions`](http://www.scala-lang.org/api/current/index.html#scala.reflect.api.StandardDefinitions$StandardTypes) 内の `StandardTypes` -トレイトにて定義されている。 - -### 型の一般的な演算 - -型の典型的な用例としては型の適合性の検査や、メンバの問い合わせがある。 -型に対する演算を 3つに大別すると以下のようになる: - -
          -
        1. 2つの型の間のサブタイプ関係の検査。
        2. -
        3. 2つの型の間の等価性の検査。
        4. -
        5. 渡された型の特定のメンバや内部型の問い合わせ。
        6. -
        - -#### サブタイプ関係 - -2つの `Type` インスタンスがあるとき、`<:<` を用いて簡単に一方がもう片方のサブタイプであるかを調べることができる。 -(後で説明する例外的な場合においては、`weak_<:<` を使う) - - scala> import scala.reflect.runtime.universe._ - import scala-lang.reflect.runtime.universe._ - - scala> class A; class B extends A - defined class A - defined class B - - scala> typeOf[A] <:< typeOf[B] - res0: Boolean = false - - scala> typeOf[B] <:< typeOf[A] - res1: Boolean = true - -`weak_<:<` メソッドは、2つの型の間の**弱い適合性** (weak conformance) をチェックするのに使われることに注意。 -これは典型的には数値型を取り扱う際に重要となる。 - -Scala の数値型は以下の順序付けに従っている (Scala 言語仕様 3.5.3 節): - -> Scala はいくつかの状況では、より一般的な適合性関係を用います。 もし `S <: T` であるか、あるいは、`S` と `T` 両方がプリミティブな数値型で、次の順序中で `S` が `T` の前にあるなら、型 `S` は型 `T` に弱く適合するといい、`S <:w T` と書きます。 - -| 弱適合性関係 | - --------------------------- -| `Byte` `<:w` `Short` | -| `Short` `<:w` `Int` | -| `Char` `<:w` `Int` | -| `Int` `<:w` `Long` | -| `Long` `<:w` `Float` | -| `Float` `<:w` `Double` | - -例えば、以下の if-式の型は弱い適合性によって決定されている。 - - scala> if (true) 1 else 1d - res2: Double = 1.0 - -上記の if-式では結果の型は 2つの型の**弱い最小の上限境界** -(weak least upper bound、つまり弱い適合性上で最小の上限境界) だと定義されている。 - -`Int` と `Double` の間では (上記の仕様により) `Double` -が弱い適合性上での最小の上限境界だと定義されいるため、 -`Double` が例の if-式の型だと推論される。 - -`weak_<:<` メソッドは弱い適合性をチェックすることに注意してほしい。 -(それに対して、`<:<` は仕様 3.5.3 節の弱い適合性を考慮しない適合性を検査する) -そのため、数値型 `Int` と `Double` の適合性関係を正しくインスペクトできる: - - scala> typeOf[Int] weak_<:< typeOf[Double] - res3: Boolean = true - - scala> typeOf[Double] weak_<:< typeOf[Int] - res4: Boolean = false - -`<:<` を使った場合は `Int` と `Double` は互いに不適合であると間違った結果となる: - - scala> typeOf[Int] <:< typeOf[Double] - res5: Boolean = false - - scala> typeOf[Double] <:< typeOf[Int] - res6: Boolean = false - -#### 型の等価性 - -型の適合性同様に 2つの型の等価性を簡単に検査することができる。 -2つの任意の型が与えられたとき、`=:=` メソッドを使うことでそれらが全く同一のコンパイル時型を表記しているかを調べることができる。 - - scala> import scala.reflect.runtime.universe._ - import scala.reflect.runtime.universe._ - - scala> def getType[T: TypeTag](obj: T) = typeOf[T] - getType: [T](obj: T)(implicit evidence$1: scala.reflect.runtime.universe.TypeTag[T])scala.reflect.runtime.universe.Type - - scala> class A - defined class A - - scala> val a1 = new A; val a2 = new A - a1: A = A@cddb2e7 - a2: A = A@2f0c624a - - scala> getType(a1) =:= getType(a2) - res0: Boolean = true - -両方のインスタンスの型情報が寸分違わず一致している必要があることに注意してほしい。 -例えば、以下のコードにおいて異なる型引数を取る 2つの `List` のインスタンスがある。 - - scala> getType(List(1,2,3)) =:= getType(List(1.0, 2.0, 3.0)) - res1: Boolean = false - - scala> getType(List(1,2,3)) =:= getType(List(9,8,7)) - res2: Boolean = true - -また、型の等価性を検査するためには**常に** `=:=` を使う必要があることに注意してほしい。 -つまり、型エイリアスをチェックすることができない `==` は絶対に使ってはいけないということだ: - - scala> type Histogram = List[Int] - defined type alias Histogram - - scala> typeOf[Histogram] =:= getType(List(4,5,6)) - res3: Boolean = true - - scala> typeOf[Histogram] == getType(List(4,5,6)) - res4: Boolean = false - -見てのとおり、`==` は `Histogram` と `List[Int]` が異なる型であると間違った結果を出している。 - -#### 型に対するメンバと宣言の照会 - -ある `Type` があるとき、特定のメンバや宣言を**照会** (query) することができる。 -`Type` の**メンバ** (member) には全てのフィールド、メソッド、型エイリアス、抽象型、内部クラス/オブジェクト/トレイトなどが含まれる。 -`Type` の**宣言** (declaration) にはその `Type` が表すクラス/オブジェクト/トレイト内で宣言された (継承されなかった) メンバのみが含まれる。 - -ある特定のメンバや宣言の `Symbol` を取得するにはその型に関連する定義のリストを提供する -`members` か `declarations` メソッドを使うだけでいい。単一のシンボルのみを返す -`meber` と `declaration` というメソッドもある。以下に 4つのメソッド全てのシグネチャを示す: - - /** The member with given name, either directly declared or inherited, an - * OverloadedSymbol if several exist, NoSymbol if none exist. */ - def member(name: Universe.Name): Universe.Symbol - - /** The defined or declared members with name name in this type; an - * OverloadedSymbol if several exist, NoSymbol if none exist. */ - def declaration(name: Universe.Name): Universe.Symbol - - /** A Scope containing all members of this type - * (directly declared or inherited). */ - def members: Universe.MemberScope // MemberScope is a type of - // Traversable, use higher-order - // functions such as map, - // filter, foreach to query! - - /** A Scope containing the members declared directly on this type. */ - def declarations: Universe.MemberScope // MemberScope is a type of - // Traversable, use higher-order - // functions such as map, - // filter, foreach to query! - -例えば、`List` の `map` メソッドを照会するには以下のようにする。 - - scala> import scala.reflect.runtime.universe._ - import scala.reflect.runtime.universe._ - - scala> typeOf[List[_]].member("map": TermName) - res0: scala.reflect.runtime.universe.Symbol = method map - -メソッドを照会するために `member` メソッドに `TermName` を渡していることに注意してほしい。 -ここで、`List` の自分型である `Self` のような型メンバを照会する場合は `TypeName` を渡す: - - scala> typeOf[List[_]].member("Self": TypeName) - res1: scala.reflect.runtime.universe.Symbol = type Self - -型の全てのメンバや宣言を面白い方法で照会することもできる。 -`members` メソッドを使って、渡された型の全ての継承もしくは宣言されたメンバを表す -`Symbol` の `Traversable` を取得することができる (`MemberScopeApi` は `Traversable` を継承する)。 -これにより、`foreach`、`filter`、`map` などの馴染み深いコレクションに対する高階関数を使って型のメンバを探検することができる。 -例えば、`List` のメンバのうち private なものだけを表示したいとする: - - scala> typeOf[List[Int]].members.filter(_.isPrivate).foreach(println _) - method super$sameElements - method occCounts - class CombinationsItr - class PermutationsItr - method sequential - method iterateUntilEmpty - -## 構文木 - -**構文木** (`Tree`) は、プログラムを表す Scala の抽象構文の基盤となっている。 -これらは抽象構文木 (abstract syntax tree) とも呼ばれ、一般に AST と略される。 - -Scala リフレクションで、構文木を生成または利用する API には以下のようなものがある: - -1. Scala アノテーションは引数に構文木を用い、`Annotation.scalaArgs` として公開されている。(詳細はこのガイドの[アノテーション]({{ site.baseurl }}/ja/overviews/reflection/names-exprs-scopes-more.html)の節を参照) -2. 任意の式を受け取りその AST を返す `reify` という特殊なメソッド。 -3. マクロを用いたコンパイル時リフレクション (詳細は[マクロ]({{ site.baseurl }}/ja/overview/macros/overview.html)参照) とツールボックスを用いた実行時リフレクションは両方とも構文木を用いてプログラムを表現する。 - -ここで注意してほしいのは構文木は `pos` (`Position`)、 `symbol` (`Symbol`)、 -と型検査の際に代入される `tpe` (`Type`) という 3つのフィールドの他は不変 (immutable) であることだ。 - -### 構文木の種類 - -構文木は以下の 3つのカテゴリーに大別することができる: - -1. **`TermTree` のサブクラス**は項を表す。例えば、メソッドの呼び出しは `Apply` ノードで表され、オブジェクトのインスタンス化は `New` ノードで行われる。 -2. **`TypTree` のサブクラス**はプログラムのソースコード中に現れる型を表す。例えば、`List[Int]` は `AppliedTypeTree` へとパースされる。**注意**: `TypTree` は綴り間違いではないし、概念的に `TypeTree` とは異なるものだ。(例えば型推論などによって) コンパイラが `Type` を構築する場合にプログラムの AST に統合できるように `TypeTree` にラッピングされる。 -3. **`SymTree` のサブクラス**は定義を導入または参照する。新しい定義の導入の具体例としてはクラスやトレイトの定義を表す `ClassDef` や、フィールドやパラメータ定義を表す `ValDef` が挙げられる。既存の定義に対する参照の例としてはローカル変数やメソッドなど現行のスコープ内にある既存の定義を参照する `Ident` を挙げることができる。 - -上記のカテゴリー以外の構文木を目にすることがあるとすれば、それは典型的には合成的なものか短命な構築物だ。 -例えば、各マッチケースをラッピングする `CaseDef` は項でも型でもなく、シンボルも持たない。 - -### 構文木をインスペクトする - -Scala リフレクションは、ユニバース経由で構文木を視覚化する方法をいくつか提供する。渡された構文木があるとき、 - -- `show` もしくは `toString` メソッドを使って構文木が表す擬似 Scala コードを表示することができる。 -- `showRaw` メソッドを使ってタイプチェッカが用いる生の構文木の内部構造を見ることができる。 - -具体例を使って説明しよう: - - scala> import scala.reflect.runtime.universe._ - import scala.reflect.runtime.universe._ - - scala> val tree = Apply(Select(Ident(TermName("x")), TermName("$plus")), List(Literal(Constant(2)))) - tree: scala.reflect.runtime.universe.Apply = x.$plus(2) - -`show` メソッド (もしくは同等の `toString`) を使ってこの構文木が何を表しているかを見てみよう。 - - scala> show(tree) - res0: String = x.$plus(2) - -見てのとおり、`tree` は `2` を項 `x` に加算する。 - -逆の方向に行くこともできる。ある Scala の式が与えられたとき、そこから構文木を取得した後で -`showRaw` メソッドを用いてコンパイラやタイプチェッカが使っている生の構文木の内部構造を見ることができる。 -例えば、以下の式があるとする: - - scala> import scala.reflect.runtime.universe._ - import scala.reflect.runtime.universe._ - - scala> val expr = reify { class Flower { def name = "Rose" } } - expr: scala.reflect.runtime.universe.Expr[Unit] = ... - -ここで、`reify` は Scala 式を受け取り `Tree` と `TypeTag` をラッピングする `Expr` を返す。 -(`Expr` の詳細に関してはこのガイドの[式]({{ site.baseurl }}/ja/overviews/reflection/names-exprs-scopes-more.html)の節を参照) -`expr` が保持する構文木は以下のように取得できる: - - scala> val tree = expr.tree - tree: scala.reflect.runtime.universe.Tree = - { - class Flower extends AnyRef { - def () = { - super.(); - () - }; - def name = "Rose" - }; - () - } - -生の構文木の内部構造をインスペクトするには以下のように行う: - - scala> showRaw(tree) - res1: String = Block(List(ClassDef(Modifiers(), TypeName("Flower"), List(), Template(List(Ident(TypeName("AnyRef"))), emptyValDef, List(DefDef(Modifiers(), nme.CONSTRUCTOR, List(), List(List()), TypeTree(), Block(List(Apply(Select(Super(This(tpnme.EMPTY), tpnme.EMPTY), nme.CONSTRUCTOR), List())), Literal(Constant(())))), DefDef(Modifiers(), TermName("name"), List(), List(), TypeTree(), Literal(Constant("Rose"))))))), Literal(Constant(()))) - -### 構文木の走査 - -構文木の内部構造が分かった所で、よくある次のステップは情報を抽出することだ。 -これは構文木を**走査**することで行われ、以下の 2通りの方法がある: - -
          -
        • パターンマッチングを用いた走査
        • -
        • Traverser のサブクラスを用いた走査
        • -
        - -#### パターンマッチングを用いた走査 - -パターンマッチングを用いた走査は最も簡単で一般的な構文木の走査方法だ。 -典型的にはある構文木の単一のノードの状態を知りたい場合にパターンマッチングを用いた走査を行う。 -例えば、以下の構文木に1つだけある `Apply` ノードから関数と引数を取得したいとする。 - - scala> import scala.reflect.runtime.universe._ - import scala.reflect.runtime.universe._ - - scala> val tree = Apply(Select(Ident(TermName("x")), TermName("$plus")), List(Literal(Constant(2)))) - tree: scala.reflect.runtime.universe.Apply = x.$plus(2) - -`tree` に対してマッチをかけてやるだけでよく、`Apply` ケースの場合には `Apply` の関数と引数を返す: - - scala> val (fun, arg) = tree match { - | case Apply(fn, a :: Nil) => (fn, a) - | } - fun: scala.reflect.runtime.universe.Tree = x.$plus - arg: scala.reflect.runtime.universe.Tree = 2 - -パターンマッチを左辺項に移すことで上記と同じことをより簡潔に実現できる: - - scala> val Apply(fun, arg :: Nil) = tree - fun: scala.reflect.runtime.universe.Tree = x.$plus - arg: scala.reflect.runtime.universe.Tree = 2 - -ノードは他のノード内に任意の深さで入れ子になることができるため、`Tree` -は普通かなり複雑となることに注意してほしい。これを示す簡単な例として、上記の構文木に -2つ目の `Apply` を加えて既にある和に `3` を加算する: - - scala> val tree = Apply(Select(Apply(Select(Ident(TermName("x")), TermName("$plus")), List(Literal(Constant(2)))), TermName("$plus")), List(Literal(Constant(3)))) - tree: scala.reflect.runtime.universe.Apply = x.$plus(2).$plus(3) - -これに上記と同じパターンマッチを適用すると外側の `Apply` -ノードが得られ、それは上で見た `x.$plus(2)` を表す構文木を関数部分として格納する: - - scala> val Apply(fun, arg :: Nil) = tree - fun: scala.reflect.runtime.universe.Tree = x.$plus(2).$plus - arg: scala.reflect.runtime.universe.Tree = 3 - - scala> showRaw(fun) - res3: String = Select(Apply(Select(Ident(TermName("x")), TermName("$plus")), List(Literal(Constant(2)))), TermName("$plus")) - -特定のノードで止まることなく構文木全体を走査したり、特定の型のノードを収集してインスペクトするなどより複雑なタスクを行うためには -`Traverser` を用いた走査の方が適しているかもしれない。 - -#### `Traverser` のサブクラスを用いた走査 - -初めから終わりまで構文木全体を走査する必要がある場合は、パターンマッチ中に現れうる全ての型に対する処理をする必要があるためパターンマッチングを用いた走査は適さない。 -そのため、そのような場合は `Traverser` クラスを用いる。 - -`Traevrser` は幅優先探索を用いて渡された構文木の全てのノードを訪れることを保証する。 - -`Traverser` を使うには、`Traverser` を継承して `traverse` メソッドをオーバーライドする。 -こうすることで必要なケースだけを処理するカスタムロジックを提供する。 -例えば `x.$plus(2).$plus(3)` の構文木があるとき、全ての `Apply` ノードを収集したいとする: - - scala> import scala.reflect.runtime.universe._ - import scala.reflect.runtime.universe._ - - scala> val tree = Apply(Select(Apply(Select(Ident(TermName("x")), TermName("$plus")), List(Literal(Constant(2)))), TermName("$plus")), List(Literal(Constant(3)))) - tree: scala.reflect.runtime.universe.Apply = x.$plus(2).$plus(3) - - scala> object traverser extends Traverser { - | var applies = List[Apply]() - | override def traverse(tree: Tree): Unit = tree match { - | case app @ Apply(fun, args) => - | applies = app :: applies - | super.traverse(fun) - | super.traverseTrees(args) - | case _ => super.traverse(tree) - | } - | } - defined module traverser - -上のコードは渡された構文木のうち `Apply` ノードだけを探してリストを構築している。 - -これはスーパークラス `Traverser` で既に幅優先探索として実装されている -`traverse` メソッドをサブクラス `traverser` のオーバーライドされた -`traverse` メソッドが特別なケースを**追加**するという形で実現されている。 -この特別なケースは `Apply(fun, args)` というパターンにマッチするノードのみに効用がある。 -(`fun` は `Tree` で表される関数、`args` は `Tree` のリストで表される引数のリストとなる) - -ある構文木がこのパターンにマッチすると (つまり、`Apply` ノードがあるとき)、 -`List[Apply]` である `applies` に追加して、走査を続行する。 - -マッチした場合の処理で `Apply` にラッピングされた関数 `fun` に対して `super.traverse` -そして引数のリスト `args` に対しては `super.traverseTrees` -(`super.traverse` とほぼ同じものだが、単一の `Tree` の代わりに `List[Tree]` を受け取る) -を呼び出していることに注意してほしい。 -両方の呼び出しとも目的は簡単で、`fun` の中にも `Apply` パターンがあるか分からないため部分木に対してもデフォルトの -`Traverser` の `traverse` メソッドが確かに呼ばれるようにしている。 -スーパークラスである `Traverser` は全ての入れ子になっている部分木に対して `this.traverse` -を呼び出すため、`Apply` パターンを含む部分木もカスタムの `traverse` メソッドを呼び出すことが保証されている。 - -`traverse` を開始して、その結果の `Apply` の `List` を表示するには以下のように行う: - - scala> traverser.traverse(tree) - - scala> traverser.applies - res0: List[scala.reflect.runtime.universe.Apply] = List(x.$plus(2), x.$plus(2).$plus(3)) - -### 構文木の構築 - -実行時リフレクションを行う際に、構文木を手動で構築する必要は無い。 -しかし、ツールボックスを用いて実行時コンパイルする場合やマクロを用いてコンパイル時リフレクションを行う場合はプログラムを表現する媒体として構文木が使われる。 -そのような場合、構文木を構築する 3通りの方法がある: - -1. `reify` メソッドを用いる (可能な限りこれを使うことを推奨する) -2. ツールボックスの `parse` メソッドを用いる -3. 手動で構築する (非推奨) - -#### `reify` を用いた構文木の構築 - -`reify` メソッドは Scala 式を引数として受け取り、その引数を `Tree` として表現したものを結果として返す。 - -Scala リフレクションでは、`reify` メソッドを用いた構文木の構築が推奨される方法だ。その理由を具体例を用いて説明しよう: - - scala> import scala.reflect.runtime.universe._ - import scala.reflect.runtime.universe._ - - scala> { val tree = reify(println(2)).tree; showRaw(tree) } - res0: String = Apply(Select(Select(This(TypeName("scala")), TermName("Predef")), TermName("println")), List(Literal(Constant(2)))) - -ここで、単に `println(2)` という呼び出しを `reify` している。 -つまり、`println(2)` という式をそれに対応する構文木の表現に変換している。そして、生の構文木の内部構造を出力している。 -`println` メソッドが `scala.Predef.println` に変換されたことに注目してほしい。 -このような変換は `reify` の結果がどこで用いられても意味が変わらないことを保証する。 -例えば、この `println(2)` というコードが独自の `println` を定義するブロックに挿入されたとしてもこのコードの振る舞いには影響しない。 - -このような構文木の構築は、識別子のバインディングを保持するため**健全** (hygenic) であるといわれる。 - -##### 構文木のスプライシング - -`reify` を使うことで複数の小さい構文木から 1つの構文木へと合成することもできる。これは -`Expr.splice` (スプライス、「継ぎ足す」という意味) を用いて行われる。 - -**注意**: `Expr` は `reify` の戻り値の型だ。**型付けされた** (typed) 構文木、`TypeTag` -そして `splice` などのレイフィケーションに関連するいくつかのメソッドを含む簡単なラッパーだと思ってもらえばいい。 -`Expr` に関する詳細は[このガイドの関連項目]({{ site.baseurl}}/ja/overviews/reflection/annotations-names-scopes.html)を参照。 - -例えば、`splice` を用いて `println(2)` を表す構文木を構築してみよう: - - scala> val x = reify(2) - x: scala.reflect.runtime.universe.Expr[Int(2)] = Expr[Int(2)](2) - - scala> reify(println(x.splice)) - res1: scala.reflect.runtime.universe.Expr[Unit] = Expr[Unit](scala.this.Predef.println(2)) - -ここで `2` と `println` をそれぞれ別に `reify` して、一方を他方の中に `splice` している。 - -しかし、`reify` の引数は妥当で型付け可能な Scala のコードであることが要求されることに注意してほしい。 -`println` の引数の代わりに、`println` そのものを抽象化しようとした場合は失敗することを以下に示す: - - scala> val fn = reify(println) - fn: scala.reflect.runtime.universe.Expr[Unit] = Expr[Unit](scala.this.Predef.println()) - - scala> reify(fn.splice(2)) - :12: error: Unit does not take parameters - reify(fn.splice(2)) - ^ - -見てのとおり、呼び出された関数の名前だけを捕捉したかったわけだが、コンパイラは引数無しの `println` という呼び出しをレイファイしたかったのだと決めてかかっている。 - -このようなユースケースは現在 `reify` を用いては表現することはできない。 - -#### ツールボックスの `parse` を用いた構文木の構築 - -**ツールボックス** (`Toolbox`) を使って構文木の型検査、コンパイル、および実行を行うことができる。 -ツールボックスはまた、文字列を構文木へとパースすることができる。 - -**注意**: ツールボックスの仕様は `scala-compiler.jar` にクラスパスが通っていることを必要とする。 - -`parse` メソッドを使った場合に、前述の `println` の例がどうなるかみてみよう: - - scala> import scala.reflect.runtime.universe._ - import scala.reflect.runtime.universe._ - - scala> import scala.tools.reflect.ToolBox - import scala.tools.reflect.ToolBox - - scala> val tb = runtimeMirror(getClass.getClassLoader).mkToolBox() - tb: scala.tools.reflect.ToolBox[scala.reflect.runtime.universe.type] = scala.tools.reflect.ToolBoxFactory$ToolBoxImpl@7bc979dd - - scala> showRaw(tb.parse("println(2)")) - res2: String = Apply(Ident(TermName("println")), List(Literal(Constant(2)))) - -`reify` と違って、ツールボックスは型付けの要求を必要としないことに注目してほしい。 -この柔軟性の引き換えに堅牢性が犠牲になっている。どういう事かと言うと、`reify` -と違ってこの `parse` は `println` が標準の `println` メソッドにバインドされていることが反映されていない。 - -**注意**: マクロを使っている場合は、`ToolBox.parse` を使うべきではない。マクロコンテキストに既に -`parse` メソッドが組み込まれているからだ。具体例を使って説明しよう: - - scala> import scala.language.experimental.macros - import scala.language.experimental.macros - - scala> def impl(c: scala.reflect.macros.Context) = c.Expr[Unit](c.parse("println(2)")) - impl: (c: scala.reflect.macros.Context)c.Expr[Unit] - - scala> def test = macro impl - test: Unit - - scala> test - 2 - -##### ツールボックスを用いた型検査 - -前に少し触れたが、ツールボックス (`ToolBox`) は文字列から構文木を構築する以外にも使い道があって、構文木の型検査、コンパイル、および実行を行うことができる。 - -プログラムの大まかな構造を保持する他に、構文木はプログラムの意味論に関する重要な情報を -`symbol` (定義を導入または参照する構文木に割り当てられたシンボル) や -`tpe` (構文木の型) という形で保持する。デフォルトでは、これらのフィールドは空だが、型検査をすることで充足される。 - -実行時リフレクションのフレームワークを利用する場合、型検査は `ToolBox.typeCheck` によって実装される。 -コンパイル時にマクロを利用する場合は `Context.typeCheck` メソッドを使う。 - - scala> import scala.reflect.runtime.universe._ - import scala.reflect.runtime.universe._ - - scala> val tree = reify { "test".length }.tree - tree: scala.reflect.runtime.universe.Tree = "test".length() - - scala> import scala.tools.reflect.ToolBox - import scala.tools.reflect.ToolBox - - scala> val tb = runtimeMirror(getClass.getClassLoader).mkToolBox() - tb: scala.tools.reflect.ToolBox[scala.reflect.runtime.universe.type] = ... - - scala> val ttree = tb.typeCheck(tree) - ttree: tb.u.Tree = "test".length() - - scala> ttree.tpe - res5: tb.u.Type = Int - - scala> ttree.symbol - res6: tb.u.Symbol = method length - -上の例では、`"test".length` という呼び出しを表現する構文木を構築して、`ToolBox` `tb` -の `typeCheck` メソッドを用いて構文木を型検査している。 -見てのとおり、`ttree` は正しい型 `Int` を取得して、`Symbol` も正しく設定されている。 - -#### 手動の構文木の構築 - -もし全てが失敗した場合は、手動で構文木を構築することもできる。これは最も低レベルな構文木を構築する方法で、他の方法がうまくいかなかった場合のみ挑むべき方法だ。 -`parse` に比べてより柔軟な方法を提供するが、その柔軟性は過度の冗長さと脆弱さによって実現されている。 - -`println(2)` を使った例題を手動で構築すると、こうなる: - - scala> Apply(Ident(TermName("println")), List(Literal(Constant(2)))) - res0: scala.reflect.runtime.universe.Apply = println(2) - -このテクニックの典型的なユースケースは単独では意味を成さない動的に構築された部分木を組み合わせて構文木を作る必要がある場合だ。 -そのような場合、引数が型付けられていることを必要とする `reify` はおそらく不適切だろう。 -構文木は個々の部分木では Scala ソースとして表現することができない式以下のレベルから組み立てられることがよくあるため、`parse` -でもうまくいかないだろう。 diff --git a/ja/overviews/reflection/thread-safety.md b/ja/overviews/reflection/thread-safety.md deleted file mode 100644 index 7fd7c14fcb..0000000000 --- a/ja/overviews/reflection/thread-safety.md +++ /dev/null @@ -1,60 +0,0 @@ ---- -layout: overview-large - -discourse: false - -partof: reflection -num: 6 -outof: 6 -language: ja -title: スレッドセーフティ ---- - -EXPERIMENTAL - -残念ながら Scala 2.10.0 でリリースされた現行の状態ではリフレクションはスレッドセーフではない。 -[SI-6240](https://issues.scala-lang.org/browse/SI-6240) が報告されているので、それを使って進捗を追跡したり、技術的な詳細を照会することができるが、ここに現状で分かっていることをまとめてみたい。 - -

        NEWThread safety issues have been fixed in Scala 2.11.0-RC1, but we are going to keep this document available for now, since the problem still remains in the Scala 2.10.x series, and we currently don't have concrete plans on when the fix is going to be backported.

        - -現在の所、リフレクション関連では 2通りの競合状態があることが分かっている。第一はリフレクションの初期化 -(`scala.reflect.runtime.universe` が最初にアクセルされるときに呼ばれるコード) -は複数のスレッドから安全に呼び出すことができない。 -第二に、シンボルの初期化 -(シンボルのフラグまたは型シグネチャが最初にアクセスされたときに呼ばれるコード) -も安全ではない。以下が典型的な症例だ: - - java.lang.NullPointerException: - at s.r.i.Types$TypeRef.computeHashCode(Types.scala:2332) - at s.r.i.Types$UniqueType.(Types.scala:1274) - at s.r.i.Types$TypeRef.(Types.scala:2315) - at s.r.i.Types$NoArgsTypeRef.(Types.scala:2107) - at s.r.i.Types$ModuleTypeRef.(Types.scala:2078) - at s.r.i.Types$PackageTypeRef.(Types.scala:2095) - at s.r.i.Types$TypeRef$.apply(Types.scala:2516) - at s.r.i.Types$class.typeRef(Types.scala:3577) - at s.r.i.SymbolTable.typeRef(SymbolTable.scala:13) - at s.r.i.Symbols$TypeSymbol.newTypeRef(Symbols.scala:2754) - -実行時リフレクション (`scala.reflect.runtime.universe` から公開されるもの) -に比べてコンパイル時リフレクション (`scala.reflect.macros.Context` によってマクロに公開されるもの) -の方がこの問題の影響を受けづらいことはせめてもの救いだ。 -第一の理由は、マクロが実行される段階においてはコンパイル時リフレクションのユニバースは既に初期化済みであるため、競合状態の最初の状態は無くなることだ。 -第二の理由はこれまでにコンパイラそのものがスレッドセーフであったことが無いため、並列実行を行なっているツールが無いことだ。 -しかし、複数のスレッドを作成するマクロを作っている場合は気をつけるべきだろう。 - -一転して、実行時リフレクションの話は暗くなる。リフレクションの初期化は -`scala.reflect.runtime.universe` が初期化されるときに呼び出され、これは間接的に起こりうる。 -中でも顕著な例は context bound の `TypeTag` がついたメソッドを呼び出すと問題が起こりえることだ。 -これは、そのようなメソッドを呼び出すと Scala は普通は型タグを自動生成する必要があり、そのために型を生成する必要があり、そのためにはリフレクションのユニバースの初期化が必要だからだ。この結果、特殊な対策を取らない限りテストなどから -`TypeTag` を使ったメソッドを安全に呼び出すことができないということが導き出される。 -これは sbt など多くのツールがテストを並列実行するからだ。 - -まとめ: - -
          -
        • マクロを書いているならば、明示的にスレッドを使わない限り大丈夫だ。
        • -
        • 実行時リフレクションとスレッドやアクターを混ぜると危険。
        • -
        • TypeTag の context bound を使ったメソッドを複数のスレッドから呼び出すと非決定的な結果になる可能性がある。
        • -
        • この問題の進捗を知りたければ SI-6240 を参照する。
        • -
        diff --git a/ja/overviews/reflection/typetags-manifests.md b/ja/overviews/reflection/typetags-manifests.md deleted file mode 100644 index fefd43dcb4..0000000000 --- a/ja/overviews/reflection/typetags-manifests.md +++ /dev/null @@ -1,154 +0,0 @@ ---- -layout: overview-large - -discourse: false - -partof: reflection -num: 5 -outof: 6 - -language: ja -title: 型タグとマニフェスト ---- - -他の JVM言語同様に、Scala の型はコンパイル時に**消去** (erase) される。 -これは、何らかのインスタンスのランタイム型をインスペクトしてもコンパイル時に -Scala コンパイラが持つ型情報を全ては入手できない可能性があることを意味する。 - -マニフェスト (`scala.reflect.Manifest`) 同様に、**型タグ** (`TypeTag`) はコンパイル時に入手可能な全ての型情報を実行時に持ち込むオブジェクトだと考えることができる。 -例えば、`TypeTag[T]` はコンパイル時の型 `T` のランタイム型形式をカプセル化する。 -しかし `TypeTag` は、2.10 以前の `Manifest` という概念に比べより豊かで、かつ -Scala リフレクションに統合された代替であることに注意してほしい。 - -3通りの型タグがある: - -1. `scala.reflect.api.TypeTags#TypeTag`。Scala 型の完全な型記述子。例えば、`TypeTag[List[String]]` は型 `scala.List[String]` に関する全ての型情報を持つ。 - -2. `scala.reflect.ClassTag`。Scala 型の部分的な型記述子。例えば、`ClassTag[List[String]]` は消去されたクラス型の情報のみ (この場合、`scala.collection.immutable.List`) を保持する。`ClassTag` は型のランタイムクラスへのアクセスのみを提供し、`scala.reflect.ClassManifest` に相当する。 - -3. `scala.reflect.api.TypeTags#WeakTypeTag`。抽象型の型記述子 (以下の節での説明を参照)。 - -## 型タグの取得 - -マニフェスト同様、型タグは常にコンパイラによって生成され、以下の 3通りの方法で取得できる。 - -### `typeTag`、`classTag`、`weakTypeTag` メソッドを使う - -`Universe` が公開している `typeTag` を使うことで特定の型の `TypeTag` を直接取得することができる。 - -例えば、`Int` を表す `TypeTag` を得るには以下のようにする: - - import scala.reflect.runtime.universe._ - val tt = typeTag[Int] - -同様に `String` を表す `ClassTag` を得るには以下のように行う: - - import scala.reflect._ - val ct = classTag[String] - -これらのメソッドはそれぞれ型引数 `T` の `TypeTag[T]` か `ClassTag[T]` を構築する。 - -### `TypeTag[T]`、`ClassTag[T]`、もしくは `WeakTypeTag[T]` 型の暗黙のパラメータを使う - -`Manifest` 同様にコンパイラに `TypeTag` を生成するように申請することができる。 -これは、`TypeTag[T]` 型の暗黙のパラメータを宣言するだけで行われる。 -もしコンパイラが implicit の検索時にマッチする implicit の値を探すことができなければ自動的に -`TypeTag[T]` を生成する。 - -**注意**: 典型的に、これはメソッドかクラスのみに暗黙のパラメータを使うことで達成される。 - -例えば、任意のオブジェクトを受け取るメソッドを書いて、`TypeTag` -を使ってそのオブジェクトの型引数を表示することができる: - - import scala.reflect.runtime.universe._ - - def paramInfo[T](x: T)(implicit tag: TypeTag[T]): Unit = { - val targs = tag.tpe match { case TypeRef(_, _, args) => args } - println(s"type of $x has type arguments $targs") - } - -ここで `T` についてパラメータ化された多相メソッド `paramInfo` を暗黙のパラメータ -`(implicit tag: TypeTag[T])` と共に定義する。これで、`TypeTag` の `tpe` -メソッドを使って `tag` が表す (`Type` 型の) 型に直接アクセスすることができる。 - -実際に `paramInfo` メソッドを使ってみよう: - - scala> paramInfo(42) - type of 42 has type arguments List() - - scala> paramInfo(List(1, 2)) - type of List(1, 2) has type arguments List(Int) - -### 型パラメータの context bound を使う - -型パラメータに context bound を付けることで上と同じことをもう少し簡潔に書ける。 -独立した暗黙のパラメータを定義する代わりに以下のように型パラメータのリストに -`TypeTag` を付けることができる: - - def myMethod[T: TypeTag] = ... - -context bound `[T: TypeTag]` からコンパイラは `TypeTag[T]` -型の暗黙のパラメータを生成して前節の暗黙のパラメータを使った用例のようにメソッドを書き換える。 - -上の具体例を context bound を使って書きなおしてみる: - - import scala.reflect.runtime.universe._ - - def paramInfo[T: TypeTag](x: T): Unit = { - val targs = typeOf[T] match { case TypeRef(_, _, args) => args } - println(s"type of $x has type arguments $targs") - } - - scala> paramInfo(42) - type of 42 has type arguments List() - - scala> paramInfo(List(1, 2)) - type of List(1, 2) has type arguments List(Int) - -## WeakTypeTags - -`WeakTypeTag[T]` は `TypeTag[T]` を一般化する。普通の -`TypeTag` と違ってそれが表す型の構成要素は型パラメータか抽象型への参照であることもできる。 -しかし、`WeakTypeTag[T]` は可能な限り具象的であろうとするため、 -参照された型引数か抽象型の型タグが入手可能ならばそれを使って -`WeakTypeTag[T]` に具象型を埋め込む。 - -先ほどからの具体例を続けよう: - - def weakParamInfo[T](x: T)(implicit tag: WeakTypeTag[T]): Unit = { - val targs = tag.tpe match { case TypeRef(_, _, args) => args } - println(s"type of $x has type arguments $targs") - } - - scala> def foo[T] = weakParamInfo(List[T]()) - foo: [T]=> Unit - - scala> foo[Int] - type of List() has type arguments List(T) - -## 型タグとマニフェストの比較 - -型タグ (`TypeTag`) は 2.10 以前のマニフェスト (`scala.reflect.Manifest`) の概念に相当する。 -`scala.reflect.ClassTag` は `scala.reflect.ClassManifest` -に相当するもので、 -`scala.reflect.api.TypeTags#TypeTag` は `scala.reflect.Manifest` -に相当するものだが、他の 2.10 以前のマニフェスト型には直接対応する 2.10 のタグ型は存在しない。 - -
          -
        • scala.reflect.OptManifest はサポートされない。 -これはタグが任意の型をレイファイすることができるため、必要無くなったからだ。
        • - -
        • scala.reflect.AnyValManifest に相当するものは無い。 -ある型がプリミティブ値クラスであるかを調べるには、型タグを (基底タグのコンパニオンオブジェクトで定義されている) 基底タグと比較することができる。もしくは、 -<tag>.tpe.typeSymbol.isPrimitiveValueClass を使うこともできる。
        • - -
        • マニフェストのコンパニオンオブジェクトに定義されるファクトリ・メソッドに相当するものは無い。 -代わりに、(クラスの場合は) Java か (型の場合は) Scala によって提供されるリフレクション API を使って対応する型を生成することができる。
        • - -
        • いくつかのマニフェスト演算 (具体的には、<:<>:>、と typeArguments) はサポートされない。 -代わりに、(クラスの場合は) Java か (型の場合は) Scala によって提供されるリフレクション API を使うことができる。
        • -
        - -`scala.reflect.ClassManifest` は Scala 2.10 から廃止予定となり、将来のマイナーリリースにおいて -`scala.reflect.Manifest` も廃止予定として `TypeTag` と `ClassTag` に道を開けることを予定している。 -そのため、マニフェストを使ったコードは型タグを使うものに移行することを推奨する。 diff --git a/ko/tutorials/scala-for-java-programmers.md b/ko/tutorials/scala-for-java-programmers.md deleted file mode 100644 index 919b8783e6..0000000000 --- a/ko/tutorials/scala-for-java-programmers.md +++ /dev/null @@ -1,682 +0,0 @@ ---- -layout: overview -title: 자바 프로그래머를 위한 스칼라 튜토리얼 -overview: scala-for-java-programmers - -discourse: false -language: ko ---- - -Michel Schinz, Philipp Haller 지음. -이희종 (heejong@gmail.com) 옮김. - - -## 시작하면서 - -이 문서는 Scala 언어와 그 컴파일러에 대해 간단히 소개한다. -어느 정도의 프로그래밍 경험이 있으며 Scala를 통해 무엇을 할 수 -있는지를 빠르게 배우고 싶은 사람들을 위해 만들어 졌다. -여기서는 독자가 객체 지향 프로그래밍, 특히 Java에 대한 지식을 -가지고 있다고 가정한다. - -## 첫 번째 예제 - -첫번째 예제로 흔히 쓰이는 *Hello world* 프로그램을 사용하자. -이 프로그램은 그다지 멋지지는 않지만 언어에 대한 많은 지식 없이도 -Scala 언어를 다루는데 필요한 도구들의 사용법을 쉽게 보여 줄 수 있다. -아래를 보자: - - object HelloWorld { - def main(args: Array[String]) { - println("Hello, world!") - } - } - -자바 프로그래머들은 이 프로그램의 구조가 익숙 할 것이다. -프로그램은 문자열 배열 타입의 명령줄 인자를 받는 이름이 `main`인 -함수 하나를 가지고 있다. 이 함수의 구현은 하나의 또 다른 함수 호출로 -이루어져 있는데 미리 정의 된 함수 `println`에 어디선가 많이 본 -바로 그 환영 메시지를 넘겨주어 호출 한다. `main` 함수는 값을 돌려주지 -않기 때문에 리턴 타입을 선언 할 필요가 없다. - -자바 프로그래머들에게 익숙하지 않은 부분은 `main` 함수를 감싸고 -있는 `object` 선언일 것이다. 이 선언은 **싱글턴 객체**를 생성하는데, -이는 하나의 인스턴스만을 가지는 클래스라 할 수 있다. 따라서 위의 선언은 -`HelloWorld`라는 클래스와 역시 `HelloWorld`라고 이름 -붙인 이 클래스의 인스턴스를 함께 정의 하는 것이다. 이 인스턴스는 처음 -사용 될 때에 필요에 따라 만들어 진다. - -똑똑한 독자들은 이미 눈치챘겠지만 위의 예제에서 `main` 함수는 -`static`이 아니다. Scala에는 정적 멤버(함수든 필드든)라는 개념이 -아얘 존재하지 않는다. 클래스의 일부로 정적 멤버를 정의하는 대신에 Scala -프로그래머들은 정적이기 원하는 멤버들을 싱글턴 객체안에 선언한다. - -### 예제를 컴파일 하기 - -예제를 컴파일 하기 위하여 Scala 컴파일러인 `scalac`를 사용한다. -`scalac`는 대부분의 컴파일러들과 비슷하게 동작한다. 소스파일과 필요에 -따라 몇개의 옵션들을 인자로 받아 한개 또는 여러개의 오브젝트 파일을 -생성한다. `scalac`가 생성하는 오브젝트 파일은 표준적인 Java 클래스 -파일이다. - -위의 예제 프로그램을 `HelloWorld.scala`라는 이름으로 저장했다면, -아래의 명령으로 컴파일 할 수 있다 (부등호 `>`는 쉘 프롬프트이므로 -함께 입력하지 말것) : - - > scalac HelloWorld.scala - -이제 현재 디렉토리에 몇개의 클래스 파일이 생성되는 것을 확인 할 수 있다. -그 중에 하나는 `HelloWorld.class`이며 `scala` 명령을 통해 바로 실행 -가능한 클래스를 포함하고 있다. 다음 장을 보자. - -### 예제를 실행하기 - -일단 컴파일 되면 Scala 프로그램은 `scala` 명령을 통해 실행 할 수 있다. -사용법은 Java 프로그램을 실행 할 때 사용하는 `java` 명령과 매우 비슷하며 -동일한 옵션을 사용 가능하다. 위의 예제는 아래의 명령으로 실행 할 수 있으며 -예상한대로의 결과가 나온다. - - > scala -classpath . HelloWorld - - Hello, world! - -## 자바와 함께 사용하기 - -Scala의 장점 중 하나는 Java 코드와 함께 사용하기 쉽다는 것이다. -사용하고 싶은 Java 클래스를 간단히 임포트 하면 되며, `java.lang` -패키지의 모든 클래스는 임포트 하지 않아도 기본적으로 사용 할 수 있다. - -아래는 Scala가 Java와 얼마나 잘 어울리는지를 보여주는 예제이다. -우리는 아래 예제에서 현재의 날짜를 구하여 특정 국가에서 사용하는 형식으로 -변환 할 것이다. 이를테면 프랑스(불어를 사용하는 스위스의 일부 지역도 -동일한 형식을 사용한다)라 하자. - -Java의 클래스 라이브러리는 `Date`와 `DateFormat`과 같은 -강력한 유틸리티 클래스를 가지고 있다. Scala는 Java와 자연스럽게 -서로를 호출 할 수 있으므로, 동일한 역할을 하는 Scala 클래스 라이브러리를 -구현하기 보다는 우리가 원하는 기능을 가진 Java 패키지를 간단히 임포트하여 -이용하자. - - import java.util.{Date, Locale} - import java.text.DateFormat - import java.text.DateFormat._ - - object FrenchDate { - def main(args: Array[String]) { - val now = new Date - val df = getDateInstance(LONG, Locale.FRANCE) - println(df format now) - } - } - -Scala의 임포트 구문은 Java의 그것과 매우 비슷해 보이지만 사실 좀 더 -강력하다. 위 예제의 첫번째 줄과 같이 중괄호를 사용하면 같은 패키지에서 -여러개의 클래스를 선택적으로 불러 올 수 있다. Scala 임포트 구문의 -또 한가지 특징은 패키지나 클래스에 속한 모든 이름들을 불러 올 경우 -별표(`*`) 대신 밑줄(`_`) 을 사용 한다는 것이다. 별표는 Scala에서 -합법적인 식별자(함수명 등에 사용 가능한)로 사용된다. 나중에 자세히 살펴 -볼 것이다. - -따라서 세번째 줄의 임포트 구문은 `DateFormat` 클래스의 모든 멤버를 -불러온다. 이렇게 함으로써 정적 함수 `getDateInstance`와 정적 필드 -`LONG`이 바로 사용 가능하게 된다. - -`main` 함수 안에서 처음 하는 일은 Java 라이브러리에 속한 -`Date` 클래스의 인스턴스를 생성하는 것이다. 이 인스턴스는 기본적으로 -현재의 날짜를 가지고 있다. 다음으로 이전에 불러온 정적 함수 -`getDateInstance`를 통해 날짜 형식을 결정하고, 프랑스에 맞춰진 -`DateFormat` 인스턴스를 사용하여 현재의 날짜를 출력한다. 이 -마지막 줄은 Scala 문법의 재미있는 특성을 보여준다. 오직 하나의 인자를 -갖는 함수는 마치 이항연산자 같은 문법으로 호출 가능하다. 이 이야기는 곧 -아래의 표현식이: - - df format now - -아래 표현식과 동일한 의미를 가진 다는 것이다. 그저 좀 더 간단하게 표현 -되었을 뿐이다. - - df.format(now) - -이러한 특성은 그저 별것 아닌 문법의 일부 인것 처럼 보이지만 여러 곳에서 -중요하게 사용 된다. 그중에 하나가 다음 장에 나와있다. - -이번 장에서는 Java와 Scala가 얼마나 자연스럽게 서로 녹아드는지에 대해 -배웠다. 이번 장에는 나타나지 않았지만, Scala 안에서 Java의 클래스들을 -상속받고 Java의 인터페이스들을 바로 구현하는 것도 가능하다. - -## 모든 것은 객체다 - -Scala는 순수한 객체지향적 언어이다. 이 말은 곧 숫자와 함수를 포함한 -**모든것**이 객체라는 것이다. 이러한 면에서 Scala는 Java와 다르다. -Java에서는 기본적인 타입(`boolean`이나 `int` 따위)과 참조 가능한 -타입이 분리되어 있으며, 함수를 값과 동일하게 다룰 수도 없다. - -### 숫자도 하나의 객체다 - -숫자는 객체이기 때문에 함수들을 포함하고 있다. 사실 아래와 같은 -표현식은: - - 1 + 2 * 3 / x - -오직 함수 호출로만 이루어져 있다. 우리가 이전 장에서 보았듯이, 위의 -표현식은 아래의 표현식과 동일하다. - - (1).+(((2).*(3))./(x)) - -위의 표현식처럼 `+`, `*` 등은 Scala에서 합법적인 식별자이다. - -위의 두번째 표현식에서 괄호는 꼭 필요하다. 왜냐하면 스칼라의 렉서(lexer)는 -토큰들에 대하여 가장 긴 부분을 찾는 방법을 사용하기 때문이다. 아래의 -표현식은: - - 1.+(2) - -세개(`1.`, `+`, `2`)의 토큰들로 분리된다. 이렇게 토큰들이 -분리되는 이유는 미리 정의되어 있는 유효한 토큰 중에 `1.`이 -`1`보다 길기 때문이다. 토큰 `1.`은 리터럴 `1.0`으로 -해석 되어 `Double` 타입이 된다. 실제로 우리는 `Int` 타입을 -의도 했음에도 말이다. 표현식을 아래와 같이 쓰면: - - (1).+(2) - -토큰 `1`이 `Double`로 해석 되는 것을 방지 할 수 있다. - -### 함수마저 객체다 - -Java 프로그래머들에게는 놀라운 일이겠지만 Scala에서는 함수도 -역시 객체이다. 따라서 함수에 함수를 인자로 넘기거나, 함수를 변수에 -저장하거나, 함수가 함수를 리턴하는 것도 가능하다. 이처럼 함수를 값과 -동일하게 다루는 것은 매우 흥미로운 프로그래밍 패러다임인 -**함수형 프로그래밍**의 핵심 요소 중 하나이다. - -함수를 값과 같이 다루는 것이 유용함을 보이기 위해 아주 간단한 예제를 -든다. 어떠한 행동을 매초 수행하는 타이머 함수를 생각해 보자. 수행 할 -행동을 어떻게 넘겨 주어야 할까? 논리적으로 생각한다면 함수를 넘겨 주어야 -한다. 함수를 전달하는 이런 종류의 상황은 많은 프로그래머들에게 익숙 할 -것이다. 바로 유저 인터페이스 코드에서 어떤 이벤트가 발생하였을 때 불릴 -콜백 함수를 등록하는 것 말이다. - -아래 프로그램에서 타이머 함수의 이름은 `oncePerSecond`이다. 이 함수는 -콜백 함수를 인자로 받는다. 인자로 받는 함수의 타입은 `() => Unit` 인데, -이 타입은 인자를 받지 않고 아무 것도 돌려주지 않는 모든 함수를 뜻한다 -(`Unit` 타입은 C/C++에서 `void`와 비슷하다). 이 프로그램의 메인 함수는 -이 타이머 함수를 화면에 문장을 출력하는 간단한 콜백함수를 인자로 호출한다. -결국 이 프로그램이 하는 일은 일초에 한번씩 "time flies like an arrow"를 -화면에 출력하는 것이 된다. - - object Timer { - def oncePerSecond(callback: () => Unit) { - while (true) { callback(); Thread sleep 1000 } - } - def timeFlies() { - println("time flies like an arrow...") - } - def main(args: Array[String]) { - oncePerSecond(timeFlies) - } - } - -우리는 문자열을 화면에 출력하기 위하여 Scala에 정의된 `println`을 사용 -하였다. 이 함수는 Java에서 흔히 사용하는 `System.out`에 정의된 것과 -다르다. - -#### 이름없는 함수 - -이 프로그램은 이해하기 쉽지만 조금 더 다듬을 수도 있다. -함수 `timeFlies`는 오직 함수 `oncePerSecond`에 인자로 -넘겨지기 위해 정의 되었다는 것에 주목하자. 이러한 한번만 사용되는 -함수에 이름을 붙여 준다는 것은 필요 없는 일일 수 있다. 더 행복한 -방법은 `oncePerSecond`에 함수가 전달 되는 그 순간 이 함수를 -생성하는 것이다. Scala에서 제공하는 **무명함수**를 사용하면 -된다. 무명함수란 말 그대로 이름이 없는 함수이다. 함수 `timeFlies` -대신에 무명함수를 사용한 새로운 버전의 타이머 프로그램은 아래와 같다: - - object TimerAnonymous { - def oncePerSecond(callback: () => Unit) { - while (true) { callback(); Thread sleep 1000 } - } - def main(args: Array[String]) { - oncePerSecond(() => - println("time flies like an arrow...")) - } - } - -`main` 함수 안에 오른쪽 화살표 `=>`가 있는 곳이 무명함수이다. -오른쪽 화살표는 함수의 인자와 함수의 내용을 분리 해주는 역할을 한다. 위 -예제에서 인자의 리스트는 비어있다. 화살표의 왼쪽을 보면 빈 괄호를 볼 수 -있다. 함수의 내용은 `timeFlies`와 일치한다. - -## 클래스에 대하여 - -지금까지 보았듯 Scala는 객체지향적 언어이며 클래스의 개념이 존재한다. -(어떤 객체지향 언어는 클래스의 개념이 존재하지 않는다. 당연하게도 -Scala는 이들에 속하지 않는다.) -Scala의 클래스 정의는 Java의 클래스 정의와 유사하다. 한가지 중요한 차이점은 -Scala 클래스의 경우 파라미터들을 가질 수 있다는 것인데 아래 복소수 예제에 -잘 나타나 있다: - - class Complex(real: Double, imaginary: Double) { - def re() = real - def im() = imaginary - } - -이 복소수 클래스는 두개의 인자를 받는다. 하나는 복소수의 실수 부분이고 -다른 하나는 복소수의 허수 부분에 해당하는 값이 된다. 이 인자들은 -`Complex` 클래스의 인스턴스를 생성 할 때 이처럼 반드시 전달 되어야 -한다: `new Complex(1.5, 2.3)`. 클래스는 `re`와 `im`라는 -두 함수를 가지고 있는데 각각의 함수를 통해 복소수를 구성하는 해당 부분의 -값을 얻을 수 있다. - -이 두 함수의 리턴타입은 명시적으로 나타나 있지 않다는 사실에 주목하자. -컴파일러는 이 함수들의 오른편을 보고 둘 다 `Double` 타입을 리턴 -한다고 자동으로 유추해 낸다. - -하지만 컴파일러가 언제나 이렇게 타입을 유추해 낼 수 있는 것은 아니다. -그리고 불행하게도 어떤 경우 이러한 타입 유추가 가능하고 어떤 경우 불가능 -한지에 관한 명확한 규칙도 존재하지 않는다. 일반적으로 이러한 상황은 -별 문제가 되지 않는다. 왜냐하면 명시적으로 주어지지 않은 타입정보를 -컴파일러가 자동으로 유추 해 낼 수 없는 경우 컴파일 시 에러가 발생하기 -때문이다. 초보 Scala 프로그래머들을 위한 한가지 방법은, 주변을 보고 쉽게 -타입을 유추 해 낼 수 있는 경우 일단 타입 선언을 생략하고 컴파일러가 받아 -들이는지 확인하는 것이다. 이렇게 몇번을 반복하고 나면 프로그래머는 언제 -타입을 생략해도 되고 언제 명시적으로 써주어야 하는지 감을 잡게 된다. - -### 인자 없는 함수 - -함수 `re`와 `im`의 사소한 문제는 그들을 호출하기 위해 항상 -뒤에 빈 괄호를 붙여 주어야 한다는 것이다. 아래를 보자: - - object ComplexNumbers { - def main(args: Array[String]) { - val c = new Complex(1.2, 3.4) - println("imaginary part: " + c.im()) - } - } - -실수 부분과 허수 부분에 접근 할 때에 마치 그들이 필드인 것 처럼 함수 -마지막에 빈 괄호를 붙이지 않을 수 있다면 더욱 좋겠다. 놀라지 마시라, -Scala는 이러한 기능을 완벽하게 제공한다. 그저 **인자를 제외**하고 -함수를 정의하면 된다. 이런 종류의 함수는 인자가 0개인 함수와는 다른데, -인자가 0개인 함수는 빈 괄호가 따라 붙는 반면 이 함수는 정의 할 때도 -사용 할 때도 이름 뒤에 괄호를 붙이지 않는다. 우리가 앞서 정의한 -`Complex` 클래스는 아래와 같이 다시 쓸 수 있다: - - class Complex(real: Double, imaginary: Double) { - def re = real - def im = imaginary - } - - -### 상속과 재정의 - -모든 Scala의 클래스들은 항상 상위 클래스로부터 상속된다. 만약 -`Complex` 예제 처럼 상위 클래스가 존재하지 않을 경우는 -묵시적으로 `scala.AnyRef`를 상속한다. - -Scala에서는 물론 상위 클래스에 정의된 함수를 오버라이드 하는 것도 -가능하다. 그러나 의도하지 않는 실수를 방지하기 위하여 다른 함수를 -오버라이드 하는 함수는 `override` 지시자를 꼭 적어주어야 한다. -예를 들면, 우리의 `Complex` 클래스에 대해 `Object`로 부터 -상속된 `toString` 함수를 재정의 하는 법은 아래와 같다: - - class Complex(real: Double, imaginary: Double) { - def re = real - def im = imaginary - override def toString() = - "" + re + (if (im < 0) "" else "+") + im + "i" - } - - -## 케이스 클래스 그리고 패턴 매칭 - -프로그램에 자주 등장하는 데이터 구조 중의 하나는 트리이다. -인터프리터와 컴파일러는 흔히 트리를 사용하여 내부 표현을 저장하고, -XML 문서도 트리이며, 레드블랙 트리와 같은 저장구조 들도 트리에 -기반을 두고 있다. - -작은 계산기 프로그램을 통해 Scala에서 이러한 트리들을 어떻게 -표현하고 다루는지에 대해 알아 보자. 이 프로그램의 목표는 더하기와 -상수인 정수 그리고 변수로 이루어진 간단한 산술 표현식을 다루는 것이다. -예를 들면, `1+2`나 `(x+x)+(7+y)` 같은 식들 말이다. - -처음으로, 우리는 해당 산술 표현식들을 어떻게 표현 할지 결정해야 한다. -가장 자연스러운 방법은 트리를 사용하는 것이다. 노드는 연산(여기서는 -덧셈)이 될 것이고, 리프는 값(여기서는 상수 또는 변수)가 되겠다. - -Java였다면 트리를 나타내기 위해, 트리에 대한 추상 상위 클래스와 -노드와 리프 각각에 대한 실제 하위 클래스들을 정의 했을 것이다. -함수형 언어였다면 같은 목적으로 대수적 데이터 타입을 사용 했을 것이다. -Scala는 **케이스 클래스**라 하는 이 둘 사이의 어디쯤에 놓여 질 수 -있는 장치를 제공한다. 우리 예제의 트리 타입을 정의하기 위해 이 장치가 -어떻게 사용 되는지 아래에서 실제적인 예를 보자: - - abstract class Tree - case class Sum(l: Tree, r: Tree) extends Tree - case class Var(n: String) extends Tree - case class Const(v: Int) extends Tree - -클래스 `Sum`, `Var` 그리고 `Const`가 케이스 클래스로 -선언되었다는 것은 이들이 여러가지 면에서 일반적인 클래스와 다르다는 -의미이다: - -- 인스턴스를 생성 할 때 `new` 키워드를 생략 할 수 있다. - 다른 말로, `new Const(5)`라 쓰는 대신 `Const(5)`라 쓰면 된다. -- 생성자 파라미터들에 대한 getter 함수가 자동으로 정의된다. 다른 말로, - 클래스 `Const`의 인스턴스 `c`에 있는 생성자 파라미터 `v`의 - 값은 `c.v`로 접근 가능하다. -- 함수 `equals`와 `hashCode`도 공짜로 제공된다. 이 함수들은 - 레퍼런스의 동일함 보다 **구조**의 동일함을 확인 하도록 구현되어 있다. - 다른 말로, 생성 된 곳이 다르더라도 각각의 생성자 파라미터 값이 같다면 - 같은 것으로 여긴다. -- 함수 `toString`에 대한 기본적 구현이 제공된다. 이 기본적인 - 구현은 "값이 생성 될 때"의 형태를 출력한다. 예를 들어 `x+1`의 트리 표현 - 을 출력 한다면 `Sum(Var(x),Const(1))`이 된다. -- 케이스 클래스들의 인스턴스는 **패턴 매칭**을 통해 따로 사용 될 - 수 있다. 자세한 내용은 아래에서 다룬다. - -산술 표현식을 나타낼 수 있는 데이터 타입을 정의 했으므로 이제 그것들을 -계산 할 연산자들을 정의 할 차례다. 일단, 어떤 **환경**안에서 표현식을 -계산 해주는 함수부터 시작하자. 환경은 각각의 변수마다 주어진 값들을 저장 -해 두는 곳이다. 컴퓨터에서 메모리의 역할과 비슷 하다고 생각하면 된다. -예를 들어, 변수 `x`에 `5`가 저장된 환경(`{ x -> 5 }`)에서 표현식 -`x+1`을 계산하면 결과로 `6`이 나온다. - -환경은 어떻게 표현하는게 좋을까? 간단히 생각하면, 해쉬 테이블 같은 -두 값을 묶어주는 데이터 구조를 사용 할 수 있겠다. 그러나 우리는 이러한 -데이터를 저장하는 목적으로 함수를 직접 사용 할 수도 있다! 가만 생각해 -보면 환경이라는 것은 변수명에서 값으로 가는 함수에 지나지 않는다. -위에서 사용한 환경 `{ x -> 5 }` 은 Scala로 간단히 아래와 같이 -쓴다: - - { case "x" => 5 } - -이 문법은 함수를 정의한다. 이 함수는 문자열 `"x"`가 인자로 들어 -왔을 때 정수 `5`를 돌려주고, 다른 모든 경우에 예외를 발생시키는 함수이다. - -계산하는 함수를 작성하기 전에 환경 타입에 이름을 붙여 주는 것이 좋겠다. -물론 항상 환경 타입으로 `String => Int`를 사용해도 되지만 보기 좋은 -이름을 붙이는 것은 프로그램을 더 읽기에 명료하고 변경에 유연하게 해 준다. -Scala에서는 아래와 같이 할 수 있다: - - type Environment = String => Int - -이제부터 타입 `Environment`는 `String`에서 `Int`로 가는 -함수 타입의 다른 이름이다. - -지금부터 계산하는 함수를 정의하자. 개념으로 따지면 매우 간단하다: -두 표현식의 합은 각 표현식의 값을 구하여 더한 것이다. 변수의 값은 -환경에서 바로 가져 올 수 있고, 상수의 값은 상수 자체이다. 이것을 -Scala로 나타내는 것은 어렵지 않다: - - def eval(t: Tree, env: Environment): Int = t match { - case Sum(l, r) => eval(l, env) + eval(r, env) - case Var(n) => env(n) - case Const(v) => v - } - -이 계산 함수는 트리 `t`에 대해 **패턴 매칭**을 수행함으로써 -동작한다. 위의 함수 정의는 직관적으로도 이해하기 쉽다: - -1. 처음으로 `t`가 `Sum`인지 확인한다. 만약 맞다면 왼쪽 - 서브트리를 새로운 변수 `l`에 오른쪽 서브트리를 새로운 변수 - `r`에 할당 한다. 그리고 화살표를 따라 화살표의 오른편으로 계산을 - 이어 나간다. 화살표의 오른편에서는 화살표의 왼편에서 할당된 변수 - `l`과 `r`을 사용 한다. -2. 첫번째 확인이 성공하지 못하면 트리는 `Sum`이 아니라는 - 이야기이다. 다음으로는 `t`가 `Var`인지 확인한다. 만약 - 맞다면 `Var` 노드 안에 포함된 이름을 변수 `n`에 할당한다. - 그리고 화살표의 오른쪽으로 진행한다. -3. 두번째 확인 역시 실패하면 `t`는 `Sum`도 `Var`도 - 아니라는 뜻이다. 이제는 `Const`에 대해 확인 해본다. 만약 - 맞다면 `Const` 노드 안의 값을 변수 `v`에 할당하고 화살표의 - 오른쪽으로 진행한다. -4. 마지막으로 모든 확인이 실패하면 패턴 매칭이 실패 했음을 알리는 - 예외가 발생하게 된다. 이러한 상황은 확인 한 것 외에 `Tree`의 - 하위 클래스가 더 존재 할 경우 일어난다. - -패턴 매칭의 기본적인 아이디어는 대상이 되는 값을 여러가지 관심있는 -패턴에 대해 순서대로 맞춰 본 후, 맞는 것이 있으면 맞은 값 중 관심 있는 -부분에 대해 새롭게 이름 붙이고, 그 이름 붙인 부분을 사용하는 어떠한 -작업을 진행하는 것이다. - -객체지향에 숙련된 프로그래머라면 왜 `eval`을 클래스 `Tree`와 -그 하위 클래스에 대한 **멤버 함수**로 정의하지 않았는지 궁금 할 것이다. -사실 그렇게 할 수도 있었다. Scala는 일반적인 클래스 처럼 케이스 클래스에 -대해서도 함수 정의를 허용한다. 패턴 매칭을 사용하느냐 멤버 함수를 -사용하느냐는 사용자의 취향에 달린 문제다. 하지만 확장성에 관해 시사하는 -중요한 점이 있다: - -- 멤버 함수를 사용하면 단지 `Tree`에 대한 하위 클래스를 새롭게 - 정의 함으로 새로운 노드를 추가하기 쉽다. 반면에 트리에 대한 새로운 - 연산을 추가하는 작업이 고되다. 새로운 연산을 추가하기 위해서는 - `Tree`의 모든 하위 클래스를 변경해야 하기 때문이다. -- 패턴 매칭을 사용하면 상황이 반대가 된다. 새로운 노드를 추가하려면 - 트리에 대해 패턴 매칭을 수행하는 모든 함수들을 새로운 노드도 고려하도록 - 변경해야 한다. 반면에 새로운 연산을 추가하는 것은 쉽다. 그냥 새로운 - 독립적인 함수를 만들면 된다. - -패턴 매칭에 대해 좀 더 알아보기 위해, 산술 표현식에 대한 또 다른 연산을 -정의 해보자. 이번 연산은 심볼 추출이다. 트리에서 우리가 원하는 특정 변수만 -1로 표시하는 일이다. 독자는 아래 규칙만 기억하면 된다: - -1. 더하기 표현식에서의 심볼 추출은 좌변과 우변의 심볼을 추출하여 더한 - 것과 같다. -2. 변수 `v`에 대한 심볼 추출은 `v`가 우리가 추출하기 원하는 심볼과 - 관련이 있다면 1이 되고 그 외의 경우 0이 된다. -3. 상수에 대한 심볼 추출 값은 0이다. - -이 규칙들은 거의 그대로 Scala 코드가 된다. - - def derive(t: Tree, v: String): Tree = t match { - case Sum(l, r) => Sum(derive(l, v), derive(r, v)) - case Var(n) if (v == n) => Const(1) - case _ => Const(0) - } - -위의 함수는 패턴 매칭에 관한 두 가지 새로운 기능을 소개한다. -첫 번째로, `case` 표현은 **가드**를 가질 수 있다. 가드란 -`if` 키워드 뒤에 오는 표현식을 뜻하는 말로 패턴 매칭에 추가적인 -조건을 부여한다. 가드가 참이 되지 않으면 패턴 매칭은 성공하지 못한다. -여기서는, 매칭 된 변수의 이름이 우리가 추출하는 심볼 `v`와 같을 -때만 상수 1을 리턴함을 보장하는 용도로 사용된다. 두 번째 새로운 기능은 -**와일드카드**이다. 밑줄 문자 `_`로 쓰며, 모든 값과 매치 되고 -따로 이름을 붙이지 않는다. - -매턴 매칭의 뛰어난 기능들을 모두 살펴보지는 못했지만, 문서를 너무 -지루하게 만들지 않기 위하여 이쯤에서 멈추기로 한다. 이제 위에서 정의한 -두 개의 예제 함수가 실제로 동작하는 모습을 보자. 산술 표현식 -`(x+x)+(7+y)`에 대해 몇가지의 연산을 실행하는 간단한 `main` 함수를 -만들기로 한다. 첫번째로 환경 `{ x -> 5, y -> 7 }`에서 -그 값을 계산 할 것이고, 다음으로 `x`와 `y`에 대한 심볼 추출을 수행 할 -것이다. - - def main(args: Array[String]) { - val exp: Tree = Sum(Sum(Var("x"),Var("x")),Sum(Const(7),Var("y"))) - val env: Environment = { case "x" => 5 case "y" => 7 } - println("Expression: " + exp) - println("Evaluation with x=5, y=7: " + eval(exp, env)) - println("Derivative relative to x:\n " + derive(exp, "x")) - println("Derivative relative to y:\n " + derive(exp, "y")) - } - -이 프로그램을 실행하면, 예상된 결과를 얻을 수 있다: - - Expression: Sum(Sum(Var(x),Var(x)),Sum(Const(7),Var(y))) - Evaluation with x=5, y=7: 24 - Derivative relative to x: - Sum(Sum(Const(1),Const(1)),Sum(Const(0),Const(0))) - Derivative relative to y: - Sum(Sum(Const(0),Const(0)),Sum(Const(0),Const(1))) - -출력을 살펴 보면 심볼 추출의 결과가 사용자에게 좀 복잡하다는 -생각이 든다. 패턴 매칭을 사용하여 이 결과를 단순화 하는 함수를 -정의하는 것은 재미있는 문제이다(생각보다 복잡하기도 하다). -독자들에게 연습문제로 남겨두겠다. - -## 트레잇에 대하여 - -Scala 클래스에서는 상위 클래스에서 코드를 상속 받는 것 뿐만이 아니라, -하나 또는 여러개의 **트레잇(trait)**에서 코드를 불러 올 수 있는 방법도 -있다. - -Java 프로그래머들이 트레잇을 이해하는 가장 쉬운 길은 코드를 가질 수 있는 -인터페이스라고 생각하는 것이다. Scala에서 어떤 클래스가 트레잇을 상속하면, -그 클래스는 트레잇의 인터페이스를 구현해야만 하고 동시에 트레잇이 가진 모든 -코드들을 가져오게 된다. - -트레잇의 유용함을 보이기 위해 객체들에 순서를 붙이는 고전적인 예제 하나를 -들어보기로 하자. 순서가 있는 객체들은 정렬문제 처럼 주로 그들 사이에 비교가 -필요 할 경우 유용하다. Java에서는 비교가능한 객체들이 `Comparable` -인터페이스를 구현하게 된다. Scala에서는 이 `Comparable`을 트레잇으로 -정의하여 더 나은 프로그램 디자인을 제공 할 수 있다. 여기서는 이를 -`Ord`라 부를 것이다. - -객체를 비교 할 때, 여섯개의 서로 다른 관계가 주로 사용 된다: 작다, -작거나 같다, 같다, 같지 않다, 크거나 같다, 크다. 하지만 이 여섯개를 -일일히 구현하는 것은 지루하고 의미 없는 일이 될 것이다. 게다가 이중 두 -가지 관계만 정의 되어도 나머지 네가지 관계를 계산 할 수 있지 않은가. -예를 들어 같다와 작다만 결정 할 수 있어도 나머지 관계의 참 거짓을 쉽게 -판단 할 수 있다. Scala에서는 이러한 논리들을 트레잇의 정의 안에 -우아하게 표현 해 낼 수 있다: - - trait Ord { - def < (that: Any): Boolean - def <=(that: Any): Boolean = (this < that) || (this == that) - def > (that: Any): Boolean = !(this <= that) - def >=(that: Any): Boolean = !(this < that) - } - -위의 정의는 Java의 `Comparable` 인터페이스와 같은 역할을 하는 -`Ord`라고 불리는 새로운 타입을 만든다. 이 새로운 타입에는 -세가지의 관계식이 기본적으로 구현이 되어 있으며 이 구현은 모두 하나의 -추상 함수를 사용하고 있다. 모든 객체에 대해 기본적으로 존재하는 같다와 -같지 않다에 대한 관계식은 빠져 있다. - -위에서 사용된 타입 `Any`는 Scala의 최상위 타입이다. Java의 -`Object` 타입과 같으나, `Int`, `Float`과 같은 기본 타입의 -상위 타입이라는 점에서 좀 더 일반화 된 버전이라 생각 할 수 있다. - -객체를 비교 가능하게 만들기 위해 정의해야 할 것은 같다와 작다 뿐이다. -나머지는 위의 `Ord` 트레잇을 삽입하여 처리한다. 하나의 예로 -그레고리력의 날짜를 나타내는 `Date` 클래스를 만들어 보자. -이 날짜는 정수인 날, 월, 년으로 구성 된다. 일단 아래처럼 만든다: - - class Date(y: Int, m: Int, d: Int) extends Ord { - def year = y - def month = m - def day = d - override def toString(): String = year + "-" + month + "-" + day - -여기서 중요한 부분은 클래스 이름과 파라미터 뒤에 따라오는 -`extends Ord` 선언이다. 이 선언은 `Date` 클래스가 `Ord` -트레잇을 상속함을 뜻한다. - -다음으로 `Object`에서 상속된 `equals` 함수를 재정의 하여 -각각의 일, 월, 년을 비교하여 같음을 올바르게 판단하도록 한다. -`equals`의 기본 정의는 쓸모가 없다. 왜냐하면 Java와 같이 -기본적인 `equals`는 물리적 주소를 비교하기 때문이다. 최종적인 -코드는 다음과 같다: - - override def equals(that: Any): Boolean = - that.isInstanceOf[Date] && { - val o = that.asInstanceOf[Date] - o.day == day && o.month == month && o.year == year - } - -이 함수는 미리 정의된 함수인 `isInstanceOf`와 `asInstanceOf`를 -사용한다. 첫번째 `isInstanceOf`는 Java의 `instanceof` 연산자와 -동일한 일을 한다. 함수가 호출 된 객체가 함수의 인자로 들어온 타입의 -인스턴스이면 참을 리턴한다. 두번째 `asInstanceOf`는 Java의 캐스트 -연산자와 동일하다. 호출 된 객체가 인자로 들어온 타입의 인스턴스이면 그렇게 -여겨지도록 변환하고 아니라면 `ClassCastException`을 발생시킨다. - -아래 마지막으로 정의된 함수는 작음을 판단하는 함수이다. 여기서는 -`error`라는 또 다른 미리 정의된 함수가 쓰였는데, 이 함수는 -주어진 에러 메시지와 함께 예외를 발생 시키는 역할을 한다. - - def <(that: Any): Boolean = { - if (!that.isInstanceOf[Date]) - error("cannot compare " + that + " and a Date") - - val o = that.asInstanceOf[Date] - (year < o.year) || - (year == o.year && (month < o.month || - (month == o.month && day < o.day))) - } - -이걸로 `Date` 클래스의 정의가 완성되었다. 이 클래스의 인스턴스는 -날짜로도 또는 비교가능한 어떤 객체로도 여겨질 수 있다. 이들은 위에서 -언급한 여섯가지 비교연산을 모두 가지고 있는데, `equals`와 `<`는 -`Date` 클래스의 정의 안에 직접 구현되어 있고 나머지는 `Ord` -트레잇에서 상속 받은 것이다. - -트레잇은 여기서 예로 든 경우 외에도 물론 다양하게 사용 될 수 있다. -하지만 다양한 경우들에 대하여 깊게 다루는 일은 이 문서의 범위 밖이다. - -## 제네릭함 - -이 튜토리얼에서 다룰 Scala의 마지막 특징은 제네릭함이다. Java -프로그래머들은 Java의 제네릭 지원이 부족하기 때문에 발생한 여러가지 -문제점들에 대해 잘 알고 있을 것이다. 이 문제점들은 Java 1.5에서 -다뤄졌다. - -제네릭함이란 코드를 타입에 대하여 파라미터화 할 수 있는 능력이다. -이해를 돕기 위해 하나의 예를 들어 보자. 연결 리스트 라이브러리를 작성하는 -프로그래머는 리스트의 원소 타입을 도대체 무엇으로 해야 할지 고민에 -빠지게 된다. 이 연결 리스트는 서로 다른 많은 상황에서 사용 될 수 있기 -때문에 원소의 타입이 반드시 `Int` 또는 반드시 `Double`이 될 -것이라 미리 결정하는 것은 불가능하다. 이렇게 결정해 두는 일은 완전히 -임의적이며 라이브러리의 사용에 있어 필요 이상의 심한 제약으로 작용 -한다. - -Java 프로그래머는 어쩔 수 없이 `Object`를 사용하곤 한다. -`Object`는 모든 객체의 상위 타입이기 때문이다. 하지만 이런 방법은 -이상적이지 않다. `int`, `long`, `float`등과 같은 -기본 타입에 대해 동작하지 않으며, 연결 리스트에서 원소를 가져 올 때마다 -많은 동적 타입 캐스트들을 프로그래머가 직접 삽입해 주어야 하기 때문이다. - -Scala는 이 문제를 해결하기 위한 제네릭 클래스와 제네릭 함수를 지원한다. -예제로 함께 살펴보자. 예제는 레퍼런스라는 간단한 저장구조 클래스이다. -이 클래스는 비어있거나 또는 어떤 타입의 객체를 가리키는 포인터가 된다. - - class Reference[T] { - private var contents: T = _ - def set(value: T) { contents = value } - def get: T = contents - } - -클래스 `Reference`는 타입 `T`에 대해 파라미터화 되어있다. -타입 `T`는 레퍼런스의 원소 타입이다. 이 타입은 클래스 내부 -여러 곳에서 나타나는데, `contents` 변수의 타입으로, `set` -함수의 인자 타입으로, 그리고 `get` 함수의 리턴 타입으로 사용 된다. - -위의 코드 샘플은 Scala에서 필드 변수를 만드는 내용이므로 따로 설명이 -필요 없다. 한가지 흥미로운 점이 있다면 변수의 초기값이 `_`로 주어져 -있다는 것인데, 여기서 `_`는 기본값을 뜻한다. 기본값은 수 타입에 -대해서 0, `Boolean` 타입에 대해서 `false`, `Unit` -타입에 대해 `()`, 그리고 모든 객체 타입에 대해 `null`이다. - -`Reference` 클래스를 사용하려면 타입 파라미터 `T`에 대해 적당한 -타입을 지정해 주어야 한다. 이 타입은 레퍼런스 안에 들어갈 원소의 -타입이 된다. 예를 들어, 정수 값을 저장 할 수 있는 레퍼런스를 생성하고 -사용하기 위해서는 다음과 같이 쓴다: - - object IntegerReference { - def main(args: Array[String]) { - val cell = new Reference[Int] - cell.set(13) - println("Reference contains the half of " + (cell.get * 2)) - } - } - -위 예제에서 보듯 `get` 함수의 리턴값을 정수처럼 사용하기 위해 -따로 캐스팅이 필요하지 않다. 여기서 정의된 레퍼런스는 정수를 포함하도록 -선언이 되어 있으므로 정수 외에 다른 것은 넣을 수 없다. - -## 마치며 - -우리는 지금까지 Scala 언어의 간략한 소개와 몇가지의 예제를 살펴 -보았다. 흥미가 생겼다면 *Scala By Example*도 함께 읽어보자. 더 수준 -높고 다양한 예제를 만날 수 있다. 필요 할 때마다 *Scala Language -Specification*을 참고하는 것도 좋다. - diff --git a/ko/tutorials/tour/_posts/2017-02-13-abstract-types.md b/ko/tutorials/tour/_posts/2017-02-13-abstract-types.md deleted file mode 100644 index 01d27a6792..0000000000 --- a/ko/tutorials/tour/_posts/2017-02-13-abstract-types.md +++ /dev/null @@ -1,75 +0,0 @@ ---- -layout: tutorial -title: 추상 타입 - -discourse: false - -tutorial: scala-tour -categories: tour -num: 22 -outof: 35 -language: ko - -next-page: compound-types -previous-page: inner-classes ---- - -스칼라에선 값(생성자 파라미터)과 타입(클래스가 [제네릭](generic-classes.html)일 경우)으로 클래스가 매개변수화된다. 규칙성을 지키기 위해, 값이 객체 멤버가 될 수 있을 뿐만 아니라 값의 타입 역시 객체의 멤버가 된다. 또한 이런 두 형태의 멤버 모두 다 구체화되거나 추상화될 수 있다. - -이 예제에선 [클래스](traits.html) `Buffer`의 멤버로써 완전히 확정되지 않은 값과 추상 타입을 정의하고 있다. - - trait Buffer { - type T - val element: T - } - -*추상 타입*은 본성이 완전히 식별되지 않은 타입이다. 위의 예제에선 클래스 `Buffer`의 각 객체가 T라는 타입 멤버를 갖고 있다는 점만 알 수 있으며, 클래스 `Buffer`의 정의는 멤버 타입 `T`에 해당하는 특정 타입이 무엇이지 밝히고 있지 않다. 값 정의와 같이 타입 정의도 서브클래스에서 재정의(override) 할 수 있다. 이것은 타입 경계(추상 타입에 해당하는 예시를 나타내는)를 좀더 엄격하게 함으로써 추상 타입에 대해 좀더 많은 정보를 얻을수 있게 해준다. - -다음 프로그램에선 `T` 타입이 새로운 추상 타입 `U`로 표현된 `Seq[U]`의 서브타입이어야 함을 나타내서, 버퍼에 시퀀스 만을 저장하는 클래스 `SeqBuffer`를 만들었다. - - abstract class SeqBuffer extends Buffer { - type U - type T <: Seq[U] - def length = element.length - } - -추상 타입 멤버를 포함한 트레잇이나 [클래스](classes.html)는 종종 익명 클래스 인스턴스화와 함께 사용된다. 이를 알아보기 위해 정수의 리스트를 참조하는 시퀀스 버퍼를 다루는 프로그램을 살펴보자. - - 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) - } - -메소드 `newIntSeqBuf`의 반환 타입은 트레잇 `Buffer`의 특수화를 따르며, 타입 `U`가 `Int`와 같아진다. 메소드 `newIntSeqBuf` 내부의 익명 클래스 인스턴스화에서도 비슷한 타입 별칭이 있다. 여기선 `T` 타입이 `List[Int]`를 가리키는 `IntSeqBuf`의 새로운 인스턴스를 생성한다. - -추상 타입 멤버를 클래스의 타입 파라미터로 하거나 클래스의 타입 파라미터로 추상 타입 멤버로를사용할 수 있음에 주목하자. 다음은 타입 파라미터만을 사용한, 앞서 살펴본 코드의 새로운 버전이다. - - 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) - } - -여기선 [가변성 어노테이션](variances.html)을 사용해야만 한다는 점에 유의하자. 이를 사용하지 않으면 메소드 `newIntSeqBuf`에서 반환되는 객체의 특정 시퀀스 구현 타입을 감출 수 없게 된다. 뿐만 아니라 추상 타입을 타입 파라미터로 대체할 수 없는 경우도 있다. - -윤창석, 이한욱 옮김, 고광현 수정 diff --git a/ko/tutorials/tour/_posts/2017-02-13-annotations.md b/ko/tutorials/tour/_posts/2017-02-13-annotations.md deleted file mode 100644 index b54a7a1587..0000000000 --- a/ko/tutorials/tour/_posts/2017-02-13-annotations.md +++ /dev/null @@ -1,128 +0,0 @@ ---- -layout: tutorial -title: 어노테이션 - -discourse: false - -tutorial: scala-tour -categories: tour -num: 31 -language: ko - -next-page: default-parameter-values -previous-page: automatic-closures ---- - -어노테이션은 메타 정보와 정의 내용을 연결해준다. - -간단한 어노테이션 절은 `@C`나 `@C(a1, .., an)`와 같은 형태다. 여기서 `C`는 `C` 클래스의 생성자이며, `scala.Annotation`에 맞는 클래스여야만 한다. `a1, .., an`으로 주어지는 모든 생성자의 인수는 반드시 상수 표현식이여야 한다(예, 숫자 리터럴, 문자열, 클래스 리터럴, 자바 열거형, 그리고 이들의 1차원 배열). - -어노테이션 절은 첫 번째 정의나, 그 다음에 이어지는 선언에 적용된다. 정의와 선언에는 하나 이상의 어노테이션 절이 붙을 수 있다. 이런 절이 표현되는 순서는 영향을 미치지 않는다. - -어노테이션 절의 의미는 _구현 종속적_ 이다. 자바 플랫폼에선 다음의 스칼라 어노테이션이 표준에 해당하는 의미를 갖고 있다. - -| Scala | Java | -| ------ | ------ | -| [`scala.SerialVersionUID`](https://www.scala-lang.org/api/current/scala/SerialVersionUID.html) | [`serialVersionUID`](http://java.sun.com/j2se/1.5.0/docs/api/java/io/Serializable.html#navbar_bottom) (필드) | -| [`scala.deprecated`](https://www.scala-lang.org/api/current/scala/deprecated.html) | [`java.lang.Deprecated`](http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Deprecated.html) | -| [`scala.inline`](https://www.scala-lang.org/api/current/scala/inline.html) (2.6.0 부터) | 해당 없음 | -| [`scala.native`](https://www.scala-lang.org/api/current/scala/native.html) (2.6.0 부터) | [`native`](http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html) (키워드) | -| [`scala.remote`](https://www.scala-lang.org/api/current/scala/remote.html) | [`java.rmi.Remote`](http://java.sun.com/j2se/1.5.0/docs/api/java/rmi/Remote.html) | -| [`scala.throws`](https://www.scala-lang.org/api/current/scala/throws.html) | [`throws`](http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html) (키워드) | -| [`scala.transient`](https://www.scala-lang.org/api/current/scala/transient.html) | [`transient`](http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html) (키워드) | -| [`scala.unchecked`](https://www.scala-lang.org/api/current/scala/unchecked.html) (2.4.0 부터) | 해당 없음 | -| [`scala.volatile`](https://www.scala-lang.org/api/current/scala/volatile.html) | [`volatile`](http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html) (키워드) | -| [`scala.beans.BeanProperty`](https://www.scala-lang.org/api/current/scala/beans/BeanProperty.html) | [`디자인 패턴`](http://docs.oracle.com/javase/tutorial/javabeans/writing/properties.html) | - -다음 예제에선 자바의 메인 프로그램에서 던지는 예외를 잡기 위해, `read` 메소드에 `throws` 어노테이션을 추가했다. - -> 자바 컴파일러는 메소드나 생성자를 실행할 때 어떤 확인 예외가 발생할 수 있는지 분석해, 프로그램이 [확인이 필요한 예외](http://docs.oracle.com/javase/specs/jls/se5.0/html/exceptions.html)를 처리할 핸들러를 포함하고 있는지 검사한다. 메소드나 생성자의 **throws** 절에선 발생할 가능성이 있는 확인 예외마다, 해당 예외의 클래스나 해당 예외 클래스의 상위 클래스를 반드시 명시해야 한다. -> 스칼라는 확인 예외가 없기 때문에 스칼라 메소드는 스칼라 메소드가 던지는 예외를 자바 코드가 잡을 수 있도록 반드시 하나 이상의 `throws` 어노테이션을 붙여야 한다. - - package examples - import java.io._ - class Reader(fname: String) { - private val in = new BufferedReader(new FileReader(fname)) - @throws(classOf[IOException]) - def read() = in.read() - } - -다음의 자바 프로그램은 `main` 메소드의 첫 번째 인수로 전달된 이름의 파일을 열어 내용을 출력한다. - - package test; - import examples.Reader; // Scala class !! - 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()); - } - } - } - -Reader 클래스의 `throws` 어노테이션을 주석으로 처리하면 자바 메인 프로그램을 컴파일 할 때 다음과 같은 오류 메시지가 나타난다. - - Main.java:11: exception java.io.IOException is never thrown in body of - corresponding try statement - } catch (java.io.IOException e) { - ^ - 1 error - -### 자바 어노테이션 ### - -**주의:** 자바 어노테이션과 함께 `-target:jvm-1.5` 옵션을 사용해야 한다. - -자바 1.5에선 [어노테이션](http://java.sun.com/j2se/1.5.0/docs/guide/language/annotations.html)이란 형태로 사용자 지정 메타데이터가 추가됐다. 어노테이션의 핵심 기능은 키와 값의 쌍을 지정해 자신의 항목을 초기화하는 데 기반하고 있다. 예를 들어 클래스의 출처를 추적하고 싶다면 다음과 같이 정의할 수 있다. - - @interface Source { - public String URL(); - public String mail(); - } - -그리고 이를 다음과 같이 적용한다. - - @Source(URL = "http://coders.com/", - mail = "support@coders.com") - public class MyClass extends HisClass ... - -스칼라에선 어노테이션을 적용하는 방식은 생성자 호출과 비슷한 모습을 갖고 있으며 자바 어노테이션을 인스턴스화 하기 위해선 이름을 지정한 인수를 사용해야 한다. - - @Source(URL = "http://coders.com/", - mail = "support@coders.com") - class MyScalaClass ... - -어노테이션에 단 하나의 항목(기본 값이 없는)만 있다면 이 구문은 상당히 장황하게 느껴지기 때문에, 자바에선 그 이름이 `value`로 지정됐다면 편의를 위해 생성자와 유사한 구문을 사용할 수도 있다. - - @interface SourceURL { - public String value(); - public String mail() default ""; - } - -그리고 이를 다음과 같이 적용한다. - - @SourceURL("http://coders.com/") - public class MyClass extends HisClass ... - -이 경우엔 스칼라도 같은 기능을 제공한다. - - @SourceURL("http://coders.com/") - class MyScalaClass ... - -`mail` 항목은 기본 값과 함께 설정됐기 때문에 이 항목에 반드시 값을 명시적으로 할당할 필요는 없다. 하지만 만약 해야만 한다면, 자바의 두 스타일을 함께 섞어서 사용할 순 없다. - - @SourceURL(value = "http://coders.com/", - mail = "support@coders.com") - public class MyClass extends HisClass ... - -스칼라에선 이를 사용하는 더 유연한 방법을 제공한다. - - @SourceURL("http://coders.com/", - mail = "support@coders.com") - class MyScalaClass ... - -윤창석, 이한욱 옮김 diff --git a/ko/tutorials/tour/_posts/2017-02-13-anonymous-function-syntax.md b/ko/tutorials/tour/_posts/2017-02-13-anonymous-function-syntax.md deleted file mode 100644 index ff4a5e991d..0000000000 --- a/ko/tutorials/tour/_posts/2017-02-13-anonymous-function-syntax.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -layout: tutorial -title: 익명 함수 구문 - -discourse: false - -tutorial: scala-tour -categories: tour -num: 6 -language: ko - -next-page: higher-order-functions -previous-page: mixin-class-composition ---- - -스칼라를 사용하면 비교적 간결한 구문을 통해 익명 함수를 정의할 수 있다. 다음 표현식은 정수의 지정 함수를 만들어준다. - - (x: Int) => x + 1 - -이는 다음의 익명 클래스 정의를 축약한 표현이다. - - new Function1[Int, Int] { - def apply(x: Int): Int = x + 1 - } - -마찬가지로 여러 파라미터의 함수를 정의하거나: - - (x: Int, y: Int) => "(" + x + ", " + y + ")" - -파라미터가 없는 함수를 정의할 수도 있다: - - () => { System.getProperty("user.dir") } - -매우 간결하게 함수 타입을 작성하는 방법도 있다. 다음은 위에서 정의한 세 함수의 타입이다. - - Int => Int - (Int, Int) => String - () => String - -이 구문은 다음 타입을 축약한 표현이다. - - Function1[Int, Int] - Function2[Int, Int, String] - Function0[String] - -윤창석, 이한욱 옮김 \ No newline at end of file diff --git a/ko/tutorials/tour/_posts/2017-02-13-automatic-closures.md b/ko/tutorials/tour/_posts/2017-02-13-automatic-closures.md deleted file mode 100644 index 7cb62db483..0000000000 --- a/ko/tutorials/tour/_posts/2017-02-13-automatic-closures.md +++ /dev/null @@ -1,70 +0,0 @@ ---- -layout: tutorial -title: 타입 의존 클로저의 자동 구성 - -discourse: false - -tutorial: scala-tour -categories: tour -num: 30 -language: ko - -next-page: annotations -previous-page: operators ---- - -스칼라에선 파라미터가 없는 함수의 이름을 메소드의 파라미터로 사용할 수 있다. 이런 메소드가 호출되면 파라미터가 없는 함수의 이름에 해당하는 실제 파라미터를 찾지 않고, 대신 해당 파라미터의 계산을 캡슐화한 무항 함수를 전달하게 된다(소위 말하는 *이름에 의한 호출* 연산). - -다음 코드는 이 방식을 사용하는 방법을 보여준다. - - 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 - } - } - -`whileLoop` 함수는 `cond`와 `body`라는 두 파라미터를 받는다. 이 함수가 적용될 때 실제 파라미터는 계산되지 않는다. 대신 `whileLoop`의 내부에서 이 정형 파라미터를 사용할 때마다 암시적으로 생성된 무항 함수로 처리한다. 따라서 `whileLoop` 메소드는 재귀 구현의 방식에 맞춰 자바와 같은 while 반복문을 구현한다. - -[중위/후위 연산자](operators.html)와 이 기법을 함께 사용해 좀 더 복잡한 명령문(보기 좋게 작성된)을 생성할 수 있다. - -다음은 반복문을 제거한 명령문 구현이다. - - 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) - } - -`loop` 함수는 단순히 반복문의 내용을 받아서 `LoopUnlessCond` 클래스의 인스턴스(반복문 내용에 해당하는 객체를 캡슐화한)를 반환한다. 해당 내용이 아직 계산되지 않았음을 유념하자. `LoopUnlessCond` 클래스는 *중위 연산자*로 사용할 수 있는 `unless`라는 메소드를 포함하고 있다. 이런 접근을 통해 상당히 자연스럽게 표현된 새로운 반복문을 완성하게 된다: `loop { < stats > } unless ( < cond > )`. - -다음은 `TargetTest2`를 실행한 출력 결과다. - - i = 10 - i = 9 - i = 8 - i = 7 - i = 6 - i = 5 - i = 4 - i = 3 - i = 2 - i = 1 - -윤창석, 이한욱 옮김 diff --git a/ko/tutorials/tour/_posts/2017-02-13-case-classes.md b/ko/tutorials/tour/_posts/2017-02-13-case-classes.md deleted file mode 100644 index 7420622200..0000000000 --- a/ko/tutorials/tour/_posts/2017-02-13-case-classes.md +++ /dev/null @@ -1,125 +0,0 @@ ---- -layout: tutorial -title: 케이스 클래스 - -discourse: false - -tutorial: scala-tour -categories: tour -num: 10 -language: ko - -next-page: pattern-matching -previous-page: currying ---- - -스칼라는 _케이스 클래스_ 개념을 지원한다. 케이스 클래스는 아래와 같은 특징을 가지는 일반 클래스이다. - -* 기본적으로 불변 -* [패턴 매칭](pattern-matching.html)을 통해 분해가능 -* 레퍼런스가 아닌 구조적인 동등성으로 비교됨 -* 초기화와 운영이 간결함 - -추상 상위 클래스 Notification과 세개의 특정 Notification 타입들(케이스 클래스 Email, SMS, VoiceRecording으로 구현됨)로 구성된 Notification타입 계층구조를 위한 예제가 하나 있다. - - 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 - -케이스클래스를 인스턴스화 하는 것은 쉽다 (new 키워드를 사용할 필요가 없음을 주목하자.) - - val emailFromJohn = Email("john.doe@mail.com", "Greetings From John!", "Hello World!") - -케이스 클래스의 생성자 파라미터들은 public 값으로 다뤄지며, 직접 접근이 가능하다. - - val title = emailFromJohn.title - println(title) // prints "Greetings From John!" - -여러분은 케이스 클래스의 필드를 직접 수정할 수 없다. (필드 앞에 var를 넣으면 가능하지만, 권장되지는 않는다.) - - emailFromJohn.title = "Goodbye From John!" // 이것은 컴파일시에 에러가 난다. 우리는 val인 필드에 다른 값을 할당할수 없으며, 모든 케이스 클래스 필드는 기본적으로 val이다. - -대신에, 당신은 copy메서드를 사용해서 복사본을 만들수 있다. 아래에서 보듯, 당신은 몇몇 필드를 대체할수 있다. - - val editedEmail = emailFromJohn.copy(title = "I am learning Scala!", body = "It's so cool!") - println(emailFromJohn) // prints "Email(john.doe@mail.com,Greetings From John!,Hello World!)" - println(editedEmail) // prints "Email(john.doe@mail.com,I am learning Scala,It's so cool!)" - -모든 케이스 클래스에 대해서 스칼라 컴파일러는 구조적 동등성을 구현한 equals 메서드와, toString 메서드를 생성한다. - - val firstSms = SMS("12345", "Hello!") - val secondSms = SMS("12345", "Hello!") - - if (firstSms == secondSms) { - println("They are equal!") - } - - println("SMS is: " + firstSms) - -위 코드는 아래과 같이 출력한다 - - They are equal! - SMS is: SMS(12345, Hello!) - -케이스 클래스를 통해, 데이터와 함께 동작하는 패턴매칭을 사용할수 있다. 어떤Notification 타입을 받느냐에 따라 다른 메시지를 출력하는 함수가 있다. - - 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)) - - // prints: - // 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 - -아래에 if 방어구문을 사용한 다른 예제가 있다. if 방어구문을 통해, 패턴이 일치하는 분기문은 방어구문안의 조건이 false를 리턴하면 실패한다. - - 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) // nothing special, delegate to our original showNotification function - } - } - - 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)) - - // prints: - // 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! - -스칼라 프로그래밍에 있어서, 보통 model/group 데이터에 케이스 클래스를 사용하도록 권장되는데, 당신이 더욱 표현적이거나 유지보수가능한 코드를 작성할 때 도움이 된다. - -* 불변성은 당신이 언제 어디서 그것들이 수정되는지 신경쓸 필요 없게 만들어 준다. -* 값을 통한 비교는 여러분이 인스턴스들을 원시 값들인 것처럼 비교할수 있게 만들어 준다 - 클래스의 인스턴스들이 값 또는 참조를 통해 비교되는지와 같은 불확실성을 제거 -* 패턴매칭은 로직의 분기를 심플하게 만들어주며, 결국 적은 버그와 가독성 높은 코드로 이어진다. - -고광현 옮김 \ No newline at end of file diff --git a/ko/tutorials/tour/_posts/2017-02-13-classes.md b/ko/tutorials/tour/_posts/2017-02-13-classes.md deleted file mode 100644 index 5508390f90..0000000000 --- a/ko/tutorials/tour/_posts/2017-02-13-classes.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -layout: tutorial -title: 클래스 - -discourse: false - -tutorial: scala-tour -categories: tour -num: 3 -language: ko - -next-page: traits -previous-page: unified-types ---- - -스칼라의 클래스는 런타임에 많은 객체로 인스턴스화 될 수 있는 정적 템플릿이다. -아래는 'Point' 클래스의 정의이다. - - class Point(xc: Int, yc: Int) { - var x: Int = xc - var y: Int = yc - def move(dx: Int, dy: Int) { - x = x + dx - y = y + dy - } - override def toString(): String = "(" + x + ", " + y + ")"; - } - -이 클래스는 변수 'x' 와 'y' 그리고 두 메서드 `move` 와 `toString` 을 정의한다. `move`는 두 개의 정수를 인자로 받지만 값을 반환하지는 않는다 (암시적 반환 타입인 `Unit` 은 Java와 같은 언어의 `void` 에 해당한다). 반면 `toString`은 아무 파라미터도 입력받지 않지만 `String` 값을 반환한다. `toString`은 기정의된 `toString` 메서드를 오버라이드 하기 때문에 `override` 플래그로 표시되어야 한다. - -스칼라의 클래스들은 생성자의 인자로 파라미터화 된다. 위의 코드는 두 개의 생성자 인자 `xc`와 `yc`를 정의한다. 이 두 인자는 클래스 전체에서 접근 가능하다. 예제에서 이들은 변수 `x`와 `y`를 초기화하는 데에 사용된다. - -클래스들은 아래의 예제가 보여주는 것처럼 새로운 기본형으로 초기화된다. - - object Classes { - def main(args: Array[String]) { - val pt = new Point(1, 2) - println(pt) - pt.move(10, 10) - println(pt) - } - } - -이 프로그램은 `main` 메서드를 가지고 있는 최상위 싱글톤의 형태로 실행가능한 어플리케이션 클래스를 정의한다. `main` 메서드는 새로운 `Point`를 생성하고 변수 `pt`에 저장한다. `val` 키워드로 정의된 값은 변경을 허용하지 않는다는 점에서 `var` 키워드로 정의된 값(`Point` 클래스 참)과는 다르다는 점에 주의하자. 즉, 이 값은 상수이다. - -이 프로그램의 결과는 아래와 같다: - - (1, 2) - (11, 12) - -윤창석, 이한욱 옮김 diff --git a/ko/tutorials/tour/_posts/2017-02-13-compound-types.md b/ko/tutorials/tour/_posts/2017-02-13-compound-types.md deleted file mode 100644 index afc1fb2ca5..0000000000 --- a/ko/tutorials/tour/_posts/2017-02-13-compound-types.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -layout: tutorial -title: 합성 타입 - -discourse: false - -tutorial: scala-tour -categories: tour -num: 23 -language: ko - -next-page: explicitly-typed-self-references -previous-page: abstract-types ---- - -때론 객체의 타입을 여러 다른 타입의 서브타입으로 표현해야 할 때가 있다. 스칼라에선 *합성 타입(Compound Types)*으로 표현될 수 있는데, 이는 객체 타입들의 교차점을 의미한다. - -`Cloneable` 과 `Resetable`이라는 두 트레잇을 생각해보자: - - trait Cloneable extends java.lang.Cloneable { - override def clone(): Cloneable = { - super.clone().asInstanceOf[Cloneable] - } - } - trait Resetable { - def reset: Unit - } - -그리고 어떤 객체를 파라미터로 받아 복제한 뒤 원래의 객체를 초기화하는 `cloneAndReset` 이라는 함수를 작성하려고 한다: - - def cloneAndReset(obj: ?): Cloneable = { - val cloned = obj.clone() - obj.reset - cloned - } - -여기에서 파라미터 `obj`의 타입이 무엇인가 하는 의문을 가질 수 있다. 만약 타입이 `Clonable`이라면 이 객체는 복제될(`Cloned`) 수 있지만 리셋될(`reset`) 수는 없다. 반면 타입이 `Resetable`이라면 이 객체를 리셋할(`reset`) 수는 있지만 `clone` 기능을 사용할 수는 없다. 이러한 상황에서 타입 캐스팅을 피하기 위해 두개의 타입 `Clonable`과 `Resetable` 모두를 지정해 줄 수 있다. 스칼라의 이러한 합성타입은 다음과 같이 쓰인다. : `Clonable with Resetable` - -위의 함수를 다시 작성하면 다음과 같다. - - def cloneAndReset(obj: Cloneable with Resetable): Cloneable = { - //... - } - -합성 타입은 여러 객체 타입으로 구성될 수 있고, 단일 리파인먼트를 가짐으로써 객체 멤버의 시그니처의 범위를 좁힐 수도 있다. 일반적인 형태는 `A with B with C ... { 리파인먼트 }`이다. - -리파인먼트 사용 예제는 [추상 타입](abstract-types.html) 에 있다. - -윤창석, 이한욱 옮김, 고광현 수정 diff --git a/ko/tutorials/tour/_posts/2017-02-13-currying.md b/ko/tutorials/tour/_posts/2017-02-13-currying.md deleted file mode 100644 index 291db39865..0000000000 --- a/ko/tutorials/tour/_posts/2017-02-13-currying.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -layout: tutorial -title: 커링 - -discourse: false - -tutorial: scala-tour -categories: tour -num: 9 -language: ko - -next-page: case-classes -previous-page: nested-functions ---- - -메소드에는 파라미터 목록을 여럿 정의할 수 있다. 파라미터 목록의 수 보다 적은 파라미터로 메소드가 호출되면, 해당 함수는 누락된 파라미터 목록을 인수로 받는 새로운 함수를 만든다. - -다음의 예제를 살펴보자. - - 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))) - } - -_주의: `modN` 메소드는 두 번의 `filter` 호출에서 부분적으로 사용됐다. 즉, 오직 첫 번째 인수만이 실제로 사용됐다. `modN(2)`라는 구문은 `Int => Boolean` 타입의 함수를 만들기 때문에 `filter` 함수의 두 번째 인수로 사용할 수 있게 된다._ - -다음은 위 프로그램의 결과다. - - List(2,4,6,8) - List(3,6) - -윤창석, 이한욱 옮김 \ No newline at end of file diff --git a/ko/tutorials/tour/_posts/2017-02-13-default-parameter-values.md b/ko/tutorials/tour/_posts/2017-02-13-default-parameter-values.md deleted file mode 100644 index 2afee5db01..0000000000 --- a/ko/tutorials/tour/_posts/2017-02-13-default-parameter-values.md +++ /dev/null @@ -1,66 +0,0 @@ ---- -layout: tutorial -title: 기본 파라미터 값 - -discourse: false - -tutorial: scala-tour -categories: tour -num: 32 -language: ko - -next-page: named-parameters -previous-page: annotations ---- - -스칼라는 파라미터에 기본 값을 부여해서 호출자가 해당 파라미터를 생략할 수 있는 편리함을 제공한다. - -자바에선 거대한 메소드의 특정 파라미터에 기본 값을 제공하기 위해 수 많은 메소드를 오버로드하는 상황을 어렵지 않게 찾을 수 있다. 이는 특히 생성자의 경우에 그러하다. - - public class HashMap { - public HashMap(Map m); - /** 기본 크기가 (16)이고 로드 팩터가 (0.75)인 새로운 HashMap의 생성 */ - public HashMap(); - /** 기본 로드 팩터가 (0.75)인 새로운 HashMap의 생성 */ - public HashMap(int initialCapacity); - public HashMap(int initialCapacity, float loadFactor); - } - -여기선 실제론 다른 맵을 받는 생성자와 크기와 로드 팩터를 받는 생성자, 단지 이 두 생성자가 있을 뿐이다. 세 번째와 네 번째 생성자는 HashMap의 사용자가 대부분의 경우에서 아마도 유용하게 사용할 기본 값으로 로드 팩터와 크기를 설정해 인스턴스를 생성할 수 있도록 해준다. - -더 큰 문제는 기본으로 사용되는 값이 자바독과 코드 *모두*에 존재한다는 점이다. 이를 최신으로 유지하는 일은 쉽게 잊어버리게 된다. 이런 문제를 피하기 위해선 자바독에 해당 값이 표시될 퍼블릭 상수를 추가하는 접근을 주로 사용한다. - - public class HashMap { - public static final int DEFAULT_CAPACITY = 16; - public static final float DEFAULT_LOAD_FACTOR = 0.75; - - public HashMap(Map m); - /** 기본 크기가 (16)이고 로드 팩터가 (0.75)인 HashMap을 생성 */ - public HashMap(); - /** 기본 로드 팩터가 (0.75)인 새로운 HashMap의 생성 */ - public HashMap(int initialCapacity); - public HashMap(int initialCapacity, float loadFactor); - } - -이 때문에 우리가 계속 반복하는 상황은 줄지만, 표현력은 더욱 줄어든다. - -스칼라는 이에 관한 직접적인 지원을 추가했다. - - class HashMap[K,V](initialCapacity:Int = 16, loadFactor:Float = 0.75) { - } - - // 기본 값을 사용 - val m1 = new HashMap[String,Int] - - // 초기 크기는 20, 로드 팩터는 기본 값 - val m2= new HashMap[String,Int](20) - - // 둘 모드를 오버라이드 - val m3 = new HashMap[String,Int](20,0.8) - - // 이름을 지정한 인수를 통해 로드 팩터만을 오버라이드 - val m4 = new HashMap[String,Int](loadFactor = 0.8) - -*모든* 기본 값에 [이름을 지정한 파라미터]({{ site.baseurl }}/tutorials/tour/named-parameters.html)를 활용할 수 있음을 기억하자. - -윤창석, 이한욱 옮김 \ No newline at end of file diff --git a/ko/tutorials/tour/_posts/2017-02-13-explicitly-typed-self-references.md b/ko/tutorials/tour/_posts/2017-02-13-explicitly-typed-self-references.md deleted file mode 100644 index 439b728099..0000000000 --- a/ko/tutorials/tour/_posts/2017-02-13-explicitly-typed-self-references.md +++ /dev/null @@ -1,104 +0,0 @@ ---- -layout: tutorial -title: 명시적으로 타입이 지정된 자기 참조 - -discourse: false - -tutorial: scala-tour -categories: tour -num: 24 -language: ko - -next-page: implicit-parameters -previous-page: compound-types ---- - -확장 가능한 소프트웨어를 개발할 땐 `this` 값의 타입을 명시적으로 선언하는 편이 편리할 수도 있다. 이를 이해하기 위해 스칼라로 작성된 작고 확장 가능한 그래프 데이터 구조를 만들어 보기로 하자. - -다음은 그래프가 무엇인지 설명해주는 정의이다. - - 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 - } - -그래프는 노드와 엣지의 리스트로 구성되며, 노드와 엣지의 타입은 모두 추상적으로 남겨뒀다. [추상 타입](abstract-types.html)을 사용해서 트레잇 Graph를 구현할 수 있도록 했고, 이를 통해 노드와 엣지에 해당하는 자신의 콘크리트 클래스를 만들 수 있다. 뿐만 아니라 `addNode`라는 메소드는 그래프에 새로운 노드를 추가해준다. 메소드 `connectWith`를 사용해 노드를 연결한다. - -다음 클래스는 클래스 `Graph`를 구현하는 한 예다. - - 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 - } - } - -클래스 `DirectedGraph`는 부분적 구현을 제공해서 `Graph` 클래스를 특수화한다. `DirectedGraph`가 더욱 확장 가능하길 원하기 때문에 그 일부만을 구현했다. 그 결과, 이 클래스의 구현 세부 사항은 모두가 확정되지 않은 상태로 열려있고, 엣지와 노드 타입은 추상적으로 처리됐다. 반면에, 클래스 `DirectedGraph`는 클래스 `EdgeImpl`과의 결합을 강화함으로써 엣지 타입의 구현에 관한 내용 일부를 추가적으로 표현하고 있다. 또한 클래스 `EdgeImpl`과 `NodeImpl`을 통해 엣지와 노드의 구현 일부를 먼저 정의했다. 새로운 노드와 엣지 객체를 부분 클래스 구현 안에서 생성해야만 하기 때문에 팩토리 메소드 `newNode`와 `newEdge`도 추가해야 한다. 메소드 `addNode`와 `connectWith`는 이 팩토리 메소드를 사용해 정의했다. 메소드 `connectWith`의 구현을 좀 더 자세히 살펴보면, 엣지를 생성하기 위해선 반드시 자기 참조 `this`를 팩토리 메소드 `newEdge`로 전달해야 함을 알 수 있다. 하지만 `this`에는 타입 `NodeImpl`이 할당되고, 이는 팩토리 메소드에서 요구하는 타입 `Node`와 호환되지 않는다. 결국, 위의 프로그램은 제대로 만들어지지 않았으며, 스칼라 컴파일러는 오류 메시지를 표시한다. - -스칼라에선 자기 참조 `this`에 다른 타입을 명시적으로 부여함으로써 클래스를 다른 타입(향후에 구현될)과 묶을 수 있다. 이 기법을 사용하면 위의 코드를 올바르게 고칠 수 있다. 명시적 자기 타입은 클래스 `DirectedGraph`의 내부에서 지정된다. - -다음은 고쳐진 프로그램이다. - - abstract class DirectedGraph extends Graph { - ... - class NodeImpl extends NodeIntf { - self: Node => - def connectWith(node: Node): Edge = { - val edge = newEdge(this, node) // now legal - edges = edge :: edges - edge - } - } - ... - } - -새롭게 정의한 클래스 `NodeImpl`에선 `this`의 타입이 `Node`다. 타입 `Node`가 추상적이기 때문에 `NodeImpl`이 정말 `Node`의 서브타입인지 알 수 없으며, 스칼라의 타입 시스템은 이 클래스의 인스턴스화를 허용하지 않는다. 하지만 인스턴스화를 위해선 언젠간 `NodeImpl`(의 서브클래스)이 타입 `Node`의 서브타입을 지정해주도록 명시적 타입 어노테이션을 표시했다. - -다음은 모든 추상 클래스 멤버가 콘크리트하게 변경된, `DirectedGraph`의 콘크리트한 특수화다. - - 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) - } - -이젠 `NodeImpl`에서 `Node`(단순히 `NodeImpl`의 또 다른 이름일 뿐이다)의 서브타입을 지정했기 때문에, 이 클래스에선 `NodeImpl`을 인스턴스화 할 수 있음을 기억하자. - -다음은 클래스 `ConcreteDirectedGraph`를 사용하는 예다. - - 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) - } - -윤창석, 이한욱 옮김 \ No newline at end of file diff --git a/ko/tutorials/tour/_posts/2017-02-13-extractor-objects.md b/ko/tutorials/tour/_posts/2017-02-13-extractor-objects.md deleted file mode 100644 index 259bcf8684..0000000000 --- a/ko/tutorials/tour/_posts/2017-02-13-extractor-objects.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -layout: tutorial -title: 추출자 오브젝트 - -discourse: false - -tutorial: scala-tour -categories: tour -num: 15 -language: ko - -next-page: sequence-comprehensions -previous-page: regular-expression-patterns ---- - -스칼라에선 캐이스 클래스와 상관 없이 패턴을 정의할 수 있다. 이런 측면에서 추출자라 불리는 unapply라는 이름의 메소드를 정의한다. 예를 들어, 다음의 코드는 추출자 오브젝트 Twice를 정의한다. - - 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) } // prints 21 - } - -여기선 두 가지 구문적 컨벤션을 사용했다. - -패턴 `case Twice(n)`는 `Twice.unapply`를 호출하는데, 이는 짝수와의 매칭에 사용된다. `unapply`의 반환 값은 인수가 매칭됐는지 여부와 다른 하위 값을 더 매칭해 나가야 할지를 알려준다. 여기선 하위 값이 `z/2`이다. - -`apply` 메소드는 패턴 매칭에 필수 요소가 아니며, 단지 생성자를 흉내내기 위해 사용된다. `val x = Twice(21)`는 `val x = Twice.apply(21)`로 확장된다. - -`unapply`의 반환 값은 반드시 다음 중에서 선택해야 한다. - -* 단순한 테스트라면 `Boolean`을 반환한다. `case even()`이 한 예다. -* 타입 T의 단일 하위 값을 반환한다면, `Option[T]`를 반환한다. -* 여러 하위 값 `T1,...,Tn`를 반환하고 싶다면, 이를 `Option[(T1,...,Tn)]`과 같이 튜플로 묶어준다. - -때론 하위 값의 개수가 미리 고정돼 시퀀스를 반환하고 싶을 때도 있다. 이런 이유로 `unapplySeq`를 통해 패턴을 정의할 수 있다. 마지막 하위 값의 타입 `Tn`은 반드시 `Seq[S]`여야 한다. 이 기법은 `case List(x1, ..., xn)`과 같은 패턴에 사용된다. - -추출자는 코드의 유지 관리성을 향상시켜준다. 더욱 자세한 내용은 Emir, Odersky, Willians의 ["패턴을 통한 오브젝트의 매칭"](https://infoscience.epfl.ch/record/98468/files/MatchingObjectsWithPatterns-TR.pdf)(2007년 1월) 4장을 읽어보도록 하자. - -윤창석, 이한욱 옮김 diff --git a/ko/tutorials/tour/_posts/2017-02-13-generic-classes.md b/ko/tutorials/tour/_posts/2017-02-13-generic-classes.md deleted file mode 100644 index a4f96883ad..0000000000 --- a/ko/tutorials/tour/_posts/2017-02-13-generic-classes.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -layout: tutorial -title: 제네릭 클래스 - -discourse: false - -tutorial: scala-tour -categories: tour -num: 17 -language: ko - -next-page: variances -previous-page: sequence-comprehensions ---- - -자바 5(다른 이름은 [JDK 1.5](http://java.sun.com/j2se/1.5/))와 같이, 스칼라는 타입으로 파라미터화된 클래스의 빌트인 지원을 제공한다. 이런 제네릭 클래스는 특히 컬렉션 클래스의 개발에 유용하다. 이에 관한 예제를 살펴보자. - - 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 } - } - -클래스 `Stack`은 임의의 타입 `T`를 항목의 타입으로 하는 명령형(변경 가능한) 스택이다. 타입 파라미터는 올바른 항목(타입 `T` 인)만을 스택에 푸시하도록 강제한다. 마찬가지로 타입 파라미터를 사용해서 메소드 `top`이 항상 지정된 타입만을 반환하도록 할 수 있다. - -다음은 스택을 사용하는 예다. - - object GenericsTest extends App { - val stack = new Stack[Int] - stack.push(1) - stack.push('a') - println(stack.top) - stack.pop() - println(stack.top) - } - -이 프로그램의 결과는 다음과 같다. - - 97 - 1 - -_주의: 제네릭 클래스의 서브타입은 *불가변*이다. 즉, 캐릭터 타입의 스택인 `Stack[Char]`를 정수형 스택인 `Stack[Int]`처럼 사용할 수는 없다. 실제로 캐릭터 스택에는 정수가 들어가기 때문에 이런 제약은 이상하게 보일 수도 있다. 결론적으로 `S = T`일 때만 `Stack[T]`가 `Stack[S]`의 서브타입일 수 있으며, 반대의 관계도 마찬가지로 성립해야 한다. 이런 특징이 상당히 큰 제약일 수 있기 때문에, 스칼라는 제네릭 타입의 서브타입을 지정하는 행위를 제어하기 위해 [타입 파라미터 어노테이션 방법](variances.html)을 제공한다._ - -윤창석, 이한욱 옮김 \ No newline at end of file diff --git a/ko/tutorials/tour/_posts/2017-02-13-higher-order-functions.md b/ko/tutorials/tour/_posts/2017-02-13-higher-order-functions.md deleted file mode 100644 index 219fc26e72..0000000000 --- a/ko/tutorials/tour/_posts/2017-02-13-higher-order-functions.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -layout: tutorial -title: 고차 함수 - -discourse: false - -tutorial: scala-tour -categories: tour -num: 7 -language: ko - -next-page: nested-functions -previous-page: anonymous-function-syntax ---- - -스칼라는 고차 함수의 정의를 허용한다. 이런 함수는 _다른 함수를 파라미터로 받거나_, 수행의 _결과가 함수다_. 다음과 같은 함수 `apply`는 다른 함수 `f`와 값 `v`를 받아서 함수 `f`를 `v`에 적용한다. - - def apply(f: Int => String, v: Int) = f(v) - -_주의: 문맥적으로 함수가 필요하다면, 메소드는 자동으로 이에 맞게 강제된다._ - -다음은 또 다른 예제다. - - 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)) - } - -실행 결과는 다음과 같다. - - [7] - -이 예제에서 메소드 `decorator.layout`은 메소드 `apply`에서 요구하는 바와 같이 타입 `Int => String`의 값으로 자동 강제된다. 메소드 `decorator.layout`이 _다형성 메소드_(즉, 자신의 서명 타입 중 일부를 추상화하는)이고, 스칼라 컴파일러는 가장 적합한 메소드 타입을 인스턴스화 해야만 한다는 점을 명심하자. - -윤창석, 이한욱 옮김 \ No newline at end of file diff --git a/ko/tutorials/tour/_posts/2017-02-13-implicit-conversions.md b/ko/tutorials/tour/_posts/2017-02-13-implicit-conversions.md deleted file mode 100644 index d3c4c2d198..0000000000 --- a/ko/tutorials/tour/_posts/2017-02-13-implicit-conversions.md +++ /dev/null @@ -1,62 +0,0 @@ ---- -layout: tutorial -title: 암시적 변환 - -discourse: false - -tutorial: scala-tour -categories: tour -num: 26 -language: ko - -next-page: polymorphic-methods -previous-page: implicit-parameters ---- - -타입 `S`로부터 타입 `T`로의 암시적 변환는 함수 타입 `S => T`의 암시적 값이나 해당 타입으로 변환 가능한 암시적 메소드로 정의된다. - -암시적 변환은 두 가지 상황에 적용된다. - -* 표현식 `e`의 타입이 `S`이고, `S`는 표현식의 기대 타입 `T`를 따르지 않을 때. -* `e`의 타입이 `T`인 `e.m`를 선택한 상황에서, 선택자 `m`이 `T`의 멤버가 아닐 때. - - -첫 번째 경우에서 변환 `c`가 `e`에 적용되며, 결과 타입이 `T`를 따르는지 탐색한다. -두 번째 경우에선 변환 `c`가 `e`에 적용되며, 결과가 `m`이라는 이름의 멤버를 포함하고 있는지 탐색한다. - -타입이 `List[Int]`인 두 리스트 xs와 ys의 아래 연산은 허용된다: - - xs <= ys - -아래에 정의된 암시적 메소드 `list2ordered`와 `int2ordered`가 범위 안에 있다고 가정한다. - - 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] { /* .. */ } - -암시적으로 임포트되는 오브젝트 `scala.Predef`는 미리 정의된 여러 타입(예: `Pair`)과 메소드(예: `assert`)뿐만 아니라 여러 뷰도 함께 선언한다. - -예를들면, `java.lang.Integer`를 기대하는 자바 메서드를 호출할때, `scala.Int`를 대신 넘겨도 무방하다. 그 이유는 Predef가 아래 암시적 변환들을 포함하기 때문이다. - -```tut -import scala.language.implicitConversions - -implicit def int2Integer(x: Int) = - java.lang.Integer.valueOf(x) -``` - -암시적 변환이 무분별하게 사용될 경우 잠재적인 위험을 가질 수 있기 때문에, 컴파일러는 암시적 변환의 선언을 컴파일할때 경고한다. - -To turn off the warnings take either of these actions: -경고를 끄기 위해서는 아래 중 하나를 선택해야 한다. - -* `scala.language.implicitConversions` 를 암시적 변환의 선언이 있는 범위로 import -* `-language:implicitConversions` 옵션으로 컴파일러 실행 - -변환이 컴팡일러에 의해 적용될때 경고가 발생하지 않는다. - - -윤창석, 이한욱 옮김, 고광현 업데이트 diff --git a/ko/tutorials/tour/_posts/2017-02-13-implicit-parameters.md b/ko/tutorials/tour/_posts/2017-02-13-implicit-parameters.md deleted file mode 100644 index f18debd1e3..0000000000 --- a/ko/tutorials/tour/_posts/2017-02-13-implicit-parameters.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -layout: tutorial -title: 암시적 파라미터 - -discourse: false - -tutorial: scala-tour -categories: tour -num: 25 -language: ko - -next-page: implicit-conversions -previous-page: explicitly-typed-self-references ---- - -_암시적 파라미터_ 를 갖는 메서드 역시 다른 일반적인 메서드와 마찬가지로 인수를 적용할 수 있다. 이런 경우 implicit 키워드는 아무런 영향을 미치지 않는다. 하지만, 이 경우에 암시적 파라미터에 주는 인수를 생략한다면, 그 생략된 인수는 자동적으로 제공될 것이다. - -실제로 암시적 파라미터가 넘겨받을 수 있는 인수의 종류는 두 가지로 나눌 수 있다: - -* 첫째, 메서드가 호출되는 시점에서 prefix 없이 접근할 수 있고 암시적 정의나 암시적 파라미터로 표시된 모든 식별자 x -* 둘째, 암시적(implicit)이라고 표시된 암시적 파라미터의 타입과 관련된 모듈의 모든 멤버 - -아래 예제에서는 모노이드의 `add`와 `unit`메서드를 이용해서 리스트 항목들의 합을 구하는 `sum` 메서드를 정의한다. -암시적 값은 최상위 레벨이 될 수 없고 템플릿의 멤버여야만 한다는 점에 주의하자. - - /** 이 예제는 어떻게 암묵적인 파라미터들이 동작하는지 보여주기 위해, 추상적인 수학으로부터 나온 구조를 사용한다. 하위 그룹은 관련된 연산(add를 말함/한쌍의 A를 합치고 또다른 A를 리턴)을 가진 집합 A에 대한 수학적인 구조이다. */ - abstract class SemiGroup[A] { - def add(x: A, y: A): A - } - /** monoid는 A의 구분된 요소(unit을 말함/A의 어떤 다른 요소와 합산될때, 다른 요소를 다시 리턴하는)를 가진 하위 그룹이다 */ - abstract class Monoid[A] extends SemiGroup[A] { - def unit: A - } - object ImplicitTest extends App { - /** 암묵적 파라미터들의 동작을 보여주기 위해서, 우리는 먼저 문자열과 숫자에 대한 monoid를 정의했다. implicit 키워드는 해당하는 object가 암묵적으로 어떤 함수의 implicit으로 표시된 파라미터에 이 scope안에서 사용될수 있음을 나타낸다. */ - 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 - } - /** 이 메서드는 List[A]를 가지며 A를 리턴한다. 그리고 리턴된 A는 전체 리스트에 걸쳐, 지속적으로 monoid 연산이 적용되 합쳐진 값을 이야기 한다. 파라미터 m을 암시적으로 만든다는 것은, 우리가 반드시 호출 시점에 xs파라미터를 제공해야한다는 것을 의미하고, 그 이후에 우리가 List[A]를 가진다면, A가 실제로 어떤 타입인지, 어떤타입의 Monoid[A]가 필요한지도 알게 된다. 그 후, 현재 scope에서 어떤 val 또는 object가 해당 타입을 가지고 있는지 암묵적으로 알게 되며, 명시적으로 표현할 필요 없이 그것을 사용할 수 있다. */ - def sum[A](xs: List[A])(implicit m: Monoid[A]): A = - if (xs.isEmpty) m.unit - else m.add(xs.head, sum(xs.tail)) - - /** 아래코드에서 우리는 각각 하나의 파라미터로 sum을 두번 호출한다. sum의 두번째 파라미터인 m이 암시적이기 때문에 그 값은 현재 scope에서 각 케이스마다 요구되는 monoid의 타입을 기준으로 탐색된다. 두 표현은 완전히 계산될 수 있음을 의미한다. */ - println(sum(List(1, 2, 3))) // uses IntMonoid implicitly - println(sum(List("a", "b", "c"))) // uses StringMonoid implicitly - } - -아래는 이 스칼라 프로그램의 결과이다. - - 6 - abc - -윤창석, 이한욱, 고광현 옮김 diff --git a/ko/tutorials/tour/_posts/2017-02-13-inner-classes.md b/ko/tutorials/tour/_posts/2017-02-13-inner-classes.md deleted file mode 100644 index 4cd0c3adc9..0000000000 --- a/ko/tutorials/tour/_posts/2017-02-13-inner-classes.md +++ /dev/null @@ -1,91 +0,0 @@ ---- -layout: tutorial -title: 내부 클래스 - -discourse: false - -tutorial: scala-tour -categories: tour -num: 21 -language: ko - -next-page: abstract-types -previous-page: lower-type-bounds ---- - -스칼라의 클래스는 다른 클래스를 멤버로 가질 수 있다. 자바와 같은 언어의 내부 클래스는 자신을 감싸고 있는 클래스의 멤버인 반면에, 스칼라에선 내부 클래스가 외부 객체의 경계 안에 있다. 이런 차이점을 분명히 하기 위해 그래프 데이터타입의 구현을 간단히 그려보자. - - 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 - } - } - -이 프로그램에선 노드의 리스트로 그래프를 나타냈다. 노드는 내부 클래스 `Node`의 객체다. 각 노드는 리스트 `connectedNodes`에 저장되는 이웃의 목록을 갖고 있다. 이제 몇몇 노드를 선택하고 이에 연결된 노드를 추가하면서 점진적으로 그래프를 구축할 수 있다. - - 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) - } - -정의된 여러 엔티티의 타입이 무엇인지 명시적으로 알려주는 타입 정보를 사용해 위의 예제를 확장해보자. - - 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) - } - -이 코드는 외부 인스턴스(이 예제의 객체 `g`)를 접두어로 지정해 노드 타입을 분명히 나타내고 있다. 두 그래프가 있는 상황에서, 스칼라의 타입 시스템은 한 그래프에 정의된 노드를 다른 그래프에서도 정의해 공유하는 상황을 허용하지 않는다. 이는 다른 그래프의 노드는 다른 타입을 갖기 때문이다. -다음은 잘못된 프로그램이다. - - object IllegalGraphTest extends App { - val g: Graph = new Graph - val n1: g.Node = g.newNode - val n2: g.Node = g.newNode - n1.connectTo(n2) // legal - val h: Graph = new Graph - val n3: h.Node = h.newNode - n1.connectTo(n3) // illegal! - } - -자바에선 이 예제 프로그램의 마지막 줄이 올바른 표현임을 상기하자. 자바는 두 그래프의 노드에 `Graph.Node`라는 동일한 타입을 할당한다. 즉, `Node`에는 클래스 `Graph`가 접두어로 붙는다. 스칼라에서도 이런 타입을 표현할 수 있으며, 이를 `Graph#Node`로 나타낸다. 서로 다른 그래프 간에 노드를 연결할 수 있길 원한다면 초기 그래프 구현의 정의를 다음과 같이 변경해야 한다. - - 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 - } - } - -> 이 프로그램에선 하나의 노드를 서로 다른 두 그래프에 추가할 수 없음에 주의하자. 이 제약도 함께 제거하기 위해선 변수 nodes의 타입을 `Graph#Node`로 바꿔야 한다. - -윤창석, 이한욱 옮김 \ No newline at end of file diff --git a/ko/tutorials/tour/_posts/2017-02-13-local-type-inference.md b/ko/tutorials/tour/_posts/2017-02-13-local-type-inference.md deleted file mode 100644 index 146d8cc592..0000000000 --- a/ko/tutorials/tour/_posts/2017-02-13-local-type-inference.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -layout: tutorial -title: 로컬 타입 추론 - -discourse: false - -tutorial: scala-tour -categories: tour -num: 28 -language: ko - -next-page: operators -previous-page: polymorphic-methods ---- - -스칼라는 프로그래머가 특정한 타입 어노테이션을 생략할 수 있도록 해주는 빌트인 타입 추론 기능을 갖추고 있다. 예를 들어, 스칼라에선 컴파일러가 변수의 초기화 표현식으로부터 타입을 추론할 수 있기 때문에 변수의 타입을 지정할 필요가 없을 때가 많다. 또한 메소드의 반환 타입은 본문의 타입과 일치하기 때문에 이 반환 타입 역시 컴파일러가 추론할 수 있고, 주로 생략된다. - -다음 예제를 살펴보자. - - object InferenceTest1 extends App { - val x = 1 + 2 * 3 // x의 타입은 Int다. - val y = x.toString() // y의 타입은 String이다. - def succ(x: Int) = x + 1 // 메소드 succ는 Int 값을 반환한다. - } - -재귀 메소드의 경우는 컴파일러가 결과 타입을 추론할 수 없다. 다음 프로그램에선 이와 같은 이유로 컴파일러가 추론할 수 없다. - - object InferenceTest2 { - def fac(n: Int) = if (n == 0) 1 else n * fac(n - 1) - } - -[다형성 메소드](polymorphic-methods.html)를 호출하거나 [제네릭 클래스](generic-classes.html)를 인스턴스화 할 때 반드시 타입을 지정할 의무는 없다. 스칼라 컴파일러는 컨텍스트와 실제 메소드/생성자 파라미터로부터 생략된 타입 파라미터를 추론한다. - -다음 예제는 이를 나타낸 예제다. - - case class MyPair[A, B](x: A, y: B); - object InferenceTest3 extends App { - def id[T](x: T) = x - val p = MyPair(1, "scala") // 타입: MyPair[Int, String] - val q = id(1) // 타입: Int - } - -이 프로그램의 마지막 두 줄은 추론된 타입을 명시적으로 나타낸 다음 코드와 동일하다. - - val x: MyPair[Int, String] = MyPair[Int, String](1, "scala") - val y: Int = id[Int](1) - -다음 프로그램에서 나타나는 문제와 같이, 일부 상황에선 스칼라의 타입 추론 기능에 의존하는 것은 상당히 위험할 수 있다. - - object InferenceTest4 { - var obj = null - obj = new Object() - } - -이 프로그램은 변수 `obj`의 타입 추론이 `Null`이기 때문에 컴파일되지 않는다. 해당 타입의 유일한 값이 `null`이기 때문에 이 변수는 다른 값을 나타낼 수 없다. - -윤창석, 이한욱 옮김 \ No newline at end of file diff --git a/ko/tutorials/tour/_posts/2017-02-13-lower-type-bounds.md b/ko/tutorials/tour/_posts/2017-02-13-lower-type-bounds.md deleted file mode 100644 index 64180492f2..0000000000 --- a/ko/tutorials/tour/_posts/2017-02-13-lower-type-bounds.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -layout: tutorial -title: 하위 타입 경계 - -discourse: false - -tutorial: scala-tour -categories: tour -num: 20 -language: ko - -next-page: inner-classes -previous-page: upper-type-bounds ---- - -[상위 타입 경계](upper-type-bounds.html)가 특정 타입의 서브타입으로 타입을 제한한다면, *하위 타입 경계*는 대상 타입을 다른 타입의 슈퍼타입으로 선언한다. `T>:A`는 타입 파라미터 `T`나 추상 타입 `T`가 타입 `A`의 슈퍼타입임을 나타낸다. - -상위 타입 경계를 유용하게 활용할 수 있는 예제를 살펴보자. - - 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) - } - -이 프로그램은 앞쪽에 항목을 추가하는 동작을 제공하는 링크드 리스트를 구현하고 있다. 안타깝게도 클래스 `ListNode`의 타입 파라미터로 사용된 타입은 불변자이고, 타입 `ListNode[String]`은 `타입 List[Object]`의 서브타입이 아니다. 가변성 어노테이션의 도움을 받아서 이런 서브타입 관계의 시맨틱을 표현할 수 있다. - - case class ListNode[+T](h: T, t: ListNode[T]) { ... } - -하지만 순가변성 어노테이션은 반드시 순가변 위치의 타입 변수로 사용돼야만 하기 때문에, 이 프로그램은 컴파일되지 않는다. 타입 변수 `T`가 메소드 `prepend`의 파라미터 타입으로 쓰였기 때문에 이 규칙이 깨지게 된다. 그렇지만 *하위 타입 경계*의 도움을 받는다면 `T`가 순가변 위치에만 나타나도록 `prepend` 메소드를 구현할 수 있다. - -다음이 이를 적용한 코드다. - - 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) - } - -_주의:_ 새로운 `prepend` 메소드에선 타입의 제약이 조금 줄어들게 된다. 예를 들어 이미 만들어진 리스트에 슈퍼타입의 객체를 집어 넣을 수도 있다. 그 결과로 만들어지는 리스트는 이 슈퍼타입의 리스트다. - -이에 관한 코드를 살펴보자. - - 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) - } - -윤창석, 이한욱 옮김 \ No newline at end of file diff --git a/ko/tutorials/tour/_posts/2017-02-13-mixin-class-composition.md b/ko/tutorials/tour/_posts/2017-02-13-mixin-class-composition.md deleted file mode 100644 index 63d678b684..0000000000 --- a/ko/tutorials/tour/_posts/2017-02-13-mixin-class-composition.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -layout: tutorial -title: 믹스인 클래스 컴포지션 - -discourse: false - -tutorial: scala-tour -categories: tour -num: 5 -language: ko - -next-page: anonymous-function-syntax -previous-page: traits ---- - -_단일 상속_ 만을 지원하는 여러 언어와는 달리, 스칼라는 더욱 일반화된 시각에서 클래스를 재사용한다. 스칼라는 새로운 클래스를 정의할 때 _클래스의 새로운 멤버 정의_ (즉, 슈퍼클래스와 비교할 때 변경된 부분)를 재사용할 수 있다. 이를 _믹스인 클래스 컴포지션_ 이라고 부른다. 이터레이터를 추상화한 다음 예제를 살펴보자. - - abstract class AbsIterator { - type T - def hasNext: Boolean - def next: T - } - -이어서, 이터레이터가 반환하는 모든 항목에 주어진 함수를 적용해주는 `foreach` 메소드로 `AbsIterator`를 확장한 믹스인 클래스를 살펴보자. 믹스인으로 사용할 수 있는 클래스를 정의하기 위해선 `trait`이란 키워드를 사용한다. - - trait RichIterator extends AbsIterator { - def foreach(f: T => Unit) { while (hasNext) f(next) } - } - -다음은 주어진 문자열의 캐릭터를 차례로 반환해주는 콘크리트 이터레이터 클래스다. - - 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 } - } - -`StringIterator`와 `RichIterator`를 하나의 클래스로 합치고 싶다면 어떻게 할까. 두 클래스 모두는 코드가 포함된 멤버 구현을 담고 있기 때문에 단일 상속과 인터페이스 만으론 불가능한 일이다. 스칼라는 _믹스인 클래스 컴포지션_ 으로 이런 상황을 해결해준다. 프로그래머는 이를 사용해 클래스 정의에서 변경된 부분을 재사용할 수 있다. 이 기법은 주어진 문자열에 포함된 모든 캐릭터를 한 줄로 출력해주는 다음의 테스트 프로그램에서와 같이, `StringIterator`와 `RichIterator`를 통합할 수 있도록 해준다. - - object StringIteratorTest { - def main(args: Array[String]) { - class Iter extends StringIterator(args(0)) with RichIterator - val iter = new Iter - iter foreach println - } - } - -`main` 함수의 `Iter` 클래스는 부모인 `StringIterator`와 `RichIterator`를 `with` 키워드를 사용해 믹스인 컴포지션해서 만들어졌다. 첫 번째 부모를 `Iter`의 _슈퍼클래스_ 라고 부르며, 두 번째 부모를(더 이어지는 항목이 있다면 이 모두를 일컬어) _믹스인_ 이라고 한다. - -윤창석, 이한욱 옮김 diff --git a/ko/tutorials/tour/_posts/2017-02-13-named-parameters.md b/ko/tutorials/tour/_posts/2017-02-13-named-parameters.md deleted file mode 100644 index fdfb30b2e3..0000000000 --- a/ko/tutorials/tour/_posts/2017-02-13-named-parameters.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -layout: tutorial -title: 이름을 지정한 파라미터 - -discourse: false - -tutorial: scala-tour -categories: tour -num: 33 -language: ko - -previous-page: default-parameter-values ---- - -메소드와 함수를 호출할 땐 다음과 같이 해당 호출에서 명시적으로 변수의 이름을 사용할 수 있다. - - def printName(first:String, last:String) = { - println(first + " " + last) - } - - printName("John","Smith") - // "John Smith"를 출력 - printName(first = "John",last = "Smith") - // "John Smith"를 출력 - printName(last = "Smith",first = "John") - // "John Smith"를 출력 - -일단 호출에서 파라미터 이름을 사용했다면 모든 파라미터에 이름이 붙어 있는 한 그 순서는 중요치 않다. 이 기능은 [기본 파라미터 값]({{ site.baseurl }}/tutorials/tour/default-parameter-values.html)과 잘 어울려 동작한다. - - def printName(first:String = "John", last:String = "Smith") = { - println(first + " " + last) - } - - printName(last = "Jones") - // "John Jones"를 출력 - -여러분이 원하는 순서대로 파라미터를 위치시킬 수 있기 때문에 파라미터 목록 중에서 가장 중요한 파라미터부터 기본 값을 사용할 수 있다. - -윤창석, 이한욱 옮김 \ No newline at end of file diff --git a/ko/tutorials/tour/_posts/2017-02-13-nested-functions.md b/ko/tutorials/tour/_posts/2017-02-13-nested-functions.md deleted file mode 100644 index 91443f9bcb..0000000000 --- a/ko/tutorials/tour/_posts/2017-02-13-nested-functions.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -layout: tutorial -title: 중첩 함수 - -discourse: false - -tutorial: scala-tour -categories: tour -num: 8 -language: ko - -next-page: currying -previous-page: higher-order-functions ---- - -스칼라에선 중첩 함수를 정의할 수 있다. 다음 오브젝트는 정수의 리스트에서 지정된 값보다 작은 값을 값을 추출해주는 `filter` 함수를 제공한다. - - 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)) - } - -_주의: 중첩 함수 `process`는 `filter`의 파라미터 값으로써 외부 범위에서 정의된 `threshold`를 참조한다._ - -이 프로그램의 실행 결과는 다음과 같다. - - List(1,2,3,4) - -윤창석, 이한욱 옮김 \ No newline at end of file diff --git a/ko/tutorials/tour/_posts/2017-02-13-operators.md b/ko/tutorials/tour/_posts/2017-02-13-operators.md deleted file mode 100644 index 2763ce3a6b..0000000000 --- a/ko/tutorials/tour/_posts/2017-02-13-operators.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -layout: tutorial -title: 연산자 - -discourse: false - -tutorial: scala-tour -categories: tour -num: 29 -language: ko - -next-page: automatic-closures -previous-page: local-type-inference ---- - -스칼라에선 단일 파라미터를 취하는 모든 메소드를 *중위 연산자*로 사용할 수 있다. 다음은 `and`와 `or`, `negate` 등의 세 가지 메소드를 정의하고 있는 클래스 `MyBool`의 정의다. - - 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 = new MyBool(!x) - } - -이제 `and`와 `or`를 중위 연산자로 사용할 수 있다. - - def not(x: MyBool) = x negate; // 여기엔 세미콜론이 필요함 - def xor(x: MyBool, y: MyBool) = (x or y) and not(x and y) - -이 코드의 첫 번째 줄에서 알 수 있듯이, 무항 메소드는 후위 연산자로 사용할 수도 있다. 두 번째 줄에선 `and`와 `or` 메소드와 함께 새로운 함수 `not`을 사용해 `xor` 함수를 정의했다. 이 예제에선 _중위 연산자_를 사용해 `xor` 정의의 가독성을 높일 수 있다. - -다음은 이와 같은 코드를 좀 더 전통적인 객체지향 언어 구문에 따라 작성해본 코드다. - - def not(x: MyBool) = x.negate; // 여기엔 세미콜론이 필요함 - def xor(x: MyBool, y: MyBool) = x.or(y).and(x.and(y).negate) - -윤창석, 이한욱 옮김 \ No newline at end of file diff --git a/ko/tutorials/tour/_posts/2017-02-13-pattern-matching.md b/ko/tutorials/tour/_posts/2017-02-13-pattern-matching.md deleted file mode 100644 index 637ed26c26..0000000000 --- a/ko/tutorials/tour/_posts/2017-02-13-pattern-matching.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -layout: tutorial -title: 패턴 매칭 - -discourse: false - -tutorial: scala-tour -categories: tour -num: 11 -language: ko - -next-page: singleton-objects -previous-page: case-classes ---- - -스칼라는 범용적 빌트인 패턴 매칭 기능을 제공한다. 이는 우선 매칭 정책에 따라 어떤 종류의 데이터든 매칭할 수 있도록 해준다. -다음은 정수 값을 매칭하는 방법에 관한 간단한 예제다. - - object MatchTest1 extends App { - def matchTest(x: Int): String = x match { - case 1 => "one" - case 2 => "two" - case _ => "many" - } - println(matchTest(3)) - } - -`case` 명령문을 포함하고 있는 블록은 정수를 문자열로 매핑하는 함수를 정의한다. `match`라는 키워드는 함수(위에서 살펴본 패턴 매칭 함수와 같은)를 오브젝트에 적용하는 편리한 방법을 제공한다. - -다음은 두 번째 예제로 여러 타입의 패턴에 맞춰 값을 매칭한다. - - 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")) - } - -첫 번째 `case`는 `x`가 정수 값 `1`일 때 매칭된다. 두 번째 `case`는 `x`가 문자열 `"two"`일 때 매칭된다. 세 번째 케이스는 타입이 지정된 패턴으로 구성되며, 모든 정수와 매칭돼 선택자 값 `x`를 정수 타입 변수 `y`로 연결한다. - -스칼라의 패턴 매칭 명령문은 [케이스 클래스](case-classes.html)를 통해 표현되는 대수 타입과 매칭할 때 가장 유용하다. -또한 스칼라는 [추출자 오브젝트](extractor-objects.html)의 `unapply` 메소드를 사용해, 독립적인 케이스 클래스로 패턴을 정의할 수 있도록 해준다. - -윤창석, 이한욱 옮김 \ No newline at end of file diff --git a/ko/tutorials/tour/_posts/2017-02-13-polymorphic-methods.md b/ko/tutorials/tour/_posts/2017-02-13-polymorphic-methods.md deleted file mode 100644 index b8161b3ebe..0000000000 --- a/ko/tutorials/tour/_posts/2017-02-13-polymorphic-methods.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -layout: tutorial -title: 다형성 메소드 - -discourse: false - -tutorial: scala-tour -categories: tour -num: 27 -language: ko - -next-page: local-type-inference -previous-page: implicit-conversions ---- - -스칼라의 메소드는 값과 타입 모두가 파라미터화 될 수 있다. 클래스 수준에서와 같이, 값 파라미터는 괄호의 쌍으로 묶이며 타입 파라미터는 브래킷의 쌍 안에 위치한다. - -다음의 예제를 살펴보자. - - object PolyTest extends App { - 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)) - } - -오브젝트 `PolyTest`의 메소드 `dup`는 타입 `T`와 값 파라미터인 `x: T` 및 `n: Int`로 파라미터화 됐다. 프로그래머는 메소드 `dup`를 호출하며 필요한 파라미터를 전달하지만_(위의 프로그램 8번째 줄을 보자)_, 9번째 줄에서와 같이 프로그래머는 타입 파라미터를 명시적으로 전달할 필요가 없다. 메소드가 호출되는 시점에서 주어진 값 파라미터의 타입과 컨텍스트를 살펴봄으로써 이를 해결해준다. - -트레잇 `App`은 간단한 테스트 프로그램을 작성하도록 설계됐으며, JVM의 코드 최적화 능력에 영향을 주기 때문에 프로덕션 코드에선 사용할 수 없음을 상기하자(스칼라 버전 2.8.x와 그 이전 버전). 그 대신 `def main()`을 사용하도록 하자. - -윤창석, 이한욱 옮김 \ No newline at end of file diff --git a/ko/tutorials/tour/_posts/2017-02-13-regular-expression-patterns.md b/ko/tutorials/tour/_posts/2017-02-13-regular-expression-patterns.md deleted file mode 100644 index 7a49b320d9..0000000000 --- a/ko/tutorials/tour/_posts/2017-02-13-regular-expression-patterns.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -layout: tutorial -title: 정규 표현식 패턴 - -discourse: false - -tutorial: scala-tour -categories: tour -num: 14 -language: ko - -next-page: extractor-objects -previous-page: singleton-objects ---- - -## 우측 무시 시퀀스 패턴 ## - -우측 무시 패턴은 `Seq[A]`의 서브 타입이나 반복되는 정형 파라미터를 가진 케이스 클래스의 데이터를 분해할 때 유용한 기능이다. 다음 예를 살펴보자. - - Elem(prefix:String, label:String, attrs:MetaData, scp:NamespaceBinding, children:Node*) - -이런 경우, 스칼라는 패턴의 가장 오른쪽 위치에 별 와일드카드 `_*`를 사용해 임의의 긴 시퀀스를 대신할 수 있도록 해준다. -다음 예제는 시퀀스의 앞 부분을 매칭하고 나머지는 변수 `rest`와 연결하는 패턴 매칭을 보여준다. - - 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 - } - } - } - -이전의 스칼라 버전과는 달리, 다음과 같은 이유로 스칼라는 더 이상 임의의 정규 표현식을 허용하지 않는다. - -###일반적인 `RegExp` 패턴은 일시적으로 스칼라에서 제외됐다### - -정확성에 문제를 발견했기 때문에 이 기능은 스칼라 언어에서 일시적으로 제외됐다. 사용자 커뮤니티에서 요청한다면 개선된 형태로 이를 다시 활성화할 수도 있다. - -정규 표현식 패턴이 XML 처리에 예상에 비해 그다지 유용하지 않다는 것이 우리의 의견이다. 실제 상황에서 XML를 처리해야하는 애플리케이션이라면 XPath가 더 나은 선택으로 보인다. 우리의 변환이나 정규 표현식 패턴에 드물지만 제거하기 어려운 난해한 버그가 있다는 점을 발견했을 때, 우리는 이 기회에 언어를 좀 더 단순하게 만들기로 결정했다. - -윤창석, 이한욱 옮김 \ No newline at end of file diff --git a/ko/tutorials/tour/_posts/2017-02-13-sequence-comprehensions.md b/ko/tutorials/tour/_posts/2017-02-13-sequence-comprehensions.md deleted file mode 100644 index 8a519d5014..0000000000 --- a/ko/tutorials/tour/_posts/2017-02-13-sequence-comprehensions.md +++ /dev/null @@ -1,62 +0,0 @@ ---- -layout: tutorial -title: 시퀀스 컴프리헨션 - -discourse: false - -tutorial: scala-tour -categories: tour -num: 16 -language: ko - -next-page: generic-classes -previous-page: extractor-objects ---- - -스칼라는 *시퀀스 컴프리헨션(sequence comprehensions)*을 표현하기 위한 간편한 문법을 제공한다. 컴프리헨션은 `for (enumerators) yield e`와 같은 형태를 가지며, 여기서 `enumerators`는 세미콜론으로 구분된 이뉴머레이터들을 뜻한다. *이뉴머레이터*는 새로운 변수를 정의하는 생성자이거나 필터이다. 컴프리헨션은 생성된 각각의 새로운 변수에 대해서 그 몸체인 `e`를 계산하여 그 값들의 시퀀스를 반환한다. - -예제 : - - 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)) - } - -위의 함수 안에 있는 for-구문은 `int` 타입의 변수 `i`를 정의하고 이어 `i`는 리스트 `List(from, from+1, ..., to - 1)`의 모든 값에 각각 대응된다. 가드 `if i % 2 == 0`는 홀수를 필터링하여 제외하여 (i만으로 이루어진) 몸체가 짝수에 대해서만 계산되도록 한다. 그 결과, 전체 for-구문은 짝수 리스트를 반환하게 된다. - -이 프로그램의 결과값은 다음과 같다 : - - List(0, 2, 4, 6, 8, 10, 12, 14, 16, 18) - -좀 더 복잡한 예제를 보자. 아래는 `0`과 `n-1`사이의 숫자로 이루어진 모든 순서쌍 중에서 그 합이 주어진 값 `v`와 같은 순서쌍을 계산하는 예제이다. - - 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)") - } - } - -이 예제는 컴프리헨션이 리스트에만 국한되지 않는다는 것을 보여준다. 리스트 대신에 이 프로그램에서는 이터레이터를 사용하였다. `withFilter`, `map`, `flatMap`기능을 지원하는 모든 데이터타입이 시퀀스 컴프리헨션에 이용될 수 있다. - -아래는 이 프로그램의 결과값이다: - - (13, 19) - (14, 18) - (15, 17) - (16, 16) - -시퀀스 컴프리헨션의 특별한 형태로 `Unit`을 반환하는 경우도 있다. (???어려워요. binding 뭐라고하지...) 이러한 시퀀스 익스프레션을 사용할 때에는 `yield` 키워드를 사용하지 않아야 한다. 아래 예제는 위의 예제와 같은 결과값을 출력하지만 `Unit`을 반환하는, 시퀀스 컴프리헨션의 특수형태를 사용하였다. - - 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/ko/tutorials/tour/_posts/2017-02-13-singleton-objects.md b/ko/tutorials/tour/_posts/2017-02-13-singleton-objects.md deleted file mode 100644 index 7d845fbd38..0000000000 --- a/ko/tutorials/tour/_posts/2017-02-13-singleton-objects.md +++ /dev/null @@ -1,77 +0,0 @@ ---- -layout: tutorial -title: 싱글톤 객체 - -discourse: false - -tutorial: scala-tour -categories: tour -num: 12 -language: ko - -next-page: regular-expression-patterns -previous-page: pattern-matching ---- - -[클래스](classes.html)의 각 인스턴스와 연관되지 않은 메서드와 값들은 싱글톤 객체에 속하며, `class`대신에 `object`를 사용해 표시된다. - -``` -package test - -object Blah { - def sum(l: List[Int]): Int = l.sum -} -``` - -위 `sum`메서드는 전역적으로 접근가능하고 참조될수 있으며, `test.Blah.sum`로 import될수 있다. - -싱글턴 객체는 직접 인스턴스화 될 수 없는 싱글턴 클래스를 정의하기 위한 축약형 같은 것이며, `object`의 정의 시점의 같은 이름을 가진 `val` 멤버 같은 것이다. 사실 `val`과 같이, 싱글턴 객체는 변칙적이긴 하지만 [트레잇](traits.html)이나 클래스의 멤버로서 정의될수 있다. - -하나의 싱글턴 객체는 클래스와 트레잇으로 확장할수 있다. 사실, [타입 파라미터](generic-classes.html)가 없는 [케이스 클래스](case-classes.html)는 기본적으로 같은 이름의 싱글턴 객체를 생성하며, 구현된 [`Function*`](http://www.scala-lang.org/api/current/scala/Function1.html)을 가진다. - -## 동반자(Companions) ## - -대부분의 싱글턴 객체는 독립적이지 않으며, 대신에 같은 이름의 클래스와 연관되어있다. 위에서 언급한 클래스의 "같은 이름의 싱글턴 객체"는 이 예이다. 이 현상이 발생할 때, 싱글턴 객체는 클래스의 *동반자 객체* 라고 하며, 그 클래스는 객체의 *동반자 클래스*라고 한다. - -[스칼라 문서](https://wiki.scala-lang.org/display/SW/Introduction)는 클래스와 그 동반자 사이의 이동을 위한 특별한 지원을 가지고 있다. 만약 큰 "C"나 "O" 원이 아래에서 위로 접힌 경계를 가진다면, 여러분은 동반자로 이동하기 위해 해당 원을 클릭할수 있다. - -하나의 클래스와 그 동반자 객체는 어떤 경우라도, 아래와 같이 *같은* 소스파일에 정의되어야 한다. - -```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)) -} -``` - -타입클래스 패턴을 따를때, 일반적으로 타입클래스 인스턴스들을 동반자 안에 정의된 `ipord`와 같은 [암시적 값들](implicit-parameters.html)로 생각한다. - -## 자바 프로그래머들이 주의할 점 ## - -`static`은 스칼라에서 키워드가 아니다. 대신에 class를 포함한 static일 수 있는 모든 멤버는 싱글턴 객체에 있어야 한다. 그것들은 부분적으로 또는 그룹 등등으로 import될수 있으며, 같은 문법으로 참조될수 있다. - -빈번하게, 자바프로그래머들은 그 인스턴스 멤버를 목적으로 구현 할때 `private`을 사용해 static 멤버를 정의한다. 이것들은 또한 동반자(companion)으로 이동되었다. 일반적인 패턴은 아래와 같이 동반자 객체(object)의 멤버들을 클래스 안으로 import하는 것이다. - -``` -class X { - import X._ - - def blah = foo -} - -object X { - private def foo = 42 -} -``` - -이것은 또 다른 특징을 설명한다. `private`의 문맥에서 클래스와 그 동반자는 친구다. `객체 X`는 `클래스 X`의 private 멤버들에 접근할수 있다. 하나의 멤버를 *정말로* private하게 만들고 싶다면 `private[this]`를 사용하라. - -For Java convenience, methods, including `var`s and `val`s, defined directly in a singleton object also have a static method defined in the companion class, called a *static forwarder*. Other members are accessible via the `X$.MODULE$` static field for `object X`. - -자바 편의를 위해서, `var`와 `val`의 것 모두, 싱글턴 객체에 정의된 메서드들은 *static forwarder* 라고 불리는 동반자 클래스안에 정의된 static메서드를 가진다. 다른 멤버들은 `객체 X`를 위한 static 필드 `X$.MODULE$`를 통해 접근할수 있다. - -만약 당신이 모든 것을 동반자 객체에 옮기고, 당신이 남겨놓은 모든것이 인스턴스화가 되길 바라지 않는 하나의 클래스라면, 간단하게 그 클래스를 삭제하라. Static forwarder는 여전히 생성된다. diff --git a/ko/tutorials/tour/_posts/2017-02-13-tour-of-scala.md b/ko/tutorials/tour/_posts/2017-02-13-tour-of-scala.md deleted file mode 100644 index 462a8551a6..0000000000 --- a/ko/tutorials/tour/_posts/2017-02-13-tour-of-scala.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -layout: tutorial -title: 들어가며 - -discourse: false - -tutorial: scala-tour -categories: tour -num: 1 -language: ko - -next-page: unified-types ---- - -스칼라는 정확하고 명쾌하며 타입 세이프한 방식으로 일반적인 유형의 프로그래밍 패턴을 표현하기 위해 설계된 새로운 다중 패러다임 프로그래밍 언어다. 스칼라는 객체지향과 함수형 언어를 자연스럽게 통합해준다. - -## 스칼라는 객체지향이다 ## -[모든 값이 객체](unified-types.html)라는 측면에서 스칼라는 순수 객체지향 언어다. 객체의 타입과 행위는 [클래스](classes.html)와 [트레잇](traits.html)으로 나타난다. 클래스는 서브클래스를 만들거나, 다중 상속을 깔끔하게 대체하는 유연한 [믹스인 기반 컴포지션](mixin-class-composition.html) 방법을 통해 확장된다. - -## 스칼라는 함수형이다 ## -또한, 스칼라는 [모든 함수가 값](unified-types.html)이라는 측면에서 함수형 언어다. 스칼라는 익명 함수를 위한 [경량 구문](anonymous-function-syntax.html)을 제공하고, [고차 함수](higher-order-functions.html)를 지원하며, 함수의 [중첩](nested-functions.html)을 허용하고, [커링](currying.html)을 지원한다. 스칼라의 [케이스 클래스](case-classes.html)와 케이스 클래스의 [패턴 매칭](pattern-matching.html) 빌트인 지원을 통해 여러 함수형 프로그래밍 언어에서 사용되는 대수 타입을 만들 수 있다. - -뿐만 아니라 스칼라의 패턴 매칭 개념은 [우측 무시 시퀀스 패턴](regular-expression-patterns.html)의 도움을 받아 자연스럽게 [XML 데이터의 처리](xml-processing.html)로 확장된다. 이런 맥락에서 [시퀀스 컴프리헨션](sequence-comprehensions.html)은 쿼리를 만들 때 유용하다. 이런 기능 때문에 스칼라는 웹 서비스와 같은 애플리케이션 개발에 있어서 이상적인 선택이 될 수 있다. - -## 스칼라는 정적 타입이다 ## -스칼라는 안전하고 일관성 있는 추상화를 정적으로 강제하는 풍부한 타입 시스템을 장착하고 있다. 특히 타입 시스템은 다음과 같은 사항을 지원한다. - -* [제네릭 클래스](generic-classes.html) -* [가변성 어노테이션](variances.html) -* [상위 타입 경계](upper-type-bounds.html)와 [하위 타입 경계](lower-type-bounds.html) -* 객체 멤버로써의 [내부 클래스](inner-classes.html)와 [추상 타입](abstract-types.html) -* [합성 타입](compound-types.html) -* [명시적으로 타입이 지정된 자기 참조](explicitly-typed-self-references.html) -* [뷰](views.html) -* [다형 메소드](polymorphic-methods.html) - -[로컬 타입 추론 방식](local-type-inference.html)은 사용자가 불필요한 타입 정보를 어노테이션해야 하는 불편함을 줄여준다. 이와 함께, 이런 기능은 프로그래밍 추상화를 안전하게 재사용하고 소프트웨어를 타입 세이프하게 확장하는 강력한 기반이 된다. - -## 스칼라는 확장성이 높다 ## -실제 개발 상황에선, 도메인 고유 애플리케이션의 개발에 도메인 고유 언어 확장이 필요할 때가 많다. 스칼라는 라이브러리의 형태로 새로운 언어 구성을 쉽고 자연스럽게 추가할 수 있도록 고유한 언어 기능 조합을 제공한다. - -* 어떤 메소드든 [중위나 후위 연산자](operators.html)로 사용될 수 있다. -* [클로저는 기대 타입에 따라 자동으로 생성된다](automatic-closures.html)(타입이 대상에 맞춰진다). - -두 기능을 함께 엮어서 사용하면 새로운 구문을 확장하거나 매크로 같은 메타 프로그래밍을 활용할 필요없이 명령문을 정의할 수 있도록 해준다. - -스칼라는 자바와 닷넷과 상호 호환된다. -스칼라는 잘 알려진 자바 2 런타임 환경(JRE)과 상호 호환되도록 설계됐다. 특히 객체지향의 주류인 자바 프로그래밍 언어와 굉장히 자연스럽게 상호작용한다. 스칼라는 자바와 같은 컴파일 모델(컴파일 분리, 동적 클래스 로딩)을 사용하며, 현재 사용되고 있는 수많은 높은 품질의 라이브러리를 그대로 활용할 수 있도록 해준다. - -다음 페이지로 이동하면 더 자세한 내용을 알아보게 된다. - -윤창석, 이한욱 옮김 diff --git a/ko/tutorials/tour/_posts/2017-02-13-traits.md b/ko/tutorials/tour/_posts/2017-02-13-traits.md deleted file mode 100644 index 9a0d1f3b06..0000000000 --- a/ko/tutorials/tour/_posts/2017-02-13-traits.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -layout: tutorial -title: 트레잇 - -discourse: false - -tutorial: scala-tour -categories: tour -num: 4 -language: ko - -next-page: mixin-class-composition -previous-page: classes ---- - -트레잇은 자바의 인터페이스와 유사하며, 지원되는 메소드의 서명을 지정해 객체의 타입을 정의하는 데 사용한다. 자바와는 달리, 스칼라에선 트레잇의 일부만 구현할 수도 있다. 다시 말해, 일부 메소드를 선택해 기본 구현 내용을 사전에 정의할 수 있다. 클래스와는 달리, 트레잇은 생성자 파라미터를 가질 수 없다. -다음의 예를 살펴보자. - - trait Similarity { - def isSimilar(x: Any): Boolean - def isNotSimilar(x: Any): Boolean = !isSimilar(x) - } - -이 트레잇은 `isSimilar`와 `isNotSimilar`라는 두 메소드로 구성된다. 메소드 `isSimilar`의 구현은 제공되지 않지만(자바의 abstract와 같다), 메소드 `isNotSimilar`에선 실제로 구현 내용을 정의하고 있다. 그 결과, 이 트레잇과 결합되는 클래스는 오직 `isSimilar`의 실제 구현만을 제공하면 된다. `isNotSimilar`의 행위는 트레잇에서 바로 상속받는다. 일반적으로 트레잇은 [믹스인 클래스 컴포지션](mixin-class-composition.html)을 통해 [클래스](classes.html)(또는 또 다른 트레잇)와 결합된다. - - 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) - println(p1.isNotSimilar(p2)) - println(p1.isNotSimilar(p3)) - println(p1.isNotSimilar(2)) - } - -이 프로그램의 실행 결과는 다음과 같다. - - false - true - true - -윤창석, 이한욱 옮김 diff --git a/ko/tutorials/tour/_posts/2017-02-13-unified-types.md b/ko/tutorials/tour/_posts/2017-02-13-unified-types.md deleted file mode 100644 index 0908fac5d7..0000000000 --- a/ko/tutorials/tour/_posts/2017-02-13-unified-types.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -layout: tutorial -title: 통합된 타입 - -discourse: false - -tutorial: scala-tour -categories: tour -num: 2 -language: ko - -next-page: classes -previous-page: tour-of-scala ---- - -자바와는 달리, 스칼라에선 모든 값이 객체다(숫자 값과 함수를 포함해). 스칼라는 클래스 기반이기 때문에 모든 값은 클래스의 인스턴스다. 다음의 다이어그램은 클래스 계층구조를 나타낸다. - -![스칼라 타입 계층구조]({{ site.baseurl }}/resources/images/classhierarchy.img_assist_custom.png) - -## 스칼라 클래스 계층구조 ## - -모든 클래스의 슈퍼클래스인 `scala.Any`로부터 `scala.AnyVal`과 `scala.AnyRef`라는 두 서브클래스가 파생되며, 이 두 클래스는 각각 값 클래스와 참조 클래스를 대표한다. 모든 값 클래스는 미리 정의돼 있으며, 이는 자바와 같은 언어의 원시 타입에 해당한다. 다른 모든 클래스는 참조 타입으로 정의된다. 사용자 정의 클래스는 자동으로 참조 타입으로 정의되며, 이렇게 정의된 클래스는 항상 `scala.AnyRef`의 서브클래스(간접적)다. 스칼라의 모든 사용자 정의 클래스는 암시적으로 트레잇 `scala.ScalaObject`를 확장하고 있다. 스칼라가 실행되는 인프라(예, 자바 런타임 환경) 상의 클래스는 `scala.ScalaObject`를 확장하지 않는다. 스칼라를 자바 런타임 환경의 측면에 맞춰 생각해보자면 , `scala.AnyRef`는 `java.lang.Object`에 해당한다. 위의 다이어그램에는 값 클래스 사이의 암시적 변환을 의미하는 뷰도 함께 표현돼 있다. -다음은 다른 객체처럼 숫자와 문자와 불리언 값과 함수 또한 객체임을 보여주는 예제다. - - object UnifiedTypes extends App { - val set = new scala.collection.mutable.LinkedHashSet[Any] - set += "This is a string" // 문자열을 추가한다 - set += 732 // 숫자를 추가한다 - set += 'c' // 캐릭터를 추가한다 - set += true // 불리언 값을 추가한다 - set += main _ // 메인 함수를 추가한다 - val iter: Iterator[Any] = set.iterator - while (iter.hasNext) { - println(iter.next.toString()) - } - } - -이 프로그램은 `App`을 확장한 최상위 싱글턴 오브젝트로써 애플리케이션 `UnifiedTypes`를 선언했다. 애플리케이션에선 클래스 `LinkedHashSet[Any]`의 인스턴스를 가리키는 지역 변수 `set`을 정의했다. 프로그램은 이 집합에 여러 항목을 추가하는데, 해당 항목은 반드시 선언된 항목의 타입인 `Any`에 맞아야 한다. 마지막에는 모든 항목의 문자열 표현을 출력한다. - -다음은 이 프로그램의 실행 결과다. - - This is a string - 732 - c - true - - -윤창석, 이한욱 옮김 \ No newline at end of file diff --git a/ko/tutorials/tour/_posts/2017-02-13-upper-type-bounds.md b/ko/tutorials/tour/_posts/2017-02-13-upper-type-bounds.md deleted file mode 100644 index 3b4326dd04..0000000000 --- a/ko/tutorials/tour/_posts/2017-02-13-upper-type-bounds.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -layout: tutorial -title: 상위 타입 경계 - -discourse: false - -tutorial: scala-tour -categories: tour -num: 19 -language: ko - -next-page: lower-type-bounds -previous-page: variances ---- - -스칼라에선 [타입 파라미터](generic-classes.html) 와 [추상 타입](abstract-types.html)의 타입 경계를 제한할 수 있다. 이런 타입 경계는 타입 변수의 콘크리트 값을 제한하고, 해당 타입의 멤버에 관한 정보를 추가할 수도 있다. _상위 타입 경계_ `T <: A`는 타입 변수 `T`를 선언하면서 서브타입 `A`를 참조하고 있다. 다음은 다형성 메소드 `findSimilar`의 구현을 위해 상위 타입 경계를 사용한 예제다. - - trait Similar { - def isSimilar(x: Any): Boolean - } - case class MyInt(x: Int) extends Similar { - def isSimilar(m: Any): Boolean = - m.isInstanceOf[MyInt] && - m.asInstanceOf[MyInt].x == x - } - object UpperBoundTest extends App { - def findSimilar[T <: Similar](e: T, xs: List[T]): Boolean = - if (xs.isEmpty) false - else if (e.isSimilar(xs.head)) true - else findSimilar[T](e, xs.tail) - val list: List[MyInt] = List(MyInt(1), MyInt(2), MyInt(3)) - println(findSimilar[MyInt](MyInt(4), list)) - println(findSimilar[MyInt](MyInt(2), list)) - } - -상위 타입 경계 어노테이션이 없었다면 메소드 `findSimilar`에서 `isSimilar` 메소드를 호출할 수 없었을 것이다. -하위 타입 경계의 사용법은 [이 곳](lower-type-bounds.html)에서 논의한다. - -윤창석, 이한욱 옮김 \ No newline at end of file diff --git a/ko/tutorials/tour/_posts/2017-02-13-variances.md b/ko/tutorials/tour/_posts/2017-02-13-variances.md deleted file mode 100644 index 0393e7b035..0000000000 --- a/ko/tutorials/tour/_posts/2017-02-13-variances.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -layout: tutorial -title: 가변성 - -discourse: false - -tutorial: scala-tour -categories: tour -num: 18 -language: ko - -next-page: upper-type-bounds -previous-page: generic-classes ---- - -스칼라는 [제네릭 클래스](generic-classes.html)의 타입 파라미터에 관한 가변성 어노테이션을 지원한다. 자바 5(다른 이름은 [JDK 1.5](http://java.sun.com/j2se/1.5/))에선 추상화된 클래스가 사용될 때 클라이언트가 가변성 어노테이션을 결정하지만, 반면에 스칼라는 추상화된 클래스를 정의할 때 가변성 어노테이션을 추가할 수 있다. - -[제네릭 클래스](generic-classes.html)에 관한 페이지에선 변경 가능한 스택의 예제를 살펴보면서, 클래스 `Stack[T]`에서 정의한 타입은 타입 파라미터의 서브타입이 불변자여야 함을 설명했었다. 이는 추상화된 클래스의 재사용을 제한할 수 있다. 지금부턴 이런 제약이 없는 함수형(즉, 변경이 불가능한) 스택의 구현을 알아본다. 이 구현은 [다형성 메소드](polymorphic-methods.html), [하위 타입 경계](lower-type-bounds.html), 순가변 타입 파라미터 어노테이션 등의 중요 개념을 조합한 좀 더 어려운 예제임을 알아두자. 또한 [내부 클래스](inner-classes.html)를 사용해 명시적인 연결 없이도 스택의 항목을 서로 묶을 수 있도록 만들었다. - -```tut -class Stack[+T] { - def push[S >: T](elem: S): Stack[S] = new Stack[S] { - override def top: S = elem - override def pop: Stack[S] = Stack.this - override def toString: String = - elem.toString + " " + Stack.this.toString - } - def top: T = sys.error("no element on stack") - def pop: Stack[T] = sys.error("no element on stack") - override def toString: String = "" -} - -object VariancesTest extends App { - var s: Stack[Any] = new Stack().push("hello") - s = s.push(new Object()) - s = s.push(7) - println(s) -} -``` - -어노테이션 `+T`는 타입 순가변 위치에서만 사용할 수 있는 타입 `T`를 선언한다. 이와 유사하게, `-T`는 역가변 위치에서만 사용할 수 있는 `T`를 선언한다. 순가변 타입 파라미터의 경우, 타입 파라미터의 정의에 따라 서브타입과 순가변 관계를 형성한다. 즉, `T`가 `S`의 서브타입이라면 이 예제의 `Stack[T]`는 `Stack[S]`의 서브타입이다. `-`로 표시된 타입 파라미터 사이에는 이와는 반대의 관계가 맺어진다. - -스택의 예제에서 메소드 `push`를 정의하기 위해선 역가변 위치에 순가변 타입 파라미터 `T`를 사용해야만 한다. 스택의 서브타입이 순가변적여야 하기 때문에, 메소드 `push`의 파라미터 타입을 추상화하는 기법을 사용했다. 즉, 스택 항목의 타입인 `T`를 사용해, `T`가 `push`의 타입 변수 하위 경계로 설정된 다형성 메소드를 만들었다. 이는 `T` 선언에 따른 가변성이 순가변 타입 파라미터와 같도록 해준다. 이제 스택은 순가변적이지만, 지금까지 알아본 방법에 따르면 정수 스택에 문자열을 푸시하는 것과 같은 동작이 가능해진다. 이런 동작은 `Stack[Any]`라는 타입의 스택을 만드는데, 결국 정수 스택이 결과로 반환되길 기다리고 있는 상황에서 이 결과는 오류를 발생시킨다. 오류가 발생하지 않더라도, 항목의 타입이 더 일반화된 스택이 반환될 뿐이다. - - -윤창석, 이한욱 옮김 diff --git a/learn.md b/learn.md new file mode 100644 index 0000000000..adf78808de --- /dev/null +++ b/learn.md @@ -0,0 +1,32 @@ +--- +title: Learning Resources +layout: inner-page-no-masthead +redirect_from: + - /documentation/books.html +--- + +## Online Learning + +### Quick Online Exercises +[Scala Exercises](https://www.scala-exercises.org/) is a series of lessons and exercises created by [47 Degrees](https://www.47deg.com/). It's a great way to get a brief introduction to Scala while testing your knowledge along the way. + +### Coursera courses from EPFL +There are a few interactive resources for trying out Scala, to get a look and feel of the language: + + * [Functional Programming Principles in Scala](https://www.coursera.org/course/progfun), free on Coursera. This is a course about functional programming given by Martin Odersky himself. You can access the course material and exercises by + signing up for the course. + * [Functional Programming in Scala Specialization](https://www.coursera.org/specializations/scala), free on Coursera. The Specialization provides a hands-on introduction to functional programming using Scala. You can access the courses material and exercises by signing up for the specialization. Includes the following courses: + * Functional Programming Principles in Scala + * Functional Program Design in Scala + * Parallel programming + * Big Data Analysis with Scala and Spark + * Functional Programming in Scala Capstone + +### Try Scala In Your Browser! +There are a handful of websites where you can interactively run Scala code in your browser! Have a look at [scala-js-fiddle](http://www.scala-js-fiddle.com/), [Scala Kata](http://www.scalakata.com/), and [Scastie](http://scastie.org/) for more. + +### allaboutscala +[allaboutscala](http://allaboutscala.com/) provides detailed tutorials for beginners. + +### ScalaCourses +[Independent Courseware](http://getscala.com), online self-study or instructor-led Scala and Play courses for a fee. diff --git a/news/_posts/2012-12-12-functional-programming-principles-in-scala-impressions-and-statistics.md b/news/_posts/2012-12-12-functional-programming-principles-in-scala-impressions-and-statistics.md index cb7982662a..7735015903 100644 --- a/news/_posts/2012-12-12-functional-programming-principles-in-scala-impressions-and-statistics.md +++ b/news/_posts/2012-12-12-functional-programming-principles-in-scala-impressions-and-statistics.md @@ -1,9 +1,9 @@ --- -layout: news-coursera +layout: inner-page-no-masthead title: "Functional Programming Principles in Scala: Impressions and Statistics" discourse: true --- -
        By Heather Miller and Martin Odersky
        +###### By Heather Miller and Martin Odersky
        In this post, we discuss our experience giving the popular MOOC Functional Programming Principles in Scala, and provide some insight into who our course participants were, how, overall, students performed in the course, and how students felt about the course. We visualize a lot of these statistics in a number of interactive plots, and we go on to publicly release the data and the code to generate these plots within a fun Scala-based project aimed at allowing you to manipulate these statistics with functional programming in Scala, to generate HTML/Javascript for easily visualizing and sharing them. We encourage you to share what you find with us— we'll share a number of your plots in a follow-up post! @@ -105,4 +105,3 @@ Given sufficient interest, we're planning on posting a follow-up blog article wi Overall, it was an intense 7 weeks for us as well as the course students. Organizing a MOOC is no small matter. We could count on the help the EPFL team, with Lukas Rytz, Nada Amin, Vojin Jovanovic and Manohar Jonnalagedda who designed and implemented the grading infrastructure, prepared the setup instructions, designed the homeworks, and edited the videos and quizzes; Tao Lee, who did most of the video cutting and editing, Tobias Schlatter, who worked tirelessly answering questions on the discussion boards, Pedro Pinto, who designed the recording equipment setup, and Nastaran Fatemi Odersky who did content editing. Many people at Typesafe also helped, in particular the IDE team around Iulian Dragos and Mirco Dotta who implemented the worksheet software and Josh Suereth who helped with sbt. Hard as the work of preparing and running the course was, the amount of positive feedback we got made it worth for us many times over. We believe this medium has a lot of potential and, so far, we are only scratching the surface. - diff --git a/overviews/collections/arrays.md b/overviews/collections/arrays.md deleted file mode 100644 index 788c54198c..0000000000 --- a/overviews/collections/arrays.md +++ /dev/null @@ -1,119 +0,0 @@ ---- -layout: overview-large -title: Arrays - -discourse: true - -partof: collections -num: 10 -languages: [ja, zh-cn] ---- - -[Array](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/Array.html) is a special kind of collection in Scala. On the one hand, Scala arrays correspond one-to-one to Java arrays. That is, a Scala array `Array[Int]` is represented as a Java `int[]`, an `Array[Double]` is represented as a Java `double[]` and a `Array[String]` is represented as a Java `String[]`. But at the same time, Scala arrays offer much more than their Java analogues. First, Scala arrays can be _generic_. That is, you can have an `Array[T]`, where `T` is a type parameter or abstract type. Second, Scala arrays are compatible with Scala sequences - you can pass an `Array[T]` where a `Seq[T]` is required. Finally, Scala arrays also support all sequence operations. Here's an example of this in action: - - scala> val a1 = Array(1, 2, 3) - a1: Array[Int] = Array(1, 2, 3) - scala> val a2 = a1 map (_ * 3) - a2: Array[Int] = Array(3, 6, 9) - scala> val a3 = a2 filter (_ % 2 != 0) - a3: Array[Int] = Array(3, 9) - scala> a3.reverse - res0: Array[Int] = Array(9, 3) - -Given that Scala arrays are represented just like Java arrays, how can these additional features be supported in Scala? In fact, the answer to this question differs between Scala 2.8 and earlier versions. Previously, the Scala compiler somewhat "magically" wrapped and unwrapped arrays to and from `Seq` objects when required in a process called boxing and unboxing. The details of this were quite complicated, in particular when one created a new array of generic type `Array[T]`. There were some puzzling corner cases and the performance of array operations was not all that predictable. - -The Scala 2.8 design is much simpler. Almost all compiler magic is gone. Instead the Scala 2.8 array implementation makes systematic use of implicit conversions. In Scala 2.8 an array does not pretend to _be_ a sequence. It can't really be that because the data type representation of a native array is not a subtype of `Seq`. Instead there is an implicit "wrapping" conversion between arrays and instances of class `scala.collection.mutable.WrappedArray`, which is a subclass of `Seq`. Here you see it in action: - - scala> val seq: Seq[Int] = a1 - seq: Seq[Int] = WrappedArray(1, 2, 3) - scala> val a4: Array[Int] = seq.toArray - a4: Array[Int] = Array(1, 2, 3) - scala> a1 eq a4 - res1: Boolean = true - -The interaction above demonstrates that arrays are compatible with sequences, because there's an implicit conversion from arrays to `WrappedArray`s. To go the other way, from a `WrappedArray` to an `Array`, you can use the `toArray` method defined in `Traversable`. The last REPL line above shows that wrapping and then unwrapping with `toArray` gives the same array you started with. - -There is yet another implicit conversion that gets applied to arrays. This conversion simply "adds" all sequence methods to arrays but does not turn the array itself into a sequence. "Adding" means that the array is wrapped in another object of type `ArrayOps` which supports all sequence methods. Typically, this `ArrayOps` object is short-lived; it will usually be inaccessible after the call to the sequence method and its storage can be recycled. Modern VMs often avoid creating this object entirely. - -The difference between the two implicit conversions on arrays is shown in the next REPL dialogue: - - scala> val seq: Seq[Int] = a1 - seq: Seq[Int] = WrappedArray(1, 2, 3) - scala> seq.reverse - res2: Seq[Int] = WrappedArray(3, 2, 1) - scala> val ops: collection.mutable.ArrayOps[Int] = a1 - ops: scala.collection.mutable.ArrayOps[Int] = [I(1, 2, 3) - scala> ops.reverse - res3: Array[Int] = Array(3, 2, 1) - -You see that calling reverse on `seq`, which is a `WrappedArray`, will give again a `WrappedArray`. That's logical, because wrapped arrays are `Seqs`, and calling reverse on any `Seq` will give again a `Seq`. On the other hand, calling reverse on the ops value of class `ArrayOps` will give an `Array`, not a `Seq`. - -The `ArrayOps` example above was quite artificial, intended only to show the difference to `WrappedArray`. Normally, you'd never define a value of class `ArrayOps`. You'd just call a `Seq` method on an array: - - scala> a1.reverse - res4: Array[Int] = Array(3, 2, 1) - -The `ArrayOps` object gets inserted automatically by the implicit conversion. So the line above is equivalent to - - scala> intArrayOps(a1).reverse - res5: Array[Int] = Array(3, 2, 1) - -where `intArrayOps` is the implicit conversion that was inserted previously. This raises the question how the compiler picked `intArrayOps` over the other implicit conversion to `WrappedArray` in the line above. After all, both conversions map an array to a type that supports a reverse method, which is what the input specified. The answer to that question is that the two implicit conversions are prioritized. The `ArrayOps` conversion has a higher priority than the `WrappedArray` conversion. The first is defined in the `Predef` object whereas the second is defined in a class `scala.LowPriorityImplicits`, which is inherited from `Predef`. Implicits in subclasses and subobjects take precedence over implicits in base classes. So if both conversions are applicable, the one in `Predef` is chosen. A very similar scheme works for strings. - -So now you know how arrays can be compatible with sequences and how they can support all sequence operations. What about genericity? In Java you cannot write a `T[]` where `T` is a type parameter. How then is Scala's `Array[T]` represented? In fact a generic array like `Array[T]` could be at run-time any of Java's eight primitive array types `byte[]`, `short[]`, `char[]`, `int[]`, `long[]`, `float[]`, `double[]`, `boolean[]`, or it could be an array of objects. The only common run-time type encompassing all of these types is `AnyRef` (or, equivalently `java.lang.Object`), so that's the type to which the Scala compiler maps `Array[T]`. At run-time, when an element of an array of type `Array[T]` is accessed or updated there is a sequence of type tests that determine the actual array type, followed by the correct array operation on the Java array. These type tests slow down array operations somewhat. You can expect accesses to generic arrays to be three to four times slower than accesses to primitive or object arrays. This means that if you need maximal performance, you should prefer concrete over generic arrays. Representing the generic array type is not enough, however, there must also be a way to create generic arrays. This is an even harder problem, which requires a little bit of help from you. To illustrate the problem, consider the following attempt to write a generic method that creates an array. - - // this is wrong! - def evenElems[T](xs: Vector[T]): Array[T] = { - val arr = new Array[T]((xs.length + 1) / 2) - for (i <- 0 until xs.length by 2) - arr(i / 2) = xs(i) - arr - } - -The `evenElems` method returns a new array that consist of all elements of the argument vector `xs` which are at even positions in the vector. The first line of the body of `evenElems` creates the result array, which has the same element type as the argument. So depending on the actual type parameter for `T`, this could be an `Array[Int]`, or an `Array[Boolean]`, or an array of some of the other primitive types in Java, or an array of some reference type. But these types have all different runtime representations, so how is the Scala runtime going to pick the correct one? In fact, it can't do that based on the information it is given, because the actual type that corresponds to the type parameter `T` is erased at runtime. That's why you will get the following error message if you compile the code above: - - error: cannot find class manifest for element type T - val arr = new Array[T]((arr.length + 1) / 2) - ^ - -What's required here is that you help the compiler out by providing some runtime hint what the actual type parameter of `evenElems` is. This runtime hint takes the form of a class manifest of type `scala.reflect.ClassTag`. A class manifest is a type descriptor object which describes what the top-level class of a type is. Alternatively to class manifests there are also full manifests of type `scala.reflect.Manifest`, which describe all aspects of a type. But for array creation, only class manifests are needed. - -The Scala compiler will construct class manifests automatically if you instruct it to do so. "Instructing" means that you demand a class manifest as an implicit parameter, like this: - - def evenElems[T](xs: Vector[T])(implicit m: ClassTag[T]): Array[T] = ... - -Using an alternative and shorter syntax, you can also demand that the type comes with a class manifest by using a context bound. This means following the type with a colon and the class name `ClassTag`, like this: - - import scala.reflect.ClassTag - // this works - def evenElems[T: ClassTag](xs: Vector[T]): Array[T] = { - val arr = new Array[T]((xs.length + 1) / 2) - for (i <- 0 until xs.length by 2) - arr(i / 2) = xs(i) - arr - } - -The two revised versions of `evenElems` mean exactly the same. What happens in either case is that when the `Array[T]` is constructed, the compiler will look for a class manifest for the type parameter T, that is, it will look for an implicit value of type `ClassTag[T]`. If such a value is found, the manifest is used to construct the right kind of array. Otherwise, you'll see an error message like the one above. - -Here is some REPL interaction that uses the `evenElems` method. - - scala> evenElems(Vector(1, 2, 3, 4, 5)) - res6: Array[Int] = Array(1, 3, 5) - scala> evenElems(Vector("this", "is", "a", "test", "run")) - res7: Array[java.lang.String] = Array(this, a, run) - -In both cases, the Scala compiler automatically constructed a class manifest for the element type (first, `Int`, then `String`) and passed it to the implicit parameter of the `evenElems` method. The compiler can do that for all concrete types, but not if the argument is itself another type parameter without its class manifest. For instance, the following fails: - - scala> def wrap[U](xs: Vector[U]) = evenElems(xs) - :6: error: No ClassTag available for U. - def wrap[U](xs: Vector[U]) = evenElems(xs) - ^ - -What happened here is that the `evenElems` demands a class manifest for the type parameter `U`, but none was found. The solution in this case is, of course, to demand another implicit class manifest for `U`. So the following works: - - scala> def wrap[U: ClassTag](xs: Vector[U]) = evenElems(xs) - wrap: [U](xs: Vector[U])(implicit evidence$1: scala.reflect.ClassTag[U])Array[U] - -This example also shows that the context bound in the definition of `U` is just a shorthand for an implicit parameter named here `evidence$1` of type `ClassTag[U]`. - -In summary, generic array creation demands class manifests. So whenever creating an array of a type parameter `T`, you also need to provide an implicit class manifest for `T`. The easiest way to do this is to declare the type parameter with a `ClassTag` context bound, as in `[T: ClassTag]`. diff --git a/overviews/collections/concrete-immutable-collection-classes.md b/overviews/collections/concrete-immutable-collection-classes.md deleted file mode 100644 index b8221db8d0..0000000000 --- a/overviews/collections/concrete-immutable-collection-classes.md +++ /dev/null @@ -1,214 +0,0 @@ ---- -layout: overview-large -title: Concrete Immutable Collection Classes - -discourse: true - -partof: collections -num: 8 -languages: [ja, zh-cn] ---- - -Scala provides many concrete immutable collection classes for you to choose from. They differ in the traits they implement (maps, sets, sequences), whether they can be infinite, and the speed of various operations. Here are some of the most common immutable collection types used in Scala. - -## Lists - -A [List](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/List.html) is a finite immutable sequence. They provide constant-time access to their first element as well as the rest of the list, and they have a constant-time cons operation for adding a new element to the front of the list. Many other operations take linear time. - -Lists have always been the workhorse for Scala programming, so not much needs to be said about them here. The major change in 2.8 is that the `List` class together with its subclass `::` and its subobject `Nil` is now defined in package `scala.collection.immutable`, where it logically belongs. There are still aliases for `List`, `Nil`, and `::` in the `scala` package, so from a user perspective, lists can be accessed as before. - -Another change is that lists now integrate more closely into the collections framework, and are less of a special case than before. For instance all of the numerous methods that originally lived in the `List` companion object have been deprecated. They are replaced by the [uniform creation methods]({{ site.baseurl }}/overviews/collections/creating-collections-from-scratch.html) inherited by every collection. - -## Streams - -A [Stream](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Stream.html) is like a list except that its elements are computed lazily. Because of this, a stream can be infinitely long. Only those elements requested are computed. Otherwise, streams have the same performance characteristics as lists. - -Whereas lists are constructed with the `::` operator, streams are constructed with the similar-looking `#::`. Here is a simple example of a stream containing the integers 1, 2, and 3: - - scala> val str = 1 #:: 2 #:: 3 #:: Stream.empty - str: scala.collection.immutable.Stream[Int] = Stream(1, ?) - -The head of this stream is 1, and the tail of it has 2 and 3. The tail is not printed here, though, because it hasn't been computed yet! Streams are specified to compute lazily, and the `toString` method of a stream is careful not to force any extra evaluation. - -Below is a more complex example. It computes a stream that contains a Fibonacci sequence starting with the given two numbers. A Fibonacci sequence is one where each element is the sum of the previous two elements in the series. - - - scala> def fibFrom(a: Int, b: Int): Stream[Int] = a #:: fibFrom(b, a + b) - fibFrom: (a: Int,b: Int)Stream[Int] - -This function is deceptively simple. The first element of the sequence is clearly `a`, and the rest of the sequence is the Fibonacci sequence starting with `b` followed by `a + b`. The tricky part is computing this sequence without causing an infinite recursion. If the function used `::` instead of `#::`, then every call to the function would result in another call, thus causing an infinite recursion. Since it uses `#::`, though, the right-hand side is not evaluated until it is requested. -Here are the first few elements of the Fibonacci sequence starting with two ones: - - scala> val fibs = fibFrom(1, 1).take(7) - fibs: scala.collection.immutable.Stream[Int] = Stream(1, ?) - scala> fibs.toList - res9: List[Int] = List(1, 1, 2, 3, 5, 8, 13) - -## Vectors - -Lists are very efficient when the algorithm processing them is careful to only process their heads. Accessing, adding, and removing the head of a list takes only constant time, whereas accessing or modifying elements later in the list takes time linear in the depth into the list. - -[Vector](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Vector.html) is a collection type (introduced in Scala 2.8) that addresses the inefficiency for random access on lists. Vectors allow accessing any element of the list in "effectively" constant time. It's a larger constant than for access to the head of a list or for reading an element of an array, but it's a constant nonetheless. As a result, algorithms using vectors do not have to be careful about accessing just the head of the sequence. They can access and modify elements at arbitrary locations, and thus they can be much more convenient to write. - -Vectors are built and modified just like any other sequence. - - scala> val vec = scala.collection.immutable.Vector.empty - vec: scala.collection.immutable.Vector[Nothing] = Vector() - scala> val vec2 = vec :+ 1 :+ 2 - vec2: scala.collection.immutable.Vector[Int] = Vector(1, 2) - scala> val vec3 = 100 +: vec2 - vec3: scala.collection.immutable.Vector[Int] = Vector(100, 1, 2) - scala> vec3(0) - res1: Int = 100 - -Vectors are represented as trees with a high branching factor (The branching factor of a tree or a graph is the number of children at each node). Every tree node contains up to 32 elements of the vector or contains up to 32 other tree nodes. Vectors with up to 32 elements can be represented in a single node. Vectors with up to `32 * 32 = 1024` elements can be represented with a single indirection. Two hops from the root of the tree to the final element node are sufficient for vectors with up to 215 elements, three hops for vectors with 220, four hops for vectors with 225 elements and five hops for vectors with up to 230 elements. So for all vectors of reasonable size, an element selection involves up to 5 primitive array selections. This is what we meant when we wrote that element access is "effectively constant time". - -Vectors are immutable, so you cannot change an element of a vector and still retain a new vector. However, with the `updated` method you can create a new vector that differs from a given vector only in a single element: - - scala> val vec = Vector(1, 2, 3) - vec: scala.collection.immutable.Vector[Int] = Vector(1, 2, 3) - scala> vec updated (2, 4) - res0: scala.collection.immutable.Vector[Int] = Vector(1, 2, 4) - scala> vec - res1: scala.collection.immutable.Vector[Int] = Vector(1, 2, 3) - -As the last line above shows, a call to `updated` has no effect on the original vector `vec`. Like selection, functional vector updates are also "effectively constant time". Updating an element in the middle of a vector can be done by copying the node that contains the element, and every node that points to it, starting from the root of the tree. This means that a functional update creates between one and five nodes that each contain up to 32 elements or subtrees. This is certainly more expensive than an in-place update in a mutable array, but still a lot cheaper than copying the whole vector. - -Because vectors strike a good balance between fast random selections and fast random functional updates, they are currently the default implementation of immutable indexed sequences: - - - scala> collection.immutable.IndexedSeq(1, 2, 3) - res2: scala.collection.immutable.IndexedSeq[Int] = Vector(1, 2, 3) - -## Immutable stacks - -If you need a last-in-first-out sequence, you can use a [Stack](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Stack.html). You push an element onto a stack with `push`, pop an element with `pop`, and peek at the top of the stack without removing it with `top`. All of these operations are constant time. - -Here are some simple operations performed on a stack: - - - scala> val stack = scala.collection.immutable.Stack.empty - stack: scala.collection.immutable.Stack[Nothing] = Stack() - scala> val hasOne = stack.push(1) - hasOne: scala.collection.immutable.Stack[Int] = Stack(1) - scala> stack - stack: scala.collection.immutable.Stack[Nothing] = Stack() - scala> hasOne.top - res20: Int = 1 - scala> hasOne.pop - res19: scala.collection.immutable.Stack[Int] = Stack() - -Immutable stacks are used rarely in Scala programs because their functionality is subsumed by lists: A `push` on an immutable stack is the same as a `::` on a list and a `pop` on a stack is the same as a `tail` on a list. - -## Immutable Queues - -A [Queue](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Queue.html) is just like a stack except that it is first-in-first-out rather than last-in-first-out. - -Here's how you can create an empty immutable queue: - - scala> val empty = scala.collection.immutable.Queue[Int]() - empty: scala.collection.immutable.Queue[Int] = Queue() - -You can append an element to an immutable queue with `enqueue`: - - scala> val has1 = empty.enqueue(1) - has1: scala.collection.immutable.Queue[Int] = Queue(1) - -To append multiple elements to a queue, call `enqueue` with a collection as its argument: - - scala> val has123 = has1.enqueue(List(2, 3)) - has123: scala.collection.immutable.Queue[Int] - = Queue(1, 2, 3) - -To remove an element from the head of the queue, you use `dequeue`: - - scala> val (element, has23) = has123.dequeue - element: Int = 1 - has23: scala.collection.immutable.Queue[Int] = Queue(2, 3) - -Note that `dequeue` returns a pair consisting of the element removed and the rest of the queue. - -## Ranges - -A [Range](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Range.html) is an ordered sequence of integers that are equally spaced apart. For example, "1, 2, 3," is a range, as is "5, 8, 11, 14." To create a range in Scala, use the predefined methods `to` and `by`. - - scala> 1 to 3 - res2: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3) - scala> 5 to 14 by 3 - res3: scala.collection.immutable.Range = Range(5, 8, 11, 14) - -If you want to create a range that is exclusive of its upper limit, then use the convenience method `until` instead of `to`: - - scala> 1 until 3 - res2: scala.collection.immutable.Range = Range(1, 2) - -Ranges are represented in constant space, because they can be defined by just three numbers: their start, their end, and the stepping value. Because of this representation, most operations on ranges are extremely fast. - -## Hash Tries - -Hash tries are a standard way to implement immutable sets and maps efficiently. They are supported by class [immutable.HashMap](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/HashMap.html). Their representation is similar to vectors in that they are also trees where every node has 32 elements or 32 subtrees. But the selection of these keys is now done based on hash code. For instance, to find a given key in a map, one first takes the hash code of the key. Then, the lowest 5 bits of the hash code are used to select the first subtree, followed by the next 5 bits and so on. The selection stops once all elements stored in a node have hash codes that differ from each other in the bits that are selected up to this level. - -Hash tries strike a nice balance between reasonably fast lookups and reasonably efficient functional insertions (`+`) and deletions (`-`). That's why they underly Scala's default implementations of immutable maps and sets. In fact, Scala has a further optimization for immutable sets and maps that contain less than five elements. Sets and maps with one to four elements are stored as single objects that just contain the elements (or key/value pairs in the case of a map) as fields. The empty immutable set and the empty immutable map is in each case a single object - there's no need to duplicate storage for those because an empty immutable set or map will always stay empty. - -## Red-Black Trees - -Red-black trees are a form of balanced binary tree where some nodes are designated "red" and others designated "black." Like any balanced binary tree, operations on them reliably complete in time logarithmic to the size of the tree. - -Scala provides implementations of immutable sets and maps that use a red-black tree internally. Access them under the names [TreeSet](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/TreeSet.html) and [TreeMap](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/TreeMap.html). - - - scala> scala.collection.immutable.TreeSet.empty[Int] - res11: scala.collection.immutable.TreeSet[Int] = TreeSet() - scala> res11 + 1 + 3 + 3 - res12: scala.collection.immutable.TreeSet[Int] = TreeSet(1, 3) - -Red-black trees are the standard implementation of `SortedSet` in Scala, because they provide an efficient iterator that returns all elements in sorted order. - -## Immutable BitSets - -A [BitSet](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/BitSet.html) represents a collection of small integers as the bits of a larger integer. For example, the bit set containing 3, 2, and 0 would be represented as the integer 1101 in binary, which is 13 in decimal. - -Internally, bit sets use an array of 64-bit `Long`s. The first `Long` in the array is for integers 0 through 63, the second is for 64 through 127, and so on. Thus, bit sets are very compact so long as the largest integer in the set is less than a few hundred or so. - -Operations on bit sets are very fast. Testing for inclusion takes constant time. Adding an item to the set takes time proportional to the number of `Long`s in the bit set's array, which is typically a small number. Here are some simple examples of the use of a bit set: - - scala> val bits = scala.collection.immutable.BitSet.empty - bits: scala.collection.immutable.BitSet = BitSet() - scala> val moreBits = bits + 3 + 4 + 4 - moreBits: scala.collection.immutable.BitSet = BitSet(3, 4) - scala> moreBits(3) - res26: Boolean = true - scala> moreBits(0) - res27: Boolean = false - -## List Maps - -A [ListMap](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/ListMap.html) represents a map as a linked list of key-value pairs. In general, operations on a list map might have to iterate through the entire list. Thus, operations on a list map take time linear in the size of the map. In fact there is little usage for list maps in Scala because standard immutable maps are almost always faster. The only possible exception to this is if the map is for some reason constructed in such a way that the first elements in the list are selected much more often than the other elements. - - scala> val map = scala.collection.immutable.ListMap(1->"one", 2->"two") - map: scala.collection.immutable.ListMap[Int,java.lang.String] = - Map(1 -> one, 2 -> two) - scala> map(2) - res30: String = "two" - - - - - - - - - - - - - - - - - - - - - - diff --git a/overviews/collections/concrete-mutable-collection-classes.md b/overviews/collections/concrete-mutable-collection-classes.md deleted file mode 100644 index 31ea2c1e91..0000000000 --- a/overviews/collections/concrete-mutable-collection-classes.md +++ /dev/null @@ -1,182 +0,0 @@ ---- -layout: overview-large -title: Concrete Mutable Collection Classes - -discourse: true - -partof: collections -num: 9 -languages: [ja, zh-cn] ---- - -You've now seen the most commonly used immutable collection classes that Scala provides in its standard library. Take a look now at the mutable collection classes. - -## Array Buffers - -An [ArrayBuffer](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/ArrayBuffer.html) buffer holds an array and a size. Most operations on an array buffer have the same speed as for an array, because the operations simply access and modify the underlying array. Additionally, array buffers can have data efficiently added to the end. Appending an item to an array buffer takes amortized constant time. Thus, array buffers are useful for efficiently building up a large collection whenever the new items are always added to the end. - - scala> val buf = scala.collection.mutable.ArrayBuffer.empty[Int] - buf: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer() - scala> buf += 1 - res32: buf.type = ArrayBuffer(1) - scala> buf += 10 - res33: buf.type = ArrayBuffer(1, 10) - scala> buf.toArray - res34: Array[Int] = Array(1, 10) - -## List Buffers - -A [ListBuffer](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/ListBuffer.html) is like an array buffer except that it uses a linked list internally instead of an array. If you plan to convert the buffer to a list once it is built up, use a list buffer instead of an array buffer. - - scala> val buf = scala.collection.mutable.ListBuffer.empty[Int] - buf: scala.collection.mutable.ListBuffer[Int] = ListBuffer() - scala> buf += 1 - res35: buf.type = ListBuffer(1) - scala> buf += 10 - res36: buf.type = ListBuffer(1, 10) - scala> buf.toList - res37: List[Int] = List(1, 10) - -## StringBuilders - -Just like an array buffer is useful for building arrays, and a list buffer is useful for building lists, a [StringBuilder](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/StringBuilder.html) is useful for building strings. String builders are so commonly used that they are already imported into the default namespace. Create them with a simple `new StringBuilder`, like this: - - scala> val buf = new StringBuilder - buf: StringBuilder = - scala> buf += 'a' - res38: buf.type = a - scala> buf ++= "bcdef" - res39: buf.type = abcdef - scala> buf.toString - res41: String = abcdef - -## Linked Lists - -Linked lists are mutable sequences that consist of nodes which are linked with next pointers. They are supported by class [LinkedList](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/LinkedList.html). In most languages `null` would be picked as the empty linked list. That does not work for Scala collections, because even empty sequences must support all sequence methods. In particular `LinkedList.empty.isEmpty` should return `true` and not throw a `NullPointerException`. Empty linked lists are encoded instead in a special way: Their `next` field points back to the node itself. Like their immutable cousins, linked lists are best traversed sequentially. In addition linked lists make it easy to insert an element or linked list into another linked list. - -## Double Linked Lists - -Double linked lists are like single linked lists, except that they have besides `next` another mutable field `prev` that points to the element preceding the current node. The main benefit of that additional link is that it makes element removal very fast. Double linked lists are supported by class [DoubleLinkedList](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/DoubleLinkedList.html). - -## Mutable Lists - -A [MutableList](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/MutableList.html) consists of a single linked list together with a pointer that refers to the terminal empty node of that list. This makes list append a constant time operation because it avoids having to traverse the list in search for its terminal node. [MutableList](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/MutableList.html) is currently the standard implementation of [mutable.LinearSeq](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/LinearSeq.html) in Scala. - -## Queues - -Scala provides mutable queues in addition to immutable ones. You use a `mQueue` similarly to how you use an immutable one, but instead of `enqueue`, you use the `+=` and `++=` operators to append. Also, on a mutable queue, the `dequeue` method will just remove the head element from the queue and return it. Here's an example: - - scala> val queue = new scala.collection.mutable.Queue[String] - queue: scala.collection.mutable.Queue[String] = Queue() - scala> queue += "a" - res10: queue.type = Queue(a) - scala> queue ++= List("b", "c") - res11: queue.type = Queue(a, b, c) - scala> queue - res12: scala.collection.mutable.Queue[String] = Queue(a, b, c) - scala> queue.dequeue - res13: String = a - scala> queue - res14: scala.collection.mutable.Queue[String] = Queue(b, c) - -## Array Sequences - -Array sequences are mutable sequences of fixed size which store their elements internally in an `Array[Object]`. They are implemented in Scala by class [ArraySeq](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/ArraySeq.html). - -You would typically use an `ArraySeq` if you want an array for its performance characteristics, but you also want to create generic instances of the sequence where you do not know the type of the elements and you do not have a `ClassManifest` to provide it at run-time. These issues are explained in the section on [arrays]({{ site.baseurl }}/overviews/collections/arrays.html). - -## Stacks - -You saw immutable stacks earlier. There is also a mutable version, supported by class [mutable.Stack](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/Stack.html). It works exactly the same as the immutable version except that modifications happen in place. - - scala> val stack = new scala.collection.mutable.Stack[Int] - stack: scala.collection.mutable.Stack[Int] = Stack() - scala> stack.push(1) - res0: stack.type = Stack(1) - scala> stack - res1: scala.collection.mutable.Stack[Int] = Stack(1) - scala> stack.push(2) - res0: stack.type = Stack(1, 2) - scala> stack - res3: scala.collection.mutable.Stack[Int] = Stack(1, 2) - scala> stack.top - res8: Int = 2 - scala> stack - res9: scala.collection.mutable.Stack[Int] = Stack(1, 2) - scala> stack.pop - res10: Int = 2 - scala> stack - res11: scala.collection.mutable.Stack[Int] = Stack(1) - -## Array Stacks - -[ArrayStack](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/ArrayStack.html) is an alternative implementation of a mutable stack which is backed by an Array that gets re-sized as needed. It provides fast indexing and is generally slightly more efficient for most operations than a normal mutable stack. - -## Hash Tables - -A hash table stores its elements in an underlying array, placing each item at a position in the array determined by the hash code of that item. Adding an element to a hash table takes only constant time, so long as there isn't already another element in the array that has the same hash code. Hash tables are thus very fast so long as the objects placed in them have a good distribution of hash codes. As a result, the default mutable map and set types in Scala are based on hash tables. You can access them also directly under the names [mutable.HashSet](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/HashSet.html) and [mutable.HashMap](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/HashMap.html). - -Hash sets and maps are used just like any other set or map. Here are some simple examples: - - scala> val map = scala.collection.mutable.HashMap.empty[Int,String] - map: scala.collection.mutable.HashMap[Int,String] = Map() - scala> map += (1 -> "make a web site") - res42: map.type = Map(1 -> make a web site) - scala> map += (3 -> "profit!") - res43: map.type = Map(1 -> make a web site, 3 -> profit!) - scala> map(1) - res44: String = make a web site - scala> map contains 2 - res46: Boolean = false - -Iteration over a hash table is not guaranteed to occur in any particular order. Iteration simply proceeds through the underlying array in whichever order it happens to be in. To get a guaranteed iteration order, use a _linked_ hash map or set instead of a regular one. A linked hash map or set is just like a regular hash map or set except that it also includes a linked list of the elements in the order they were added. Iteration over such a collection is always in the same order that the elements were initially added. - -## Weak Hash Maps - -A weak hash map is a special kind of hash map where the garbage collector does not follow links from the map to the keys stored in it. This means that a key and its associated value will disappear from the map if there is no other reference to that key. Weak hash maps are useful for tasks such as caching, where you want to re-use an expensive function's result if the function is called again on the same key. If keys and function results are stored in a regular hash map, the map could grow without bounds, and no key would ever become garbage. Using a weak hash map avoids this problem. As soon as a key object becomes unreachable, it's entry is removed from the weak hashmap. Weak hash maps in Scala are implemented by class [WeakHashMap](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/WeakHashMap.html) as a wrapper of an underlying Java implementation `java.util.WeakHashMap`. - -## Concurrent Maps - -A concurrent map can be accessed by several threads at once. In addition to the usual [Map](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Map.html) operations, it provides the following atomic operations: - -### Operations in class ConcurrentMap - -| WHAT IT IS | WHAT IT DOES | -| ------ | ------ | -| `m putIfAbsent(k, v)` |Adds key/value binding `k -> v` unless `k` is already defined in `m` | -| `m remove (k, v)` |Removes entry for `k` if it is currently mapped to `v`. | -| `m replace (k, old, new)` |Replaces value associated with key `k` to `new`, if it was previously bound to `old`. | -| `m replace (k, v)` |Replaces value associated with key `k` to `v`, if it was previously bound to some value.| - -`ConcurrentMap` is a trait in the Scala collections library. Currently, its only implementation is Java's `java.util.concurrent.ConcurrentMap`, which can be converted automatically into a Scala map using the [standard Java/Scala collection conversions]({{ site.baseurl }}/overviews/collections/conversions-between-java-and-scala-collections.html). - -## Mutable Bitsets - -A mutable bit of type [mutable.BitSet](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/BitSet.html) set is just like an immutable one, except that it is modified in place. Mutable bit sets are slightly more efficient at updating than immutable ones, because they don't have to copy around `Long`s that haven't changed. - - scala> val bits = scala.collection.mutable.BitSet.empty - bits: scala.collection.mutable.BitSet = BitSet() - scala> bits += 1 - res49: bits.type = BitSet(1) - scala> bits += 3 - res50: bits.type = BitSet(1, 3) - scala> bits - res51: scala.collection.mutable.BitSet = BitSet(1, 3) - - - - - - - - - - - - - - - - - - diff --git a/overviews/collections/conversions-between-java-and-scala-collections.md b/overviews/collections/conversions-between-java-and-scala-collections.md deleted file mode 100644 index ce773fc394..0000000000 --- a/overviews/collections/conversions-between-java-and-scala-collections.md +++ /dev/null @@ -1,62 +0,0 @@ ---- -layout: overview-large -title: Conversions Between Java and Scala Collections - -discourse: true - -partof: collections -num: 17 -languages: [ja, zh-cn] ---- - -Like Scala, Java also has a rich collections library. There are many similarities between the two. For instance, both libraries know iterators, iterables, sets, maps, and sequences. But there are also important differences. In particular, the Scala libraries put much more emphasis on immutable collections, and provide many more operations that transform a collection into a new one. - -Sometimes you might need to pass from one collection framework to the other. For instance, you might want to access an existing Java collection as if it were a Scala collection. Or you might want to pass one of Scala's collections to a Java method that expects its Java counterpart. It is quite easy to do this, because Scala offers implicit conversions between all the major collection types in the [JavaConverters](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/JavaConverters$.html) object. In particular, you will find bidirectional conversions between the following types. - - - Iterator <=> java.util.Iterator - Iterator <=> java.util.Enumeration - Iterable <=> java.lang.Iterable - Iterable <=> java.util.Collection - mutable.Buffer <=> java.util.List - mutable.Set <=> java.util.Set - mutable.Map <=> java.util.Map - mutable.ConcurrentMap <=> java.util.concurrent.ConcurrentMap - -To enable these conversions, simply import them from the [JavaConverters](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/JavaConverters$.html) object: - - scala> import collection.JavaConverters._ - import collection.JavaConverters._ - -This enables conversions between Scala collections and their corresponding Java collections by way of extension methods called `asScala` and `asJava`: - - scala> import collection.mutable._ - import collection.mutable._ - - scala> val jul: java.util.List[Int] = ArrayBuffer(1, 2, 3).asJava - jul: java.util.List[Int] = [1, 2, 3] - - scala> val buf: Seq[Int] = jul.asScala - buf: scala.collection.mutable.Seq[Int] = ArrayBuffer(1, 2, 3) - - scala> val m: java.util.Map[String, Int] = HashMap("abc" -> 1, "hello" -> 2).asJava - m: java.util.Map[String,Int] = {abc=1, hello=2} - -Internally, these conversion work by setting up a "wrapper" object that forwards all operations to the underlying collection object. So collections are never copied when converting between Java and Scala. An interesting property is that if you do a round-trip conversion from, say a Java type to its corresponding Scala type, and back to the same Java type, you end up with the identical collection object you have started with. - -Certain other Scala collections can also be converted to Java, but do not have a conversion back to the original Scala type: - - Seq => java.util.List - mutable.Seq => java.util.List - Set => java.util.Set - Map => java.util.Map - -Because Java does not distinguish between mutable and immutable collections in their type, a conversion from, say, `scala.immutable.List` will yield a `java.util.List`, where all mutation operations throw an "UnsupportedOperationException". Here's an example: - - scala> val jul = List(1, 2, 3).asJava - jul: java.util.List[Int] = [1, 2, 3] - - scala> jul.add(7) - java.lang.UnsupportedOperationException - at java.util.AbstractList.add(AbstractList.java:148) - diff --git a/overviews/collections/creating-collections-from-scratch.md b/overviews/collections/creating-collections-from-scratch.md deleted file mode 100644 index 4d0410d9cb..0000000000 --- a/overviews/collections/creating-collections-from-scratch.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -layout: overview-large -title: Creating Collections From Scratch - -discourse: true - -partof: collections -num: 16 -languages: [ja, zh-cn] ---- - -You have syntax `List(1, 2, 3)` to create a list of three integers and `Map('A' -> 1, 'C' -> 2)` to create a map with two bindings. This is actually a universal feature of Scala collections. You can take any collection name and follow it by a list of elements in parentheses. The result will be a new collection with the given elements. Here are some more examples: - - Traversable() // An empty traversable object - List() // The empty list - List(1.0, 2.0) // A list with elements 1.0, 2.0 - Vector(1.0, 2.0) // A vector with elements 1.0, 2.0 - Iterator(1, 2, 3) // An iterator returning three integers. - Set(dog, cat, bird) // A set of three animals - HashSet(dog, cat, bird) // A hash set of the same animals - Map('a' -> 7, 'b' -> 0) // A map from characters to integers - -"Under the covers" each of the above lines is a call to the `apply` method of some object. For instance, the third line above expands to - - List.apply(1.0, 2.0) - -So this is a call to the `apply` method of the companion object of the `List` class. That method takes an arbitrary number of arguments and constructs a list from them. Every collection class in the Scala library has a companion object with such an `apply` method. It does not matter whether the collection class represents a concrete implementation, like `List`, or `Stream` or `Vector`, do, or whether it is an abstract base class such as `Seq`, `Set` or `Traversable`. In the latter case, calling apply will produce some default implementation of the abstract base class. Examples: - - scala> List(1, 2, 3) - res17: List[Int] = List(1, 2, 3) - scala> Traversable(1, 2, 3) - res18: Traversable[Int] = List(1, 2, 3) - scala> mutable.Traversable(1, 2, 3) - res19: scala.collection.mutable.Traversable[Int] = ArrayBuffer(1, 2, 3) - -Besides `apply`, every collection companion object also defines a member `empty`, which returns an empty collection. So instead of `List()` you could write `List.empty`, instead of `Map()`, `Map.empty`, and so on. - -Descendants of `Seq` classes provide also other factory operations in their companion objects. These are summarized in the following table. In short, there's - -* `concat`, which concatenates an arbitrary number of traversables together, -* `fill` and `tabulate`, which generate single or multi-dimensional sequences of given dimensions initialized by some expression or tabulating function, -* `range`, which generates integer sequences with some constant step length, and -* `iterate`, which generates the sequence resulting from repeated application of a function to a start element. - -### Factory Methods for Sequences - -| WHAT IT IS | WHAT IT DOES | -| ------ | ------ | -| `S.empty` | The empty sequence. | -| `S(x, y, z)` | A sequence consisting of elements `x, y, z`. | -| `S.concat(xs, ys, zs)` | The sequence obtained by concatenating the elements of `xs, ys, zs`. | -| `S.fill(n){e}` | A sequence of length `n` where each element is computed by expression `e`. | -| `S.fill(m, n){e}` | A sequence of sequences of dimension `m×n` where each element is computed by expression `e`. (exists also in higher dimensions). | -| `S.tabulate(n){f}` | A sequence of length `n` where the element at each index i is computed by `f(i)`. | -| `S.tabulate(m, n){f}` | A sequence of sequences of dimension `m×n` where the element at each index `(i, j)` is computed by `f(i, j)`. (exists also in higher dimensions). | -| `S.range(start, end)` | The sequence of integers `start` ... `end-1`. | -| `S.range(start, end, step)`| The sequence of integers starting with `start` and progressing by `step` increments up to, and excluding, the `end` value. | -| `S.iterate(x, n)(f)` | The sequence of length `n` with elements `x`, `f(x)`, `f(f(x))`, ... | diff --git a/overviews/collections/equality.md b/overviews/collections/equality.md deleted file mode 100644 index ddc51c703a..0000000000 --- a/overviews/collections/equality.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -layout: overview-large -title: Equality - -discourse: true - -partof: collections -num: 13 -languages: [ja, zh-cn] ---- - -The collection libraries have a uniform approach to equality and hashing. The idea is, first, to divide collections into sets, maps, and sequences. Collections in different categories are always unequal. For instance, `Set(1, 2, 3)` is unequal to `List(1, 2, 3)` even though they contain the same elements. On the other hand, within the same category, collections are equal if and only if they have the same elements (for sequences: the same elements in the same order). For example, `List(1, 2, 3) == Vector(1, 2, 3)`, and `HashSet(1, 2) == TreeSet(2, 1)`. - -It does not matter for the equality check whether a collection is mutable or immutable. For a mutable collection one simply considers its current elements at the time the equality test is performed. This means that a mutable collection might be equal to different collections at different times, depending what elements are added or removed. This is a potential trap when using a mutable collection as a key in a hashmap. Example: - - scala> import collection.mutable.{HashMap, ArrayBuffer} - import collection.mutable.{HashMap, ArrayBuffer} - scala> val buf = ArrayBuffer(1, 2, 3) - buf: scala.collection.mutable.ArrayBuffer[Int] = - ArrayBuffer(1, 2, 3) - scala> val map = HashMap(buf -> 3) - map: scala.collection.mutable.HashMap[scala.collection. - mutable.ArrayBuffer[Int],Int] = Map((ArrayBuffer(1, 2, 3),3)) - scala> map(buf) - res13: Int = 3 - scala> buf(0) += 1 - scala> map(buf) - java.util.NoSuchElementException: key not found: - ArrayBuffer(2, 2, 3) - -In this example, the selection in the last line will most likely fail because the hash-code of the array `xs` has changed in the second-to-last line. Therefore, the hash-code-based lookup will look at a different place than the one where `xs` was stored. diff --git a/overviews/collections/introduction.md b/overviews/collections/introduction.md deleted file mode 100644 index ff5d82791f..0000000000 --- a/overviews/collections/introduction.md +++ /dev/null @@ -1,96 +0,0 @@ ---- -layout: overview-large -title: Introduction - -discourse: true - -partof: collections -num: 1 -languages: [ja, zh-cn] ---- - -**Martin Odersky, and Lex Spoon** - -In the eyes of many, the new collections framework is the most significant -change in the Scala 2.8 release. Scala had collections before (and in fact the new -framework is largely compatible with them). But it's only 2.8 that -provides a common, uniform, and all-encompassing framework for -collection types. - -Even though the additions to collections are subtle at first glance, -the changes they can provoke in your programming style can be -profound. In fact, quite often it's as if you work on a higher-level -with the basic building blocks of a program being whole collections -instead of their elements. This new style of programming requires some -adaptation. Fortunately, the adaptation is helped by several nice -properties of the new Scala collections. They are easy to use, -concise, safe, fast, universal. - -**Easy to use:** A small vocabulary of 20-50 methods is -enough to solve most collection problems in a couple of operations. No -need to wrap your head around complicated looping structures or -recursions. Persistent collections and side-effect-free operations mean -that you need not worry about accidentally corrupting existing -collections with new data. Interference between iterators and -collection updates is eliminated. - -**Concise:** You can achieve with a single word what used to -take one or several loops. You can express functional operations with -lightweight syntax and combine operations effortlessly, so that the result -feels like a custom algebra. - -**Safe:** This one has to be experienced to sink in. The -statically typed and functional nature of Scala's collections means -that the overwhelming majority of errors you might make are caught at -compile-time. The reason is that (1) the collection operations -themselves are heavily used and therefore well -tested. (2) the usages of the collection operation make inputs and -output explicit as function parameters and results. (3) These explicit -inputs and outputs are subject to static type checking. The bottom line -is that the large majority of misuses will manifest themselves as type -errors. It's not at all uncommon to have programs of several hundred -lines run at first try. - -**Fast:** Collection operations are tuned and optimized in the -libraries. As a result, using collections is typically quite -efficient. You might be able to do a little bit better with carefully -hand-tuned data structures and operations, but you might also do a lot -worse by making some suboptimal implementation decisions along the -way. What's more, collections have been recently adapted to parallel -execution on multi-cores. Parallel collections support the same -operations as sequential ones, so no new operations need to be learned -and no code needs to be rewritten. You can turn a sequential collection into a -parallel one simply by invoking the `par` method. - -**Universal:** Collections provide the same operations on -any type where it makes sense to do so. So you can achieve a lot with -a fairly small vocabulary of operations. For instance, a string is -conceptually a sequence of characters. Consequently, in Scala -collections, strings support all sequence operations. The same holds -for arrays. - -**Example:** Here's one line of code that demonstrates many of the -advantages of Scala's collections. - - val (minors, adults) = people partition (_.age < 18) - -It's immediately clear what this operation does: It partitions a -collection of `people` into `minors` and `adults` depending on -their age. Because the `partition` method is defined in the root -collection type `TraversableLike`, this code works for any kind of -collection, including arrays. The resulting `minors` and `adults` -collections will be of the same type as the `people` collection. - -This code is much more concise than the one to three loops required for -traditional collection processing (three loops for an array, because -the intermediate results need to be buffered somewhere else). Once -you have learned the basic collection vocabulary you will also find -writing this code is much easier and safer than writing explicit -loops. Furthermore, the `partition` operation is quite fast, and will -get even faster on parallel collections on multi-cores. (Parallel -collections have been released -as part of Scala 2.9.) - -This document provides an in depth discussion of the APIs of the -Scala collections classes from a user perspective. It takes you on -a tour of all the fundamental classes and the methods they define. diff --git a/overviews/collections/iterators.md b/overviews/collections/iterators.md deleted file mode 100644 index fa04d15dd9..0000000000 --- a/overviews/collections/iterators.md +++ /dev/null @@ -1,215 +0,0 @@ ---- -layout: overview-large -title: Iterators - -discourse: true - -partof: collections -num: 15 -languages: [ja, zh-cn] ---- - -An iterator is not a collection, but rather a way to access the elements of a collection one by one. The two basic operations on an iterator `it` are `next` and `hasNext`. A call to `it.next()` will return the next element of the iterator and advance the state of the iterator. Calling `next` again on the same iterator will then yield the element one beyond the one returned previously. If there are no more elements to return, a call to `next` will throw a `NoSuchElementException`. You can find out whether there are more elements to return using [Iterator](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Iterator.html)'s `hasNext` method. - -The most straightforward way to "step through" all the elements returned by an iterator `it` uses a while-loop: - - while (it.hasNext) - println(it.next()) - -Iterators in Scala also provide analogues of most of the methods that you find in the `Traversable`, `Iterable` and `Seq` classes. For instance, they provide a `foreach` method which executes a given procedure on each element returned by an iterator. Using `foreach`, the loop above could be abbreviated to: - - it foreach println - -As always, for-expressions can be used as an alternate syntax for expressions involving `foreach`, `map`, `withFilter`, and `flatMap`, so yet another way to print all elements returned by an iterator would be: - - for (elem <- it) println(elem) - -There's an important difference between the foreach method on iterators and the same method on traversable collections: When called on an iterator, `foreach` will leave the iterator at its end when it is done. So calling `next` again on the same iterator will fail with a `NoSuchElementException`. By contrast, when called on a collection, `foreach` leaves the number of elements in the collection unchanged (unless the passed function adds to removes elements, but this is discouraged, because it may lead to surprising results). - -The other operations that Iterator has in common with `Traversable` have the same property. For instance, iterators provide a `map` method, which returns a new iterator: - - scala> val it = Iterator("a", "number", "of", "words") - it: Iterator[java.lang.String] = non-empty iterator - scala> it.map(_.length) - res1: Iterator[Int] = non-empty iterator - scala> res1 foreach println - 1 - 6 - 2 - 5 - scala> it.next() - java.util.NoSuchElementException: next on empty iterator - -As you can see, after the call to `it.map`, the `it` iterator has advanced to its end. - -Another example is the `dropWhile` method, which can be used to find the first elements of an iterator that has a certain property. For instance, to find the first word in the iterator above that has at least two characters you could write: - - scala> val it = Iterator("a", "number", "of", "words") - it: Iterator[java.lang.String] = non-empty iterator - scala> it dropWhile (_.length < 2) - res4: Iterator[java.lang.String] = non-empty iterator - scala> it.next() - res5: java.lang.String = number - -Note again that `it` was changed by the call to `dropWhile`: it now points to the second word "number" in the list. -In fact, `it` and the result `res4` returned by `dropWhile` will return exactly the same sequence of elements. - -One way to circumvent this behavior is to `duplicate` the underlying iterator instead of calling methods on it directly. -The _two_ iterators that result will each return exactly the same elements as the underlying iterator `it`: - - scala> val (words, ns) = Iterator("a", "number", "of", "words").duplicate - words: Iterator[String] = non-empty iterator - ns: Iterator[String] = non-empty iterator - - scala> val shorts = words.filter(_.length < 3).toList - shorts: List[String] = List(a, of) - - scala> val count = ns.map(_.length).sum - count: Int = 14 - -The two iterators work independently: advancing one does not affect the other, so that each can be -destructively modified by invoking arbitrary methods. This creates the illusion of iterating over -the elements twice, but the effect is achieved through internal buffering. -As usual, the underlying iterator `it` cannot be used directly and must be discarded. - -In summary, iterators behave like collections _if one never accesses an iterator again after invoking a method on it_. The Scala collection libraries make this explicit with an abstraction [TraversableOnce](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/TraversableOnce.html), which is a common superclass of [Traversable](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Traversable.html) and [Iterator](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Iterator.html). As the name implies, `TraversableOnce` objects can be traversed using `foreach` but the state of that object after the traversal is not specified. If the `TraversableOnce` object is in fact an `Iterator`, it will be at its end after the traversal, but if it is a `Traversable`, it will still exist as before. A common use case of `TraversableOnce` is as an argument type for methods that can take either an iterator or a traversable as argument. An example is the appending method `++` in class `Traversable`. It takes a `TraversableOnce` parameter, so you can append elements coming from either an iterator or a traversable collection. - -All operations on iterators are summarized below. - -### Operations in class Iterator - -| WHAT IT IS | WHAT IT DOES | -| ------ | ------ | -| **Abstract Methods:** | | -| `it.next()` | Returns next element on iterator and advances past it. | -| `it.hasNext` | Returns `true` if `it` can return another element. | -| **Variations:** | | -| `it.buffered` | A buffered iterator returning all elements of `it`. | -| `it grouped size` | An iterator that yields the elements returned by `it` in fixed-sized sequence "chunks". | -| `xs sliding size` | An iterator that yields the elements returned by `it` in sequences representing a sliding fixed-sized window. | -| **Duplication:** | | -| `it.duplicate` | A pair of iterators that each independently return all elements of `it`. | -| **Additions:** | | -| `it ++ jt` | An iterator returning all elements returned by iterator `it`, followed by all elements returned by iterator `jt`. | -| `it padTo (len, x)` | The iterator that first returns all elements of `it` and then follows that by copies of `x` until length `len` elements are returned overall. | -| **Maps:** | | -| `it map f` | The iterator obtained from applying the function `f` to every element returned from `it`. | -| `it flatMap f` | The iterator obtained from applying the iterator-valued function f to every element in `it` and appending the results. | -| `it collect f` | The iterator obtained from applying the partial function `f` to every element in `it` for which it is defined and collecting the results. | -| **Conversions:** | | -| `it.toArray` | Collects the elements returned by `it` in an array. | -| `it.toList` | Collects the elements returned by `it` in a list. | -| `it.toIterable` | Collects the elements returned by `it` in an iterable. | -| `it.toSeq` | Collects the elements returned by `it` in a sequence. | -| `it.toIndexedSeq` | Collects the elements returned by `it` in an indexed sequence. | -| `it.toStream` | Collects the elements returned by `it` in a stream. | -| `it.toSet` | Collects the elements returned by `it` in a set. | -| `it.toMap` | Collects the key/value pairs returned by `it` in a map. | -| **Copying:** | | -| `it copyToBuffer buf` | Copies all elements returned by `it` to buffer `buf`. | -| `it copyToArray(arr, s, n)`| Copies at most `n` elements returned by `it` to array `arr` starting at index `s`. The last two arguments are optional. | -| **Size Info:** | | -| `it.isEmpty` | Test whether the iterator is empty (opposite of `hasNext`). | -| `it.nonEmpty` | Test whether the collection contains elements (alias of `hasNext`). | -| `it.size` | The number of elements returned by `it`. Note: `it` will be at its end after this operation! | -| `it.length` | Same as `it.size`. | -| `it.hasDefiniteSize` | Returns `true` if `it` is known to return finitely many elements (by default the same as `isEmpty`). | -| **Element Retrieval Index Search:**| | -| `it find p` | An option containing the first element returned by `it` that satisfies `p`, or `None` is no element qualifies. Note: The iterator advances to after the element, or, if none is found, to the end. | -| `it indexOf x` | The index of the first element returned by `it` that equals `x`. Note: The iterator advances past the position of this element. | -| `it indexWhere p` | The index of the first element returned by `it` that satisfies `p`. Note: The iterator advances past the position of this element. | -| **Subiterators:** | | -| `it take n` | An iterator returning of the first `n` elements of `it`. Note: it will advance to the position after the `n`'th element, or to its end, if it contains less than `n` elements. | -| `it drop n` | The iterator that starts with the `(n+1)`'th element of `it`. Note: `it` will advance to the same position. | -| `it slice (m,n)` | The iterator that returns a slice of the elements returned from it, starting with the `m`'th element and ending before the `n`'th element. | -| `it takeWhile p` | An iterator returning elements from `it` as long as condition `p` is true. | -| `it dropWhile p` | An iterator skipping elements from `it` as long as condition `p` is `true`, and returning the remainder. | -| `it filter p` | An iterator returning all elements from `it` that satisfy the condition `p`. | -| `it withFilter p` | Same as `it` filter `p`. Needed so that iterators can be used in for-expressions. | -| `it filterNot p` | An iterator returning all elements from `it` that do not satisfy the condition `p`. | -| **Subdivisions:** | | -| `it partition p` | Splits `it` into a pair of two iterators: one returning all elements from `it` that satisfy the predicate `p`, the other returning all elements from `it` that do not. | -| `it span p` | Splits `it` into a pair of two iterators: one returning all elements of the prefix of `it` that satisfy the predicate `p`, the other returning all remaining elements of `it`. | -| **Element Conditions:** | | -| `it forall p` | A boolean indicating whether the predicate p holds for all elements returned by `it`. | -| `it exists p` | A boolean indicating whether the predicate p holds for some element in `it`. | -| `it count p` | The number of elements in `it` that satisfy the predicate `p`. | -| **Folds:** | | -| `it.foldLeft(z)(op)` | Apply binary operation `op` between successive elements returned by `it`, going left to right and starting with `z`. | -| `it.foldRight(z)(op)` | Apply binary operation `op` between successive elements returned by `it`, going right to left and starting with `z`. | -| `it reduceLeft op` | Apply binary operation `op` between successive elements returned by non-empty iterator `it`, going left to right. | -| `it reduceRight op` | Apply binary operation `op` between successive elements returned by non-empty iterator `it`, going right to left. | -| **Specific Folds:** | | -| `it.sum` | The sum of the numeric element values returned by iterator `it`. | -| `it.product` | The product of the numeric element values returned by iterator `it`. | -| `it.min` | The minimum of the ordered element values returned by iterator `it`. | -| `it.max` | The maximum of the ordered element values returned by iterator `it`. | -| **Zippers:** | | -| `it zip jt` | An iterator of pairs of corresponding elements returned from iterators `it` and `jt`. | -| `it zipAll (jt, x, y)` | An iterator of pairs of corresponding elements returned from iterators `it` and `jt`, where the shorter iterator is extended to match the longer one by appending elements `x` or `y`. | -| `it.zipWithIndex` | An iterator of pairs of elements returned from `it` with their indices. | -| **Update:** | | -| `it patch (i, jt, r)` | The iterator resulting from `it` by replacing `r` elements starting with `i` by the patch iterator `jt`. | -| **Comparison:** | | -| `it sameElements jt` | A test whether iterators it and `jt` return the same elements in the same order. Note: Using the iterators after this operation is undefined and subject to change. | -| **Strings:** | | -| `it addString (b, start, sep, end)`| Adds a string to `StringBuilder` `b` which shows all elements returned by `it` between separators `sep` enclosed in strings `start` and `end`. `start`, `sep`, `end` are all optional. | -| `it mkString (start, sep, end)` | Converts the collection to a string which shows all elements returned by `it` between separators `sep` enclosed in strings `start` and `end`. `start`, `sep`, `end` are all optional. | - -### Buffered iterators - -Sometimes you want an iterator that can "look ahead", so that you can inspect the next element to be returned without advancing past that element. Consider for instance, the task to skip leading empty strings from an iterator that returns a sequence of strings. You might be tempted to write the following - - - def skipEmptyWordsNOT(it: Iterator[String]) = - while (it.next().isEmpty) {} - -But looking at this code more closely, it's clear that this is wrong: The code will indeed skip leading empty strings, but it will also advance `it` past the first non-empty string! - -The solution to this problem is to use a buffered iterator. Class [BufferedIterator](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/BufferedIterator.html) is a subclass of [Iterator](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Iterator.html), which provides one extra method, `head`. Calling `head` on a buffered iterator will return its first element but will not advance the iterator. Using a buffered iterator, skipping empty words can be written as follows. - - def skipEmptyWords(it: BufferedIterator[String]) = - while (it.head.isEmpty) { it.next() } - -Every iterator can be converted to a buffered iterator by calling its `buffered` method. Here's an example: - - scala> val it = Iterator(1, 2, 3, 4) - it: Iterator[Int] = non-empty iterator - scala> val bit = it.buffered - bit: scala.collection.BufferedIterator[Int] = non-empty iterator - scala> bit.head - res10: Int = 1 - scala> bit.next() - res11: Int = 1 - scala> bit.next() - res12: Int = 2 - scala> bit.optionHead - res13: Option[Int] = Some(3) - -Note that calling `head` on the buffered iterator `bit` does not advance it. Therefore, the subsequent call `bit.next()` returns the same value as `bit.head`. - -As usual, the underlying iterator must not be used directly and must be discarded. - -The buffered iterator only buffers the next element when `head` is invoked. Other derived iterators, -such as those produced by `duplicate` and `partition`, may buffer arbitrary subsequences of the -underlying iterator. But iterators can be efficiently joined by adding them together with `++`: - - scala> def collapse(it: Iterator[Int]) = if (!it.hasNext) Iterator.empty else { - | var head = it.next - | val rest = if (head == 0) it.dropWhile(_ == 0) else it - | Iterator.single(head) ++ rest - | } - collapse: (it: Iterator[Int])Iterator[Int] - - scala> def collapse(it: Iterator[Int]) = { - | val (zeros, rest) = it.span(_ == 0) - | zeros.take(1) ++ rest - | } - collapse: (it: Iterator[Int])Iterator[Int] - - scala> collapse(Iterator(0, 0, 0, 1, 2, 3, 4)).toList - res14: List[Int] = List(0, 1, 2, 3, 4) - -In the second version of `collapse`, the unconsumed zeros are buffered internally. -In the first version, any leading zeros are dropped and the desired result constructed -as a concatenated iterator, which simply calls its two constituent iterators in turn. diff --git a/overviews/collections/maps.md b/overviews/collections/maps.md deleted file mode 100644 index b1ccb0d179..0000000000 --- a/overviews/collections/maps.md +++ /dev/null @@ -1,164 +0,0 @@ ---- -layout: overview-large -title: Maps - -discourse: true - -partof: collections -num: 7 -languages: [ja, zh-cn] ---- - -A [Map](http://www.scala-lang.org/api/current/scala/collection/Map.html) is an [Iterable](http://www.scala-lang.org/api/current/scala/collection/Iterable.html) consisting of pairs of keys and values (also named _mappings_ or _associations_). Scala's [Predef](http://www.scala-lang.org/api/current/scala/Predef$.html) object offers an implicit conversion that lets you write `key -> value` as an alternate syntax for the pair `(key, value)`. For instance `Map("x" -> 24, "y" -> 25, "z" -> 26)` means exactly the same as `Map(("x", 24), ("y", 25), ("z", 26))`, but reads better. - -The fundamental operations on maps are similar to those on sets. They are summarized in the following table and fall into the following categories: - -* **Lookup** operations `apply`, `get`, `getOrElse`, `contains`, and `isDefinedAt`. These turn maps into partial functions from keys to values. The fundamental lookup method for a map is: `def get(key): Option[Value]`. The operation "`m get key`" tests whether the map contains an association for the given `key`. If so, it returns the associated value in a `Some`. If no key is defined in the map, `get` returns `None`. Maps also define an `apply` method that returns the value associated with a given key directly, without wrapping it in an `Option`. If the key is not defined in the map, an exception is raised. -* **Additions and updates** `+`, `++`, `updated`, which let you add new bindings to a map or change existing bindings. -* **Removals** `-`, `--`, which remove bindings from a map. -* **Subcollection producers** `keys`, `keySet`, `keysIterator`, `values`, `valuesIterator`, which return a map's keys and values separately in various forms. -* **Transformations** `filterKeys` and `mapValues`, which produce a new map by filtering and transforming bindings of an existing map. - -### Operations in Class Map ### - -| WHAT IT IS | WHAT IT DOES | -| ------ | ------ | -| **Lookups:** | | -| `ms get k` |The value associated with key `k` in map `ms` as an option, `None` if not found.| -| `ms(k)` |(or, written out, `ms apply k`) The value associated with key `k` in map `ms`, or exception if not found.| -| `ms getOrElse (k, d)` |The value associated with key `k` in map `ms`, or the default value `d` if not found.| -| `ms contains k` |Tests whether `ms` contains a mapping for key `k`.| -| `ms isDefinedAt k` |Same as `contains`. | -| **Additions and Updates:**| | -| `ms + (k -> v)` |The map containing all mappings of `ms` as well as the mapping `k -> v` from key `k` to value `v`.| -| `ms + (k -> v, l -> w)` |The map containing all mappings of `ms` as well as the given key/value pairs.| -| `ms ++ kvs` |The map containing all mappings of `ms` as well as all key/value pairs of `kvs`.| -| `ms updated (k, v)` |Same as `ms + (k -> v)`.| -| **Removals:** | | -| `ms - k` |The map containing all mappings of `ms` except for any mapping of key `k`.| -| `ms - (k, l, m)` |The map containing all mappings of `ms` except for any mapping with the given keys.| -| `ms -- ks` |The map containing all mappings of `ms` except for any mapping with a key in `ks`.| -| **Subcollections:** | | -| `ms.keys` |An iterable containing each key in `ms`. | -| `ms.keySet` |A set containing each key in `ms`. | -| `ms.keyIterator` |An iterator yielding each key in `ms`. | -| `ms.values` |An iterable containing each value associated with a key in `ms`.| -| `ms.valuesIterator` |An iterator yielding each value associated with a key in `ms`.| -| **Transformation:** | | -| `ms filterKeys p` |A map view containing only those mappings in `ms` where the key satisfies predicate `p`.| -| `ms mapValues f` |A map view resulting from applying function `f` to each value associated with a key in `ms`.| - -Mutable maps support in addition the operations summarized in the following table. - - -### Operations in Class mutable.Map ### - -| WHAT IT IS | WHAT IT DOES | -| ------ | ------ | -| **Additions and Updates:**| | -| `ms(k) = v` |(Or, written out, `ms.update(x, v)`). Adds mapping from key `k` to value `v` to map ms as a side effect, overwriting any previous mapping of `k`.| -| `ms += (k -> v)` |Adds mapping from key `k` to value `v` to map `ms` as a side effect and returns `ms` itself.| -| `ms += (k -> v, l -> w)` |Adds the given mappings to `ms` as a side effect and returns `ms` itself.| -| `ms ++= kvs` |Adds all mappings in `kvs` to `ms` as a side effect and returns `ms` itself.| -| `ms put (k, v)` |Adds mapping from key `k` to value `v` to `ms` and returns any value previously associated with `k` as an option.| -| `ms getOrElseUpdate (k, d)`|If key `k` is defined in map `ms`, return its associated value. Otherwise, update `ms` with the mapping `k -> d` and return `d`.| -| **Removals:**| | -| `ms -= k` |Removes mapping with key `k` from ms as a side effect and returns `ms` itself.| -| `ms -= (k, l, m)` |Removes mappings with the given keys from `ms` as a side effect and returns `ms` itself.| -| `ms --= ks` |Removes all keys in `ks` from `ms` as a side effect and returns `ms` itself.| -| `ms remove k` |Removes any mapping with key `k` from `ms` and returns any value previously associated with `k` as an option.| -| `ms retain p` |Keeps only those mappings in `ms` that have a key satisfying predicate `p`.| -| `ms.clear()` |Removes all mappings from `ms`. | -| **Transformation:** | | -| `ms transform f` |Transforms all associated values in map `ms` with function `f`.| -| **Cloning:** | | -| `ms.clone` |Returns a new mutable map with the same mappings as `ms`.| - -The addition and removal operations for maps mirror those for sets. Like sets, mutable maps also support the non-destructive addition operations `+`, `-`, and `updated`, but they are used less frequently because they involve a copying of the mutable map. Instead, a mutable map `m` is usually updated "in place", using the two variants `m(key) = value` or `m += (key -> value)`. There is also the variant `m put (key, value)`, which returns an `Option` value that contains the value previously associated with `key`, or `None` if the `key` did not exist in the map before. - -The `getOrElseUpdate` is useful for accessing maps that act as caches. Say you have an expensive computation triggered by invoking a function `f`: - - scala> def f(x: String) = { - println("taking my time."); sleep(100) - x.reverse } - f: (x: String)String - -Assume further that `f` has no side-effects, so invoking it again with the same argument will always yield the same result. In that case you could save time by storing previously computed bindings of argument and results of `f` in a map and only computing the result of `f` if a result of an argument was not found there. One could say the map is a _cache_ for the computations of the function `f`. - - scala> val cache = collection.mutable.Map[String, String]() - cache: scala.collection.mutable.Map[String,String] = Map() - -You can now create a more efficient caching version of the `f` function: - - scala> def cachedF(s: String) = cache.getOrElseUpdate(s, f(s)) - cachedF: (s: String)String - scala> cachedF("abc") - taking my time. - res3: String = cba - scala> cachedF("abc") - res4: String = cba - -Note that the second argument to `getOrElseUpdate` is "by-name", so the computation of `f("abc")` above is only performed if `getOrElseUpdate` requires the value of its second argument, which is precisely if its first argument is not found in the `cache` map. You could also have implemented `cachedF` directly, using just basic map operations, but it would take more code to do so: - - def cachedF(arg: String) = cache get arg match { - case Some(result) => result - case None => - val result = f(x) - cache(arg) = result - result - } - -### Synchronized Sets and Maps ### - -To get a thread-safe mutable map, you can mix the `SynchronizedMap` trait into whatever particular map implementation you desire. For example, you can mix `SynchronizedMap` into `HashMap`, as shown in the code below. This example begins with an import of two traits, `Map` and `SynchronizedMap`, and one class, `HashMap`, from package `scala.collection.mutable`. The rest of the example is the definition of singleton object `MapMaker`, which declares one method, `makeMap`. The `makeMap` method declares its result type to be a mutable map of string keys to string values. - - import scala.collection.mutable.{Map, - SynchronizedMap, HashMap} - object MapMaker { - def makeMap: Map[String, String] = { - new HashMap[String, String] with - SynchronizedMap[String, String] { - override def default(key: String) = - "Why do you want to know?" - } - } - } - -
        Mixing in the `SynchronizedMap` trait.
        - -The first statement inside the body of `makeMap` constructs a new mutable `HashMap` that mixes in the `SynchronizedMap` trait: - - new HashMap[String, String] with - SynchronizedMap[String, String] - -Given this code, the Scala compiler will generate a synthetic subclass of `HashMap` that mixes in `SynchronizedMap`, and create (and return) an instance of it. This synthetic class will also override a method named `default`, because of this code: - - override def default(key: String) = - "Why do you want to know?" - -If you ask a map to give you the value for a particular key, but it doesn't have a mapping for that key, you'll by default get a `NoSuchElementException`. If you define a new map class and override the `default` method, however, your new map will return the value returned by `default` when queried with a non-existent key. Thus, the synthetic `HashMap` subclass generated by the compiler from the code in the synchronized map code will return the somewhat curt response string, `"Why do you want to know?"`, when queried with a non-existent key. - -Because the mutable map returned by the `makeMap` method mixes in the `SynchronizedMap` trait, it can be used by multiple threads at once. Each access to the map will be synchronized. Here's an example of the map being used, by one thread, in the interpreter: - - scala> val capital = MapMaker.makeMap - capital: scala.collection.mutable.Map[String,String] = Map() - scala> capital ++ List("US" -> "Washington", - "France" -> "Paris", "Japan" -> "Tokyo") - res0: scala.collection.mutable.Map[String,String] = - Map(France -> Paris, US -> Washington, Japan -> Tokyo) - scala> capital("Japan") - res1: String = Tokyo - scala> capital("New Zealand") - res2: String = Why do you want to know? - scala> capital += ("New Zealand" -> "Wellington") - scala> capital("New Zealand") - res3: String = Wellington - -You can create synchronized sets similarly to the way you create synchronized maps. For example, you could create a synchronized `HashSet` by mixing in the `SynchronizedSet` trait, like this: - - import scala.collection.mutable - val synchroSet = - new mutable.HashSet[Int] with - mutable.SynchronizedSet[Int] - -Finally, if you are thinking of using synchronized collections, you may also wish to consider the concurrent collections of `java.util.concurrent` instead. diff --git a/overviews/collections/migrating-from-scala-27.md b/overviews/collections/migrating-from-scala-27.md deleted file mode 100644 index 96417425bc..0000000000 --- a/overviews/collections/migrating-from-scala-27.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -layout: overview-large -title: Migrating from Scala 2.7 - -discourse: true - -partof: collections -num: 18 -outof: 18 -languages: [ja, zh-cn] ---- - -Porting your existing Scala applications to use the new collections should be almost automatic. There are only a couple of possible issues to take care of. - -Generally, the old functionality of Scala 2.7 collections has been left in place. Some features have been deprecated, which means they will removed in some future release. You will get a _deprecation warning_ when you compile code that makes use of these features in Scala 2.8. In a few places deprecation was unfeasible, because the operation in question was retained in 2.8, but changed in meaning or performance characteristics. These cases will be flagged with _migration warnings_ when compiled under 2.8. To get full deprecation and migration warnings with suggestions how to change your code, pass the `-deprecation` and `-Xmigration` flags to `scalac` (note that `-Xmigration` is an extended option, so it starts with an `X`). You can also pass the same options to the `scala` REPL to get the warnings in an interactive session. Example: - - >scala -deprecation -Xmigration - Welcome to Scala version 2.8.0.final - Type in expressions to have them evaluated. - Type :help for more information. - scala> val xs = List((1, 2), (3, 4)) - xs: List[(Int, Int)] = List((1,2), (3,4)) - scala> List.unzip(xs) - :7: warning: method unzip in object List is deprecated: use xs.unzip instead of List.unzip(xs) - List.unzip(xs) - ^ - res0: (List[Int], List[Int]) = (List(1, 3),List(2, 4)) - scala> xs.unzip - res1: (List[Int], List[Int]) = (List(1, 3),List(2, 4)) - scala> val m = xs.toMap - m: scala.collection.immutable.Map[Int,Int] = Map((1,2), (3,4)) - scala> m.keys - :8: warning: method keys in trait MapLike has changed semantics: - As of 2.8, keys returns Iterable[A] rather than Iterator[A]. - m.keys - ^ - res2: Iterable[Int] = Set(1, 3) - -There are two parts of the old libraries which have been replaced wholesale, and for which deprecation warnings were not feasible. - -1. The previous `scala.collection.jcl` package is gone. This package tried to mimick some of the Java collection library design in Scala, but in doing so broke many symmetries. Most people who wanted Java collections bypassed `jcl` and used `java.util` directly. Scala 2.8 offers automatic conversion mechanisms between both collection libraries in the [JavaConversions]({{ site.baseurl }}/overviews/collections/conversions-between-java-and-scala-collections.html) object which replaces the `jcl` package. -2. Projections have been generalized and cleaned up and are now available as views. It seems that projections were used rarely, so not much code should be affected by this change. - -So, if your code uses either `jcl` or projections there might be some minor rewriting to do. diff --git a/overviews/collections/overview.md b/overviews/collections/overview.md deleted file mode 100644 index 0ead02867e..0000000000 --- a/overviews/collections/overview.md +++ /dev/null @@ -1,142 +0,0 @@ ---- -layout: overview-large -title: Mutable and Immutable Collections - -discourse: true - -partof: collections -num: 2 -languages: [ja, zh-cn] ---- - -Scala collections systematically distinguish between mutable and -immutable collections. A _mutable_ collection can be updated or -extended in place. This means you can change, add, or remove elements -of a collection as a side effect. _Immutable_ collections, by -contrast, never change. You have still operations that simulate -additions, removals, or updates, but those operations will in each -case return a new collection and leave the old collection unchanged. - -All collection classes are found in the package `scala.collection` or -one of its sub-packages `mutable`, `immutable`, and `generic`. Most -collection classes needed by client code exist in three variants, -which are located in packages `scala.collection`, -`scala.collection.immutable`, and `scala.collection.mutable`, -respectively. Each variant has different characteristics with respect -to mutability. - -A collection in package `scala.collection.immutable` is guaranteed to -be immutable for everyone. Such a collection will never change after -it is created. Therefore, you can rely on the fact that accessing the -same collection value repeatedly at different points in time will -always yield a collection with the same elements. - -A collection in package `scala.collection.mutable` is known to have -some operations that change the collection in place. So dealing with -mutable collection means you need to understand which code changes -which collection when. - -A collection in package `scala.collection` can be either mutable or -immutable. For instance, [collection.IndexedSeq\[T\]](http://www.scala-lang.org/api/current/scala/collection/IndexedSeq.html) -is a superclass of both [collection.immutable.IndexedSeq\[T\]](http://www.scala-lang.org/api/current/scala/collection/immutable/IndexedSeq.html) -and -[collection.mutable.IndexedSeq\[T\]](http://www.scala-lang.org/api/current/scala/collection/mutable/IndexedSeq.html) -Generally, the root collections in -package `scala.collection` define the same interface as the immutable -collections, and the mutable collections in package -`scala.collection.mutable` typically add some side-effecting -modification operations to this immutable interface. - -The difference between root collections and immutable collections is -that clients of an immutable collection have a guarantee that nobody -can mutate the collection, whereas clients of a root collection only -promise not to change the collection themselves. Even though the -static type of such a collection provides no operations for modifying -the collection, it might still be possible that the run-time type is a -mutable collection which can be changed by other clients. - -By default, Scala always picks immutable collections. For instance, if -you just write `Set` without any prefix or without having imported -`Set` from somewhere, you get an immutable set, and if you write -`Iterable` you get an immutable iterable collection, because these -are the default bindings imported from the `scala` package. To get -the mutable default versions, you need to write explicitly -`collection.mutable.Set`, or `collection.mutable.Iterable`. - -A useful convention if you want to use both mutable and immutable -versions of collections is to import just the package -`collection.mutable`. - - import scala.collection.mutable - -Then a word like `Set` without a prefix still refers to an immutable collection, -whereas `mutable.Set` refers to the mutable counterpart. - -The last package in the collection hierarchy is `collection.generic`. This -package contains building blocks for implementing -collections. Typically, collection classes defer the implementations -of some of their operations to classes in `generic`. Users of the -collection framework on the other hand should need to refer to -classes in `generic` only in exceptional circumstances. - -For convenience and backwards compatibility some important types have -aliases in the `scala` package, so you can use them by their simple -names without needing an import. An example is the `List` type, which -can be accessed alternatively as - - scala.collection.immutable.List // that's where it is defined - scala.List // via the alias in the scala package - List // because scala._ - // is always automatically imported - -Other types aliased are -[Traversable](http://www.scala-lang.org/api/current/scala/collection/Traversable.html), [Iterable](http://www.scala-lang.org/api/current/scala/collection/Iterable.html), [Seq](http://www.scala-lang.org/api/current/scala/collection/Seq.html), [IndexedSeq](http://www.scala-lang.org/api/current/scala/collection/IndexedSeq.html), [Iterator](http://www.scala-lang.org/api/current/scala/collection/Iterator.html), [Stream](http://www.scala-lang.org/api/current/scala/collection/immutable/Stream.html), [Vector](http://www.scala-lang.org/api/current/scala/collection/immutable/Vector.html), [StringBuilder](http://www.scala-lang.org/api/current/scala/collection/mutable/StringBuilder.html), and [Range](http://www.scala-lang.org/api/current/scala/collection/immutable/Range.html). - -The following figure shows all collections in package -`scala.collection`. These are all high-level abstract classes or traits, which -generally have mutable as well as immutable implementations. - -[]({{ site.baseurl }}/resources/images/collections.png) - -The following figure shows all collections in package `scala.collection.immutable`. - -[]({{ site.baseurl }}/resources/images/collections.immutable.png) - -And the following figure shows all collections in package `scala.collection.mutable`. - -[]({{ site.baseurl }}/resources/images/collections.mutable.png) - -(All three figures were generated by Matthias at decodified.com). - -## An Overview of the Collections API ## - -The most important collection classes are shown in the figures above. There is quite a bit of commonality shared by all these classes. For instance, every kind of collection can be created by the same uniform syntax, writing the collection class name followed by its elements: - - Traversable(1, 2, 3) - Iterable("x", "y", "z") - Map("x" -> 24, "y" -> 25, "z" -> 26) - Set(Color.red, Color.green, Color.blue) - SortedSet("hello", "world") - Buffer(x, y, z) - IndexedSeq(1.0, 2.0) - LinearSeq(a, b, c) - -The same principle also applies for specific collection implementations, such as: - - List(1, 2, 3) - HashMap("x" -> 24, "y" -> 25, "z" -> 26) - -All these collections get displayed with `toString` in the same way they are written above. - -All collections support the API provided by `Traversable`, but specialize types wherever this makes sense. For instance the `map` method in class `Traversable` returns another `Traversable` as its result. But this result type is overridden in subclasses. For instance, calling `map` on a `List` yields again a `List`, calling it on a `Set` yields again a `Set` and so on. - - scala> List(1, 2, 3) map (_ + 1) - res0: List[Int] = List(2, 3, 4) - scala> Set(1, 2, 3) map (_ * 2) - res0: Set[Int] = Set(2, 4, 6) - -This behavior which is implemented everywhere in the collections libraries is called the _uniform return type principle_. - -Most of the classes in the collections hierarchy exist in three variants: root, mutable, and immutable. The only exception is the Buffer trait which only exists as a mutable collection. - -In the following, we will review these classes one by one. diff --git a/overviews/collections/performance-characteristics.md b/overviews/collections/performance-characteristics.md deleted file mode 100644 index 7beae19cab..0000000000 --- a/overviews/collections/performance-characteristics.md +++ /dev/null @@ -1,84 +0,0 @@ ---- -layout: overview-large -title: Performance Characteristics - -discourse: true - -partof: collections -num: 12 -languages: [ja, zh-cn] ---- - -The previous explanations have made it clear that different collection types have different performance characteristics. That's often the primary reason for picking one collection type over another. You can see the performance characteristics of some common operations on collections summarized in the following two tables. - -Performance characteristics of sequence types: - -| | head | tail | apply | update| prepend | append | insert | -| -------- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | -| **immutable** | | | | | | | | -| `List` | C | C | L | L | C | L | - | -| `Stream` | C | C | L | L | C | L | - | -| `Vector` | eC | eC | eC | eC | eC | eC | - | -| `Stack` | C | C | L | L | C | L | L | -| `Queue` | aC | aC | L | L | C | C | - | -| `Range` | C | C | C | - | - | - | - | -| `String` | C | L | C | L | L | L | - | -| **mutable** | | | | | | | | -| `ArrayBuffer` | C | L | C | C | L | aC | L | -| `ListBuffer` | C | L | L | L | C | C | L | -|`StringBuilder`| C | L | C | C | L | aC | L | -| `MutableList` | C | L | L | L | C | C | L | -| `Queue` | C | L | L | L | C | C | L | -| `ArraySeq` | C | L | C | C | - | - | - | -| `Stack` | C | L | L | L | C | L | L | -| `ArrayStack` | C | L | C | C | aC | L | L | -| `Array` | C | L | C | C | - | - | - | - -Performance characteristics of set and map types: - -| | lookup | add | remove | min | -| -------- | ---- | ---- | ---- | ---- | -| **immutable** | | | | | -| `HashSet`/`HashMap`| eC | eC | eC | L | -| `TreeSet`/`TreeMap`| Log | Log | Log | Log | -| `BitSet` | C | L | L | eC1| -| `ListMap` | L | L | L | L | -| **mutable** | | | | | -| `HashSet`/`HashMap`| eC | eC | eC | L | -| `WeakHashMap` | eC | eC | eC | L | -| `BitSet` | C | aC | C | eC1| -| `TreeSet` | Log | Log | Log | Log | - -Footnote: 1 Assuming bits are densely packed. - -The entries in these two tables are explained as follows: - -| | | -| --- | ---- | -| **C** | The operation takes (fast) constant time. | -| **eC** | The operation takes effectively constant time, but this might depend on some assumptions such as maximum length of a vector or distribution of hash keys.| -| **aC** | The operation takes amortized constant time. Some invocations of the operation might take longer, but if many operations are performed on average only constant time per operation is taken. | -| **Log** | The operation takes time proportional to the logarithm of the collection size. | -| **L** | The operation is linear, that is it takes time proportional to the collection size. | -| **-** | The operation is not supported. | - -The first table treats sequence types--both immutable and mutable--with the following operations: - -| | | -| --- | ---- | -| **head** | Selecting the first element of the sequence. | -| **tail** | Producing a new sequence that consists of all elements except the first one. | -| **apply** | Indexing. | -| **update** | Functional update (with `updated`) for immutable sequences, side-effecting update (with `update` for mutable sequences). | -| **prepend**| Adding an element to the front of the sequence. For immutable sequences, this produces a new sequence. For mutable sequences it modified the existing sequence. | -| **append** | Adding an element and the end of the sequence. For immutable sequences, this produces a new sequence. For mutable sequences it modified the existing sequence. | -| **insert** | Inserting an element at an arbitrary position in the sequence. This is only supported directly for mutable sequences. | - -The second table treats mutable and immutable sets and maps with the following operations: - -| | | -| --- | ---- | -| **lookup** | Testing whether an element is contained in set, or selecting a value associated with a key. | -| **add** | Adding a new element to a set or key/value pair to a map. | -| **remove** | Removing an element from a set or a key from a map. | -| **min** | The smallest element of the set, or the smallest key of a map. | diff --git a/overviews/collections/seqs.md b/overviews/collections/seqs.md deleted file mode 100644 index 49d1093f8d..0000000000 --- a/overviews/collections/seqs.md +++ /dev/null @@ -1,101 +0,0 @@ ---- -layout: overview-large -title: The sequence traits Seq, IndexedSeq, and LinearSeq - -discourse: true - -partof: collections -num: 5 -languages: [ja, zh-cn] ---- - -The [Seq](http://www.scala-lang.org/api/current/scala/collection/Seq.html) trait represents sequences. A sequence is a kind of iterable that has a `length` and whose elements have fixed index positions, starting from `0`. - -The operations on sequences, summarized in the table below, fall into the following categories: - -* **Indexing and length** operations `apply`, `isDefinedAt`, `length`, `indices`, and `lengthCompare`. For a `Seq`, the `apply` operation means indexing; hence a sequence of type `Seq[T]` is a partial function that takes an `Int` argument (an index) and which yields a sequence element of type `T`. In other words `Seq[T]` extends `PartialFunction[Int, T]`. The elements of a sequence are indexed from zero up to the `length` of the sequence minus one. The `length` method on sequences is an alias of the `size` method of general collections. The `lengthCompare` method allows you to compare the lengths of a sequences with an Int even if the sequences has infinite length. -* **Index search operations** `indexOf`, `lastIndexOf`, `indexOfSlice`, `lastIndexOfSlice`, `indexWhere`, `lastIndexWhere`, `segmentLength`, `prefixLength`, which return the index of an element equal to a given value or matching some predicate. -* **Addition operations** `+:`, `:+`, `padTo`, which return new sequences obtained by adding elements at the front or the end of a sequence. -* **Update operations** `updated`, `patch`, which return a new sequence obtained by replacing some elements of the original sequence. -* **Sorting operations** `sorted`, `sortWith`, `sortBy`, which sort sequence elements according to various criteria. -* **Reversal operations** `reverse`, `reverseIterator`, `reverseMap`, which yield or process sequence elements in reverse order. -* **Comparisons** `startsWith`, `endsWith`, `contains`, `containsSlice`, `corresponds`, which relate two sequences or search an element in a sequence. -* **Multiset** operations `intersect`, `diff`, `union`, `distinct`, which perform set-like operations on the elements of two sequences or remove duplicates. - -If a sequence is mutable, it offers in addition a side-effecting `update` method, which lets sequence elements be updated. As always in Scala, syntax like `seq(idx) = elem` is just a shorthand for `seq.update(idx, elem)`, so `update` gives convenient assignment syntax for free. Note the difference between `update` and `updated`. `update` changes a sequence element in place, and is only available for mutable sequences. `updated` is available for all sequences and always returns a new sequence instead of modifying the original. - -### Operations in Class Seq ### - -| WHAT IT IS | WHAT IT DOES | -| ------ | ------ | -| **Indexing and Length:** | | -| `xs(i)` |(or, written out, `xs apply i`). The element of `xs` at index `i`.| -| `xs isDefinedAt i` |Tests whether `i` is contained in `xs.indices`.| -| `xs.length` |The length of the sequence (same as `size`).| -| `xs lengthCompare n` |Returns `-1` if `xs` is shorter than `n`, `+1` if it is longer, and `0` if it is of length `n`. Works even if the sequence is infinite, for example `Stream.from(1) lengthCompare 42` equals `+1`.| -| `xs.indices` |The index range of `xs`, extending from `0` to `xs.length - 1`.| -| **Index Search:** | | -| `xs indexOf x` |The index of the first element in `xs` equal to `x` (several variants exist).| -| `xs lastIndexOf x` |The index of the last element in `xs` equal to `x` (several variants exist).| -| `xs indexOfSlice ys` |The first index of `xs` such that successive elements starting from that index form the sequence `ys`.| -| `xs lastIndexOfSlice ys` |The last index of `xs` such that successive elements starting from that index form the sequence `ys`.| -| `xs indexWhere p` |The index of the first element in xs that satisfies `p` (several variants exist).| -| `xs segmentLength (p, i)`|The length of the longest uninterrupted segment of elements in `xs`, starting with `xs(i)`, that all satisfy the predicate `p`.| -| `xs prefixLength p` |The length of the longest prefix of elements in `xs` that all satisfy the predicate `p`.| -| **Additions:** | | -| `x +: xs` |A new sequence that consists of `x` prepended to `xs`.| -| `xs :+ x` |A new sequence that consists of `x` appended to `xs`.| -| `xs padTo (len, x)` |The sequence resulting from appending the value `x` to `xs` until length `len` is reached.| -| **Updates:** | | -| `xs patch (i, ys, r)` |The sequence resulting from replacing `r` elements of `xs` starting with `i` by the patch `ys`.| -| `xs updated (i, x)` |A copy of `xs` with the element at index `i` replaced by `x`.| -| `xs(i) = x` |(or, written out, `xs.update(i, x)`, only available for `mutable.Seq`s). Changes the element of `xs` at index `i` to `x`.| -| **Sorting:** | | -| `xs.sorted` |A new sequence obtained by sorting the elements of `xs` using the standard ordering of the element type of `xs`.| -| `xs sortWith lt` |A new sequence obtained by sorting the elements of `xs` using `lt` as comparison operation.| -| `xs sortBy f` |A new sequence obtained by sorting the elements of `xs`. Comparison between two elements proceeds by mapping the function `f` over both and comparing the results.| -| **Reversals:** | | -| `xs.reverse` |A sequence with the elements of `xs` in reverse order.| -| `xs.reverseIterator` |An iterator yielding all the elements of `xs` in reverse order.| -| `xs reverseMap f` |A sequence obtained by mapping `f` over the elements of `xs` in reverse order.| -| **Comparisons:** | | -| `xs startsWith ys` |Tests whether `xs` starts with sequence `ys` (several variants exist).| -| `xs endsWith ys` |Tests whether `xs` ends with sequence `ys` (several variants exist).| -| `xs contains x` |Tests whether `xs` has an element equal to `x`.| -| `xs containsSlice ys` |Tests whether `xs` has a contiguous subsequence equal to `ys`.| -| `(xs corresponds ys)(p)` |Tests whether corresponding elements of `xs` and `ys` satisfy the binary predicate `p`.| -| **Multiset Operations:** | | -| `xs intersect ys` |The multi-set intersection of sequences `xs` and `ys` that preserves the order of elements in `xs`.| -| `xs diff ys` |The multi-set difference of sequences `xs` and `ys` that preserves the order of elements in `xs`.| -| `xs union ys` |Multiset union; same as `xs ++ ys`.| -| `xs.distinct` |A subsequence of `xs` that contains no duplicated element.| - -Trait [Seq](http://www.scala-lang.org/api/current/scala/collection/Seq.html) has two subtraits [LinearSeq](http://www.scala-lang.org/api/current/scala/collection/IndexedSeq.html), and [IndexedSeq](http://www.scala-lang.org/api/current/scala/collection/IndexedSeq.html). These do not add any new operations, but each offers different performance characteristics: A linear sequence has efficient `head` and `tail` operations, whereas an indexed sequence has efficient `apply`, `length`, and (if mutable) `update` operations. Frequently used linear sequences are `scala.collection.immutable.List` and `scala.collection.immutable.Stream`. Frequently used indexed sequences are `scala.Array` and `scala.collection.mutable.ArrayBuffer`. The `Vector` class provides an interesting compromise between indexed and linear access. It has both effectively constant time indexing overhead and constant time linear access overhead. Because of this, vectors are a good foundation for mixed access patterns where both indexed and linear accesses are used. You'll learn more on vectors [later](http://docs.scala-lang.org/overviews/collections/concrete-immutable-collection-classes.html#vectors). - -### Buffers ### - -An important sub-category of mutable sequences is `Buffer`s. They allow not only updates of existing elements but also element insertions, element removals, and efficient additions of new elements at the end of the buffer. The principal new methods supported by a buffer are `+=` and `++=` for element addition at the end, `+=:` and `++=:` for addition at the front, `insert` and `insertAll` for element insertions, as well as `remove` and `-=` for element removal. These operations are summarized in the following table. - -Two often used implementations of buffers are `ListBuffer` and `ArrayBuffer`. As the name implies, a `ListBuffer` is backed by a `List`, and supports efficient conversion of its elements to a `List`, whereas an `ArrayBuffer` is backed by an array, and can be quickly converted into one. - -#### Operations in Class Buffer #### - -| WHAT IT IS | WHAT IT DOES| -| ------ | ------ | -| **Additions:** | | -| `buf += x` |Appends element `x` to buffer, and returns `buf` itself as result.| -| `buf += (x, y, z)` |Appends given elements to buffer.| -| `buf ++= xs` |Appends all elements in `xs` to buffer.| -| `x +=: buf` |Prepends element `x` to buffer.| -| `xs ++=: buf` |Prepends all elements in `xs` to buffer.| -| `buf insert (i, x)` |Inserts element `x` at index `i` in buffer.| -| `buf insertAll (i, xs)` |Inserts all elements in `xs` at index `i` in buffer.| -| **Removals:** | | -| `buf -= x` |Removes element `x` from buffer.| -| `buf remove i` |Removes element at index `i` from buffer.| -| `buf remove (i, n)` |Removes `n` elements starting at index `i` from buffer.| -| `buf trimStart n` |Removes first `n` elements from buffer.| -| `buf trimEnd n` |Removes last `n` elements from buffer.| -| `buf.clear()` |Removes all elements from buffer.| -| **Cloning:** | | -| `buf.clone` |A new buffer with the same elements as `buf`.| diff --git a/overviews/collections/sets.md b/overviews/collections/sets.md deleted file mode 100644 index 274359caa8..0000000000 --- a/overviews/collections/sets.md +++ /dev/null @@ -1,148 +0,0 @@ ---- -layout: overview-large -title: Sets - -discourse: true - -partof: collections -num: 6 -languages: [ja, zh-cn] ---- - -`Set`s are `Iterable`s that contain no duplicate elements. The operations on sets are summarized in the following table for general sets and in the table after that for mutable sets. They fall into the following categories: - -* **Tests** `contains`, `apply`, `subsetOf`. The `contains` method asks whether a set contains a given element. The `apply` method for a set is the same as `contains`, so `set(elem)` is the same as `set contains elem`. That means sets can also be used as test functions that return true for the elements they contain. - -For example: - - - scala> val fruit = Set("apple", "orange", "peach", "banana") - fruit: scala.collection.immutable.Set[java.lang.String] = Set(apple, orange, peach, banana) - scala> fruit("peach") - res0: Boolean = true - scala> fruit("potato") - res1: Boolean = false - - -* **Additions** `+` and `++`, which add one or more elements to a set, yielding a new set. -* **Removals** `-`, `--`, which remove one or more elements from a set, yielding a new set. -* **Set operations** for union, intersection, and set difference. Each of these operations exists in two forms: alphabetic and symbolic. The alphabetic versions are `intersect`, `union`, and `diff`, whereas the symbolic versions are `&`, `|`, and `&~`. In fact, the `++` that Set inherits from `Traversable` can be seen as yet another alias of `union` or `|`, except that `++` takes a `Traversable` argument whereas `union` and `|` take sets. - -### Operations in Class Set ### - -| WHAT IT IS | WHAT IT DOES | -| ------ | ------ | -| **Tests:** | | -| `xs contains x` |Tests whether `x` is an element of `xs`. | -| `xs(x)` |Same as `xs contains x`. | -| `xs subsetOf ys` |Tests whether `xs` is a subset of `ys`. | -| **Additions:** | | -| `xs + x` |The set containing all elements of `xs` as well as `x`.| -| `xs + (x, y, z)` |The set containing all elements of `xs` as well as the given additional elements.| -| `xs ++ ys` |The set containing all elements of `xs` as well as all elements of `ys`.| -| **Removals:** | | -| `xs - x` |The set containing all elements of `xs` except `x`.| -| `xs - (x, y, z)` |The set containing all elements of `xs` except the given elements.| -| `xs -- ys` |The set containing all elements of `xs` except the elements of `ys`.| -| `xs.empty` |An empty set of the same class as `xs`. | -| **Binary Operations:** | | -| `xs & ys` |The set intersection of `xs` and `ys`. | -| `xs intersect ys` |Same as `xs & ys`. | -| xs | ys |The set union of `xs` and `ys`. | -| `xs union ys` |Same as xs | ys. | -| `xs &~ ys` |The set difference of `xs` and `ys`. | -| `xs diff ys` |Same as `xs &~ ys`. | - -Mutable sets offer in addition methods to add, remove, or update elements, which are summarized in below. - -### Operations in Class mutable.Set ### - -| WHAT IT IS | WHAT IT DOES | -| ------ | ------ | -| **Additions:** | | -| `xs += x` |Adds element `x` to set `xs` as a side effect and returns `xs` itself.| -| `xs += (x, y, z)` |Adds the given elements to set `xs` as a side effect and returns `xs` itself.| -| `xs ++= ys` |Adds all elements in `ys` to set `xs` as a side effect and returns `xs` itself.| -| `xs add x` |Adds element `x` to `xs` and returns `true` if `x` was not previously contained in the set, `false` if it was.| -| **Removals:** | | -| `xs -= x` |Removes element `x` from set `xs` as a side effect and returns `xs` itself.| -| `xs -= (x, y, z)` |Removes the given elements from set `xs` as a side effect and returns `xs` itself.| -| `xs --= ys` |Removes all elements in `ys` from set `xs` as a side effect and returns `xs` itself.| -| `xs remove x` |Removes element `x` from `xs` and returns `true` if `x` was previously contained in the set, `false` if it was not.| -| `xs retain p` |Keeps only those elements in `xs` that satisfy predicate `p`.| -| `xs.clear()` |Removes all elements from `xs`.| -| **Update:** | | -| `xs(x) = b` |(or, written out, `xs.update(x, b)`). If boolean argument `b` is `true`, adds `x` to `xs`, otherwise removes `x` from `xs`.| -| **Cloning:** | | -| `xs.clone` |A new mutable set with the same elements as `xs`.| - -Just like an immutable set, a mutable set offers the `+` and `++` operations for element additions and the `-` and `--` operations for element removals. But these are less often used for mutable sets since they involve copying the set. As a more efficient alternative, mutable sets offer the update methods `+=` and `-=`. The operation `s += elem` adds `elem` to the set `s` as a side effect, and returns the mutated set as a result. Likewise, `s -= elem` removes `elem` from the set, and returns the mutated set as a result. Besides `+=` and `-=` there are also the bulk operations `++=` and `--=` which add or remove all elements of a traversable or an iterator. - -The choice of the method names `+=` and `-=` means that very similar code can work with either mutable or immutable sets. Consider first the following REPL dialogue which uses an immutable set `s`: - - scala> var s = Set(1, 2, 3) - s: scala.collection.immutable.Set[Int] = Set(1, 2, 3) - scala> s += 4 - scala> s -= 2 - scala> s - res2: scala.collection.immutable.Set[Int] = Set(1, 3, 4) - -We used `+=` and `-=` on a `var` of type `immutable.Set`. A statement such as `s += 4` is an abbreviation for `s = s + 4`. So this invokes the addition method `+` on the set `s` and then assigns the result back to the `s` variable. Consider now an analogous interaction with a mutable set. - - - scala> val s = collection.mutable.Set(1, 2, 3) - s: scala.collection.mutable.Set[Int] = Set(1, 2, 3) - scala> s += 4 - res3: s.type = Set(1, 4, 2, 3) - scala> s -= 2 - res4: s.type = Set(1, 4, 3) - -The end effect is very similar to the previous interaction; we start with a `Set(1, 2, 3)` and end up with a `Set(1, 3, 4)`. However, even though the statements look the same as before, they do something different. `s += 4` now invokes the `+=` method on the mutable set value `s`, changing the set in place. Likewise, `s -= 2` now invokes the `-=` method on the same set. - -Comparing the two interactions shows an important principle. You often can replace a mutable collection stored in a `val` by an immutable collection stored in a `var`, and _vice versa_. This works at least as long as there are no alias references to the collection through which one can observe whether it was updated in place or whether a new collection was created. - -Mutable sets also provide add and remove as variants of `+=` and `-=`. The difference is that `add` and `remove` return a Boolean result indicating whether the operation had an effect on the set. - -The current default implementation of a mutable set uses a hashtable to store the set's elements. The default implementation of an immutable set uses a representation that adapts to the number of elements of the set. An empty set is represented by just a singleton object. Sets of sizes up to four are represented by a single object that stores all elements as fields. Beyond that size, immutable sets are implemented as [hash tries](concrete-immutable-collection-classes.html#hash_tries). - -A consequence of these representation choices is that, for sets of small sizes (say up to 4), immutable sets are usually more compact and also more efficient than mutable sets. So, if you expect the size of a set to be small, try making it immutable. - -Two subtraits of sets are `SortedSet` and `BitSet`. - -### Sorted Sets ### - -A [SortedSet](http://www.scala-lang.org/api/current/scala/collection/SortedSet.html) is a set that produces its elements (using `iterator` or `foreach`) in a given ordering (which can be freely chosen at the time the set is created). The default representation of a [SortedSet](http://www.scala-lang.org/api/current/scala/collection/SortedSet.html) is an ordered binary tree which maintains the invariant that all elements in the left subtree of a node are smaller than all elements in the right subtree. That way, a simple in order traversal can return all tree elements in increasing order. Scala's class [immutable.TreeSet](http://www.scala-lang.org/api/current/scala/collection/immutable/TreeSet.html) uses a _red-black_ tree implementation to maintain this ordering invariant and at the same time keep the tree _balanced_-- meaning that all paths from the root of the tree to a leaf have lengths that differ only by at most one element. - -To create an empty [TreeSet](http://www.scala-lang.org/api/current/scala/collection/immutable/TreeSet.html), you could first specify the desired ordering: - - scala> val myOrdering = Ordering.fromLessThan[String](_ > _) - myOrdering: scala.math.Ordering[String] = ... - -Then, to create an empty tree set with that ordering, use: - - scala> TreeSet.empty(myOrdering) - res1: scala.collection.immutable.TreeSet[String] = TreeSet() - -Or you can leave out the ordering argument but give an element type or the empty set. In that case, the default ordering on the element type will be used. - - scala> TreeSet.empty[String] - res2: scala.collection.immutable.TreeSet[String] = TreeSet() - -If you create new sets from a tree-set (for instance by concatenation or filtering) they will keep the same ordering as the original set. For instance, - - scala> res2 + ("one", "two", "three", "four") - res3: scala.collection.immutable.TreeSet[String] = TreeSet(four, one, three, two) - -Sorted sets also support ranges of elements. For instance, the `range` method returns all elements from a starting element up to, but excluding, an end element. Or, the `from` method returns all elements greater or equal than a starting element in the set's ordering. The result of calls to both methods is again a sorted set. Examples: - - scala> res3 range ("one", "two") - res4: scala.collection.immutable.TreeSet[String] = TreeSet(one, three) - scala> res3 from "three" - res5: scala.collection.immutable.TreeSet[String] = TreeSet(three, two) - - -### Bitsets ### - -Bitsets are sets of non-negative integer elements that are implemented in one or more words of packed bits. The internal representation of a [BitSet](http://www.scala-lang.org/api/current/scala/collection/BitSet.html) uses an array of `Long`s. The first `Long` covers elements from 0 to 63, the second from 64 to 127, and so on (Immutable bitsets of elements in the range of 0 to 127 optimize the array away and store the bits directly in a one or two `Long` fields.) For every `Long`, each of its 64 bits is set to 1 if the corresponding element is contained in the set, and is unset otherwise. It follows that the size of a bitset depends on the largest integer that's stored in it. If `N` is that largest integer, then the size of the set is `N/64` `Long` words, or `N/8` bytes, plus a small number of extra bytes for status information. - -Bitsets are hence more compact than other sets if they contain many small elements. Another advantage of bitsets is that operations such as membership test with `contains`, or element addition and removal with `+=` and `-=` are all extremely efficient. diff --git a/overviews/collections/strings.md b/overviews/collections/strings.md deleted file mode 100644 index 985b179f75..0000000000 --- a/overviews/collections/strings.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -layout: overview-large -title: Strings - -discourse: true - -partof: collections -num: 11 -languages: [ja, zh-cn] ---- - -Like arrays, strings are not directly sequences, but they can be converted to them, and they also support all sequence operations on strings. Here are some examples of operations you can invoke on strings. - - scala> val str = "hello" - str: java.lang.String = hello - scala> str.reverse - res6: String = olleh - scala> str.map(_.toUpper) - res7: String = HELLO - scala> str drop 3 - res8: String = lo - scala> str slice (1, 4) - res9: String = ell - scala> val s: Seq[Char] = str - s: Seq[Char] = WrappedString(h, e, l, l, o) - -These operations are supported by two implicit conversions. The first, low-priority conversion maps a `String` to a `WrappedString`, which is a subclass of `immutable.IndexedSeq`, This conversion got applied in the last line above where a string got converted into a Seq. the other, high-priority conversion maps a string to a `StringOps` object, which adds all methods on immutable sequences to strings. This conversion was implicitly inserted in the method calls of `reverse`, `map`, `drop`, and `slice` in the example above. \ No newline at end of file diff --git a/overviews/collections/trait-iterable.md b/overviews/collections/trait-iterable.md deleted file mode 100644 index 85f57e93a8..0000000000 --- a/overviews/collections/trait-iterable.md +++ /dev/null @@ -1,65 +0,0 @@ ---- -layout: overview-large -title: Trait Iterable - -discourse: true - -partof: collections -num: 4 -languages: [ja, zh-cn] ---- - -The next trait from the top in the collections hierarchy is `Iterable`. All methods in this trait are defined in terms of an abstract method, `iterator`, which yields the collection's elements one by one. The `foreach` method from trait `Traversable` is implemented in `Iterable` in terms of `iterator`. Here is the actual implementation: - - def foreach[U](f: Elem => U): Unit = { - val it = iterator - while (it.hasNext) f(it.next()) - } - -Quite a few subclasses of `Iterable` override this standard implementation of foreach in `Iterable`, because they can provide a more efficient implementation. Remember that `foreach` is the basis of the implementation of all operations in `Traversable`, so its performance matters. - -Two more methods exist in `Iterable` that return iterators: `grouped` and `sliding`. These iterators, however, do not return single elements but whole subsequences of elements of the original collection. The maximal size of these subsequences is given as an argument to these methods. The `grouped` method returns its elements in "chunked" increments, where `sliding` yields a sliding "window" over the elements. The difference between the two should become clear by looking at the following REPL interaction: - - scala> val xs = List(1, 2, 3, 4, 5) - xs: List[Int] = List(1, 2, 3, 4, 5) - scala> val git = xs grouped 3 - git: Iterator[List[Int]] = non-empty iterator - scala> git.next() - res3: List[Int] = List(1, 2, 3) - scala> git.next() - res4: List[Int] = List(4, 5) - scala> val sit = xs sliding 3 - sit: Iterator[List[Int]] = non-empty iterator - scala> sit.next() - res5: List[Int] = List(1, 2, 3) - scala> sit.next() - res6: List[Int] = List(2, 3, 4) - scala> sit.next() - res7: List[Int] = List(3, 4, 5) - -Trait `Iterable` also adds some other methods to `Traversable` that can be implemented efficiently only if an iterator is available. They are summarized in the following table. - -### Operations in Trait Iterable ### - -| WHAT IT IS | WHAT IT DOES | -| ------ | ------ | -| **Abstract Method:** | | -| `xs.iterator` |An `iterator` that yields every element in `xs`, in the same order as `foreach` traverses elements.| -| **Other Iterators:** | | -| `xs grouped size` |An iterator that yields fixed-sized "chunks" of this collection.| -| `xs sliding size` |An iterator that yields a sliding fixed-sized window of elements in this collection.| -| **Subcollections:** | | -| `xs takeRight n` |A collection consisting of the last `n` elements of `xs` (or, some arbitrary `n` elements, if no order is defined).| -| `xs dropRight n` |The rest of the collection except `xs takeRight n`.| -| **Zippers:** | | -| `xs zip ys` |An iterable of pairs of corresponding elements from `xs` and `ys`.| -| `xs zipAll (ys, x, y)` |An iterable of pairs of corresponding elements from `xs` and `ys`, where the shorter sequence is extended to match the longer one by appending elements `x` or `y`.| -| `xs.zipWithIndex` |An iterable of pairs of elements from `xs` with their indices.| -| **Comparison:** | | -| `xs sameElements ys` |A test whether `xs` and `ys` contain the same elements in the same order| - -In the inheritance hierarchy below Iterable you find three traits: [Seq](https://www.scala-lang.org/api/current/scala/collection/Seq.html), [Set](https://www.scala-lang.org/api/current/scala/collection/Set.html), and [Map](https://www.scala-lang.org/api/current/scala/collection/Map.html). `Seq` and `Map` implement the [PartialFunction](https://www.scala-lang.org/api/current/scala/PartialFunction.html) trait with its `apply` and `isDefinedAt` methods, each implemented differently. `Set` gets its `apply` method from [GenSetLike](https://www.scala-lang.org/api/current/scala/collection/GenSetLike.html). - -For sequences, `apply` is positional indexing, where elements are always numbered from `0`. That is, `Seq(1, 2, 3)(1)` gives `2`. For sets, `apply` is a membership test. For instance, `Set('a', 'b', 'c')('b')` gives `true` whereas `Set()('a')` gives `false`. Finally for maps, `apply` is a selection. For instance, `Map('a' -> 1, 'b' -> 10, 'c' -> 100)('b')` gives `10`. - -In the following, we will explain each of the three kinds of collections in more detail. diff --git a/overviews/collections/trait-traversable.md b/overviews/collections/trait-traversable.md deleted file mode 100644 index a3809b9629..0000000000 --- a/overviews/collections/trait-traversable.md +++ /dev/null @@ -1,109 +0,0 @@ ---- -layout: overview-large -title: Trait Traversable - -discourse: true - -partof: collections -num: 3 -languages: [ja, zh-cn] ---- - -At the top of the collection hierarchy is trait `Traversable`. Its only abstract operation is `foreach`: - - def foreach[U](f: Elem => U) - -Collection classes that implement `Traversable` just need to define this method; all other methods can be inherited from `Traversable`. - -The `foreach` method is meant to traverse all elements of the collection, and apply the given operation, f, to each element. The type of the operation is `Elem => U`, where `Elem` is the type of the collection's elements and `U` is an arbitrary result type. The invocation of `f` is done for its side effect only; in fact any function result of f is discarded by `foreach`. - -`Traversable` also defines many concrete methods, which are all listed in the following table. These methods fall into the following categories: - -* **Addition**, `++`, which appends two traversables together, or appends all elements of an iterator to a traversable. -* **Map** operations `map`, `flatMap`, and `collect`, which produce a new collection by applying some function to collection elements. -* **Conversions** `toArray`, `toList`, `toIterable`, `toSeq`, `toIndexedSeq`, `toStream`, `toSet`, `toMap`, which turn a `Traversable` collection into something more specific. All these conversions return their receiver argument unchanged if the run-time type of the collection already matches the demanded collection type. For instance, applying `toList` to a list will yield the list itself. -* **Copying operations** `copyToBuffer` and `copyToArray`. As their names imply, these copy collection elements to a buffer or array, respectively. -* **Size info** operations `isEmpty`, `nonEmpty`, `size`, and `hasDefiniteSize`: Traversable collections can be finite or infinite. An example of an infinite traversable collection is the stream of natural numbers `Stream.from(0)`. The method `hasDefiniteSize` indicates whether a collection is possibly infinite. If `hasDefiniteSize` returns true, the collection is certainly finite. If it returns false, the collection has not been not fully elaborated yet, so it might be infinite or finite. -* **Element retrieval** operations `head`, `last`, `headOption`, `lastOption`, and `find`. These select the first or last element of a collection, or else the first element matching a condition. Note, however, that not all collections have a well-defined meaning of what "first" and "last" means. For instance, a hash set might store elements according to their hash keys, which might change from run to run. In that case, the "first" element of a hash set could also be different for every run of a program. A collection is _ordered_ if it always yields its elements in the same order. Most collections are ordered, but some (_e.g._ hash sets) are not-- dropping the ordering gives a little bit of extra efficiency. Ordering is often essential to give reproducible tests and to help in debugging. That's why Scala collections give ordered alternatives for all collection types. For instance, the ordered alternative for `HashSet` is `LinkedHashSet`. -* **Sub-collection retrieval operations** `tail`, `init`, `slice`, `take`, `drop`, `takeWhile`, `dropWhile`, `filter`, `filterNot`, `withFilter`. These all return some sub-collection identified by an index range or some predicate. -* **Subdivision operations** `splitAt`, `span`, `partition`, `groupBy`, which split the elements of this collection into several sub-collections. -* **Element tests** `exists`, `forall`, `count` which test collection elements with a given predicate. -* **Folds** `foldLeft`, `foldRight`, `/:`, `:\`, `reduceLeft`, `reduceRight` which apply a binary operation to successive elements. -* **Specific folds** `sum`, `product`, `min`, `max`, which work on collections of specific types (numeric or comparable). -* **String** operations `mkString`, `addString`, `stringPrefix`, which give alternative ways of converting a collection to a string. -* **View** operations, consisting of two overloaded variants of the `view` method. A view is a collection that's evaluated lazily. You'll learn more about views in [later](#Views). - -### Operations in Class Traversable ### - -| WHAT IT IS | WHAT IT DOES | -| ------ | ------ | -| **Abstract Method:** | | -| `xs foreach f` |Executes function `f` for every element of `xs`.| -| **Addition:** | | -| `xs ++ ys` |A collection consisting of the elements of both `xs` and `ys`. `ys` is a [TraversableOnce](http://www.scala-lang.org/api/current/scala/collection/TraversableOnce.html) collection, i.e., either a [Traversable](http://www.scala-lang.org/api/current/scala/collection/Traversable.html) or an [Iterator](http://www.scala-lang.org/api/current/scala/collection/Iterator.html).| -| **Maps:** | | -| `xs map f` |The collection obtained from applying the function f to every element in `xs`.| -| `xs flatMap f` |The collection obtained from applying the collection-valued function `f` to every element in `xs` and concatenating the results.| -| `xs collect f` |The collection obtained from applying the partial function `f` to every element in `xs` for which it is defined and collecting the results.| -| **Conversions:** | | -| `xs.toArray` |Converts the collection to an array. | -| `xs.toList` |Converts the collection to a list. | -| `xs.toIterable` |Converts the collection to an iterable. | -| `xs.toSeq` |Converts the collection to a sequence. | -| `xs.toIndexedSeq` |Converts the collection to an indexed sequence. | -| `xs.toStream` |Converts the collection to a lazily computed stream.| -| `xs.toSet` |Converts the collection to a set. | -| `xs.toMap` |Converts the collection of key/value pairs to a map. If the collection does not have pairs as elements, calling this operation results in a static type error.| -| **Copying:** | | -| `xs copyToBuffer buf` |Copies all elements of the collection to buffer `buf`.| -| `xs copyToArray(arr, s, n)`|Copies at most `n` elements of the collection to array `arr` starting at index `s`. The last two arguments are optional.| -| **Size info:** | | -| `xs.isEmpty` |Tests whether the collection is empty. | -| `xs.nonEmpty` |Tests whether the collection contains elements. | -| `xs.size` |The number of elements in the collection. | -| `xs.hasDefiniteSize` |True if `xs` is known to have finite size. | -| **Element Retrieval:** | | -| `xs.head` |The first element of the collection (or, some element, if no order is defined).| -| `xs.headOption` |The first element of `xs` in an option value, or None if `xs` is empty.| -| `xs.last` |The last element of the collection (or, some element, if no order is defined).| -| `xs.lastOption` |The last element of `xs` in an option value, or None if `xs` is empty.| -| `xs find p` |An option containing the first element in `xs` that satisfies `p`, or `None` if no element qualifies.| -| **Subcollections:** | | -| `xs.tail` |The rest of the collection except `xs.head`. | -| `xs.init` |The rest of the collection except `xs.last`. | -| `xs slice (from, to)` |A collection consisting of elements in some index range of `xs` (from `from` up to, and excluding `to`).| -| `xs take n` |A collection consisting of the first `n` elements of `xs` (or, some arbitrary `n` elements, if no order is defined).| -| `xs drop n` |The rest of the collection except `xs take n`.| -| `xs takeWhile p` |The longest prefix of elements in the collection that all satisfy `p`.| -| `xs dropWhile p` |The collection without the longest prefix of elements that all satisfy `p`.| -| `xs filter p` |The collection consisting of those elements of xs that satisfy the predicate `p`.| -| `xs withFilter p` |A non-strict filter of this collection. Subsequent calls to `map`, `flatMap`, `foreach`, and `withFilter` will only apply to those elements of `xs` for which the condition `p` is true.| -| `xs filterNot p` |The collection consisting of those elements of `xs` that do not satisfy the predicate `p`.| -| **Subdivisions:** | | -| `xs splitAt n` |Split `xs` at a position, giving the pair of collections `(xs take n, xs drop n)`.| -| `xs span p` |Split `xs` according to a predicate, giving the pair of collections `(xs takeWhile p, xs.dropWhile p)`.| -| `xs partition p` |Split `xs` into a pair of collections; one with elements that satisfy the predicate `p`, the other with elements that do not, giving the pair of collections `(xs filter p, xs.filterNot p)`| -| `xs groupBy f` |Partition `xs` into a map of collections according to a discriminator function `f`.| -| **Element Conditions:** | | -| `xs forall p` |A boolean indicating whether the predicate `p` holds for all elements of `xs`.| -| `xs exists p` |A boolean indicating whether the predicate `p` holds for some element in `xs`.| -| `xs count p` |The number of elements in `xs` that satisfy the predicate `p`.| -| **Folds:** | | -| `(z /: xs)(op)` |Apply binary operation `op` between successive elements of `xs`, going left to right and starting with `z`.| -| `(xs :\ z)(op)` |Apply binary operation `op` between successive elements of `xs`, going right to left and starting with `z`.| -| `xs.foldLeft(z)(op)` |Same as `(z /: xs)(op)`.| -| `xs.foldRight(z)(op)` |Same as `(xs :\ z)(op)`.| -| `xs reduceLeft op` |Apply binary operation `op` between successive elements of non-empty collection `xs`, going left to right.| -| `xs reduceRight op` |Apply binary operation `op` between successive elements of non-empty collection `xs`, going right to left.| -| **Specific Folds:** | | -| `xs.sum` |The sum of the numeric element values of collection `xs`.| -| `xs.product` |The product of the numeric element values of collection `xs`.| -| `xs.min` |The minimum of the ordered element values of collection `xs`.| -| `xs.max` |The maximum of the ordered element values of collection `xs`.| -| **Strings:** | | -| `xs addString (b, start, sep, end)`|Adds a string to `StringBuilder` `b` that shows all elements of `xs` between separators `sep` enclosed in strings `start` and `end`. `start`, `sep`, `end` are all optional.| -| `xs mkString (start, sep, end)`|Converts the collection to a string that shows all elements of `xs` between separators `sep` enclosed in strings `start` and `end`. `start`, `sep`, `end` are all optional.| -| `xs.stringPrefix` |The collection name at the beginning of the string returned from `xs.toString`.| -| **Views:** | | -| `xs.view` |Produces a view over `xs`.| -| `xs view (from, to)` |Produces a view that represents the elements in some index range of `xs`.| diff --git a/overviews/collections/views.md b/overviews/collections/views.md deleted file mode 100644 index 64960c2f38..0000000000 --- a/overviews/collections/views.md +++ /dev/null @@ -1,129 +0,0 @@ ---- -layout: overview-large -title: Views - -discourse: true - -partof: collections -num: 14 -languages: [ja, zh-cn] ---- - -Collections have quite a few methods that construct new collections. Examples are `map`, `filter` or `++`. We call such methods transformers because they take at least one collection as their receiver object and produce another collection as their result. - -There are two principal ways to implement transformers. One is _strict_, that is a new collection with all its elements is constructed as a result of the transformer. The other is non-strict or _lazy_, that is one constructs only a proxy for the result collection, and its elements get constructed only as one demands them. - -As an example of a non-strict transformer consider the following implementation of a lazy map operation: - - def lazyMap[T, U](coll: Iterable[T], f: T => U) = new Iterable[U] { - def iterator = coll.iterator map f - } - -Note that `lazyMap` constructs a new `Iterable` without stepping through all elements of the given collection `coll`. The given function `f` is instead applied to the elements of the new collection's `iterator` as they are demanded. - -Scala collections are by default strict in all their transformers, except for `Stream`, which implements all its transformer methods lazily. However, there is a systematic way to turn every collection into a lazy one and _vice versa_, which is based on collection views. A _view_ is a special kind of collection that represents some base collection, but implements all transformers lazily. - -To go from a collection to its view, you can use the view method on the collection. If `xs` is some collection, then `xs.view` is the same collection, but with all transformers implemented lazily. To get back from a view to a strict collection, you can use the `force` method. - -Let's see an example. Say you have a vector of Ints over which you want to map two functions in succession: - - scala> val v = Vector(1 to 10: _*) - v: scala.collection.immutable.Vector[Int] = - Vector(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) - scala> v map (_ + 1) map (_ * 2) - res5: scala.collection.immutable.Vector[Int] = - Vector(4, 6, 8, 10, 12, 14, 16, 18, 20, 22) - -In the last statement, the expression `v map (_ + 1)` constructs a new vector which is then transformed into a third vector by the second call to `map (_ * 2)`. In many situations, constructing the intermediate result from the first call to map is a bit wasteful. In the example above, it would be faster to do a single map with the composition of the two functions `(_ + 1)` and `(_ * 2)`. If you have the two functions available in the same place you can do this by hand. But quite often, successive transformations of a data structure are done in different program modules. Fusing those transformations would then undermine modularity. A more general way to avoid the intermediate results is by turning the vector first into a view, then applying all transformations to the view, and finally forcing the view to a vector: - - scala> (v.view map (_ + 1) map (_ * 2)).force - res12: Seq[Int] = Vector(4, 6, 8, 10, 12, 14, 16, 18, 20, 22) - -Let's do this sequence of operations again, one by one: - - scala> val vv = v.view - vv: scala.collection.SeqView[Int,Vector[Int]] = - SeqView(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) - -The application `v.view` gives you a `SeqView`, i.e. a lazily evaluated `Seq`. The type `SeqView` has two type parameters. The first, `Int`, shows the type of the view's elements. The second, `Vector[Int]` shows you the type constructor you get back when forcing the `view`. - -Applying the first `map` to the view gives: - - scala> vv map (_ + 1) - res13: scala.collection.SeqView[Int,Seq[_]] = SeqViewM(...) - -The result of the `map` is a value that prints `SeqViewM(...)`. This is in essence a wrapper that records the fact that a `map` with function `(_ + 1)` needs to be applied on the vector `v`. It does not apply that map until the view is `force`d, however. The "M" after `SeqView` is an indication that the view encapsulates a map operation. Other letters indicate other delayed operations. For instance "S" indicates a delayed `slice` operations, and "R" indicates a `reverse`. Let's now apply the second `map` to the last result. - - scala> res13 map (_ * 2) - res14: scala.collection.SeqView[Int,Seq[_]] = SeqViewMM(...) - -You now get a `SeqView` that contains two map operations, so it prints with a double "M": `SeqViewMM(...)`. Finally, forcing the last result gives: - - scala> res14.force - res15: Seq[Int] = Vector(4, 6, 8, 10, 12, 14, 16, 18, 20, 22) - -Both stored functions get applied as part of the execution of the `force` operation and a new vector is constructed. That way, no intermediate data structure is needed. - -One detail to note is that the static type of the final result is a Seq, not a Vector. Tracing the types back we see that as soon as the first delayed map was applied, the result had static type `SeqViewM[Int, Seq[_]]`. That is, the "knowledge" that the view was applied to the specific sequence type `Vector` got lost. The implementation of a view for some class requires quite a lot of code, so the Scala collection libraries provide views mostly only for general collection types, but not for specific implementations (An exception to this are arrays: Applying delayed operations on arrays will again give results with static type `Array`). - -There are two reasons why you might want to consider using views. The first is performance. You have seen that by switching a collection to a view the construction of intermediate results can be avoided. These savings can be quite important. As another example, consider the problem of finding the first palindrome in a list of words. A palindrome is a word which reads backwards the same as forwards. Here are the necessary definitions: - - def isPalindrome(x: String) = x == x.reverse - def findPalidrome(s: Seq[String]) = s find isPalindrome - -Now, assume you have a very long sequence words and you want to find a palindrome in the first million words of that sequence. Can you re-use the definition of `findPalidrome`? Of course, you could write: - - findPalindrome(words take 1000000) - -This nicely separates the two aspects of taking the first million words of a sequence and finding a palindrome in it. But the downside is that it always constructs an intermediary sequence consisting of one million words, even if the first word of that sequence is already a palindrome. So potentially, 999'999 words are copied into the intermediary result without being inspected at all afterwards. Many programmers would give up here and write their own specialized version of finding palindromes in some given prefix of an argument sequence. But with views, you don't have to. Simply write: - - findPalindrome(words.view take 1000000) - -This has the same nice separation of concerns, but instead of a sequence of a million elements it will only construct a single lightweight view object. This way, you do not need to choose between performance and modularity. - -The second use case applies to views over mutable sequences. Many transformer functions on such views provide a window into the original sequence that can then be used to update selectively some elements of that sequence. To see this in an example, let's suppose you have an array `arr`: - - scala> val arr = (0 to 9).toArray - arr: Array[Int] = Array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9) - -You can create a subwindow into that array by creating a slice of a view of `arr`: - - scala> val subarr = arr.view.slice(3, 6) - subarr: scala.collection.mutable.IndexedSeqView[ - Int,Array[Int]] = IndexedSeqViewS(...) - -This gives a view `subarr` which refers to the elements at positions 3 through 5 of the array `arr`. The view does not copy these elements, it just provides a reference to them. Now, assume you have a method that modifies some elements of a sequence. For instance, the following `negate` method would negate all elements of the sequence of integers it's given: - - scala> def negate(xs: collection.mutable.Seq[Int]) = - for (i <- 0 until xs.length) xs(i) = -xs(i) - negate: (xs: scala.collection.mutable.Seq[Int])Unit - -Assume now you want to negate elements at positions 3 through five of the array `arr`. Can you use `negate` for this? Using a view, this is simple: - - scala> negate(subarr) - scala> arr - res4: Array[Int] = Array(0, 1, 2, -3, -4, -5, 6, 7, 8, 9) - -What happened here is that negate changed all elements of `subarr`, which was originally a slice of the array `arr`. Again, you see that views help in keeping things modular. The code above nicely separated the question of what index range to apply a method to from the question what method to apply. - -After having seen all these nifty uses of views you might wonder why have strict collections at all? One reason is that performance comparisons do not always favor lazy over strict collections. For smaller collection sizes the added overhead of forming and applying closures in views is often greater than the gain from avoiding the intermediary data structures. A probably more important reason is that evaluation in views can be very confusing if the delayed operations have side effects. - -Here's an example which bit a few users of versions of Scala before 2.8. In these versions the Range type was lazy, so it behaved in effect like a view. People were trying to create a number of actors like this: - - - val actors = for (i <- 1 to 10) yield actor { ... } - -They were surprised that none of the actors was executing afterwards, even though the actor method should create and start an actor from the code that's enclosed in the braces following it. To explain why nothing happened, remember that the for expression above is equivalent to an application of map: - - val actors = (1 to 10) map (i => actor { ... }) - -Since previously the range produced by `(1 to 10)` behaved like a view, the result of the map was again a view. That is, no element was computed, and, consequently, no actor was created! Actors would have been created by forcing the range of the whole expression, but it's far from obvious that this is what was required to make the actors do their work. - -To avoid surprises like this, the Scala 2.8 collections library has more regular rules. All collections except streams and views are strict. The only way to go from a strict to a lazy collection is via the `view` method. The only way to go back is via `force`. So the `actors` definition above would behave as expected in Scala 2.8 in that it would create and start 10 actors. To get back the surprising previous behavior, you'd have to add an explicit `view` method call: - - val actors = for (i <- (1 to 10).view) yield actor { ... } - -In summary, views are a powerful tool to reconcile concerns of efficiency with concerns of modularity. But in order not to be entangled in aspects of delayed evaluation, you should restrict views to two scenarios. Either you apply views in purely functional code where collection transformations do not have side effects. Or you apply them over mutable collections where all modifications are done explicitly. What's best avoided is a mixture of views and operations that create new collections while also having side effects. - - - diff --git a/overviews/core/_posts/2010-09-07-collections.md b/overviews/core/_posts/2010-09-07-collections.md deleted file mode 100644 index 8e6c122982..0000000000 --- a/overviews/core/_posts/2010-09-07-collections.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -layout: overview -title: Scala's Collections Library -discourse: true -partof: collections -overview: collections -languages: [ja] ---- diff --git a/overviews/core/_posts/2010-11-30-actors.md b/overviews/core/_posts/2010-11-30-actors.md deleted file mode 100644 index b4e8cf64d8..0000000000 --- a/overviews/core/_posts/2010-11-30-actors.md +++ /dev/null @@ -1,503 +0,0 @@ ---- -layout: overview -title: The Scala Actors API -overview: actors -languages: [zh-cn, es] ---- - -**Philipp Haller and Stephen Tu** - -## Introduction - -This guide describes the API of the `scala.actors` package of Scala 2.8/2.9. The organization follows groups of types that logically belong together. The trait hierarchy is taken into account to structure the individual sections. The focus is on the run-time behavior of the various methods that these traits define, thereby complementing the existing Scaladoc-based API documentation. - -NOTE: In Scala 2.10 the Actors library is deprecated and will be removed in future Scala releases. Users should use [Akka](http://akka.io) actors from the package `akka.actor`. For migration from Scala actors to Akka refer to the [Actors Migration Guide](actors-migration-guide.html). - -## The actor traits Reactor, ReplyReactor, and Actor - -### The Reactor trait - -`Reactor` is the super trait of all actor traits. Extending this trait allows defining actors with basic capabilities to send and receive messages. - -The behavior of a `Reactor` is defined by implementing its `act` method. The `act` method is executed once the `Reactor` is started by invoking `start`, which also returns the `Reactor`. The `start` method is *idempotent* which means that invoking it on an actor that has already been started has no effect. - -The `Reactor` trait has a type parameter `Msg` which indicates the type of messages that the actor can receive. - -Invoking the `Reactor`'s `!` method sends a message to the receiver. Sending a message using `!` is asynchronous which means that the sending actor does not wait until the message is received; its execution continues immediately. For example, `a ! msg` sends `msg` to `a`. All actors have a *mailbox* which buffers incoming messages until they are processed. - -The `Reactor` trait also defines a `forward` method. This method is inherited from `OutputChannel`. It has the same effect as the `!` method. Subtraits of `Reactor`, in particular the `ReplyReactor` trait, override this method to enable implicit reply destinations (see below). - -A `Reactor` receives messages using the `react` method. `react` expects an argument of type `PartialFunction[Msg, Unit]` which defines how messages of type `Msg` are handled once they arrive in the actor's mailbox. In the following example, the current actor waits to receive the string "Hello", and then prints a greeting: - - react { - case "Hello" => println("Hi there") - } - -Invoking `react` never returns. Therefore, any code that should run after a message has been received must be contained inside the partial function that is passed to `react`. For example, two messages can be received in sequence by nesting two invocations of `react`: - - react { - case Get(from) => - react { - case Put(x) => from ! x - } - } - -The `Reactor` trait also provides control structures which simplify programming with `react`. - -#### Termination and execution states - -The execution of a `Reactor` terminates when the body of its `act` method has run to completion. A `Reactor` can also terminate itself explicitly using the `exit` method. The return type of `exit` is `Nothing`, because `exit` always throws an exception. This exception is only used internally, and should never be caught. - -A terminated `Reactor` can be restarted by invoking its `restart` method. Invoking `restart` on a `Reactor` that has not terminated, yet, throws an `IllegalStateException`. Restarting a terminated actor causes its `act` method to be rerun. - -`Reactor` defines a method `getState` which returns the actor's current execution state as a member of the `Actor.State` enumeration. An actor that has not been started, yet, is in state `Actor.State.New`. An actor that can run without waiting for a message is in state `Actor.State.Runnable`. An actor that is suspended, waiting for a message is in state `Actor.State.Suspended`. A terminated actor is in state `Actor.State.Terminated`. - -#### Exception handling - -The `exceptionHandler` member allows defining an exception handler that is enabled throughout the entire lifetime of a `Reactor`: - - def exceptionHandler: PartialFunction[Exception, Unit] - -`exceptionHandler` returns a partial function which is used to handle exceptions that are not otherwise handled: whenever an exception propagates out of the body of a `Reactor`'s `act` method, the partial function is applied to that exception, allowing the actor to run clean-up code before it terminates. Note that the visibility of `exceptionHandler` is `protected`. - -Handling exceptions using `exceptionHandler` works well together with the control structures for programming with `react`. Whenever an exception has been handled using the partial function returned by `exceptionHandler`, execution continues with the current continuation closure. Example: - - loop { - react { - case Msg(data) => - if (cond) // process data - else throw new Exception("cannot process data") - } - } - -Assuming that the `Reactor` overrides `exceptionHandler`, after an exception thrown inside the body of `react` is handled, execution continues with the next loop iteration. - -### The ReplyReactor trait - -The `ReplyReactor` trait extends `Reactor[Any]` and adds or overrides the following methods: - -- The `!` method is overridden to obtain a reference to the current - actor (the sender); together with the actual message, the sender - reference is transferred to the mailbox of the receiving actor. The - receiver has access to the sender of a message through its `sender` - method (see below). - -- The `forward` method is overridden to obtain a reference to the - `sender` of the message that is currently being processed. Together - with the actual message, this reference is transferred as the sender - of the current message. As a consequence, `forward` allows - forwarding messages on behalf of actors different from the current - actor. - -- The added `sender` method returns the sender of the message that is - currently being processed. Given the fact that a message might have - been forwarded, `sender` may not return the actor that actually sent - the message. - -- The added `reply` method sends a message back to the sender of the - last message. `reply` is also used to reply to a synchronous message - send or a message send with future (see below). - -- The added `!?` methods provide *synchronous message sends*. Invoking - `!?` causes the sending actor to wait until a response is received - which is then returned. There are two overloaded variants. The - two-parameter variant takes in addition a timeout argument (in - milliseconds), and its return type is `Option[Any]` instead of - `Any`. If the sender does not receive a response within the - specified timeout period, `!?` returns `None`, otherwise it returns - the response wrapped in `Some`. - -- The added `!!` methods are similar to synchronous message sends in - that they allow transferring a response from the receiver. However, - instead of blocking the sending actor until a response is received, - they return `Future` instances. A `Future` can be used to retrieve - the response of the receiver once it is available; it can also be - used to find out whether the response is already available without - blocking the sender. There are two overloaded variants. The - two-parameter variant takes in addition an argument of type - `PartialFunction[Any, A]`. This partial function is used for - post-processing the receiver's response. Essentially, `!!` returns a - future which applies the partial function to the response once it is - received. The result of the future is the result of this - post-processing. - -- The added `reactWithin` method allows receiving messages within a - given period of time. Compared to `react` it takes an additional - parameter `msec` which indicates the time period in milliseconds - until the special `TIMEOUT` pattern matches (`TIMEOUT` is a case - object in package `scala.actors`). Example: - - reactWithin(2000) { - case Answer(text) => // process text - case TIMEOUT => println("no answer within 2 seconds") - } - - The `reactWithin` method also allows non-blocking access to the - mailbox. When specifying a time period of 0 milliseconds, the - mailbox is first scanned to find a matching message. If there is no - matching message after the first scan, the `TIMEOUT` pattern - matches. For example, this enables receiving certain messages with a - higher priority than others: - - reactWithin(0) { - case HighPriorityMsg => // ... - case TIMEOUT => - react { - case LowPriorityMsg => // ... - } - } - - In the above example, the actor first processes the next - `HighPriorityMsg`, even if there is a `LowPriorityMsg` that arrived - earlier in its mailbox. The actor only processes a `LowPriorityMsg` - *first* if there is no `HighPriorityMsg` in its mailbox. - -In addition, `ReplyReactor` adds the `Actor.State.TimedSuspended` execution state. A suspended actor, waiting to receive a message using `reactWithin` is in state `Actor.State.TimedSuspended`. - -### The Actor trait - -The `Actor` trait extends `ReplyReactor` and adds or overrides the following members: - -- The added `receive` method behaves like `react` except that it may - return a result. This is reflected in its type, which is polymorphic - in its result: `def receive[R](f: PartialFunction[Any, R]): R`. - However, using `receive` makes the actor more heavyweight, since - `receive` blocks the underlying thread while the actor is suspended - waiting for a message. The blocked thread is unavailable to execute - other actors until the invocation of `receive` returns. - -- The added `link` and `unlink` methods allow an actor to link and unlink - itself to and from another actor, respectively. Linking can be used - for monitoring and reacting to the termination of another actor. In - particular, linking affects the behavior of invoking `exit` as - explained in the API documentation of the `Actor` trait. - -- The `trapExit` member allows reacting to the termination of linked - actors independently of the exit reason (that is, it does not matter - whether the exit reason is `'normal` or not). If an actor's `trapExit` - member is set to `true`, this actor will never terminate because of - linked actors. Instead, whenever one of its linked actors terminates - it will receive a message of type `Exit`. The `Exit` case class has two - members: `from` refers to the actor that terminated; `reason` refers to - the exit reason. - -#### Termination and execution states - -When terminating the execution of an actor, the exit reason can be set -explicitly by invoking the following variant of `exit`: - - def exit(reason: AnyRef): Nothing - -An actor that terminates with an exit reason different from the symbol -`'normal` propagates its exit reason to all actors linked to it. If an -actor terminates because of an uncaught exception, its exit reason is -an instance of the `UncaughtException` case class. - -The `Actor` trait adds two new execution states. An actor waiting to -receive a message using `receive` is in state -`Actor.State.Blocked`. An actor waiting to receive a message using -`receiveWithin` is in state `Actor.State.TimedBlocked`. - -## Control structures - -The `Reactor` trait defines control structures that simplify programming -with the non-returning `react` operation. Normally, an invocation of -`react` does not return. If the actor should execute code subsequently, -then one can either pass the actor's continuation code explicitly to -`react`, or one can use one of the following control structures which -hide these continuations. - -The most basic control structure is `andThen`. It allows registering a -closure that is executed once the actor has finished executing -everything else. - - actor { - { - react { - case "hello" => // processing "hello" - }: Unit - } andThen { - println("hi there") - } - } - -For example, the above actor prints a greeting after it has processed -the `"hello"` message. Even though the invocation of `react` does not -return, we can use `andThen` to register the code which prints the -greeting as the actor's continuation. - -Note that there is a *type ascription* that follows the `react` -invocation (`: Unit`). Basically, it lets you treat the result of -`react` as having type `Unit`, which is legal, since the result of an -expression can always be dropped. This is necessary to do here, since -`andThen` cannot be a member of type `Nothing` which is the result -type of `react`. Treating the result type of `react` as `Unit` allows -the application of an implicit conversion which makes the `andThen` -member available. - -The API provides a few more control structures: - -- `loop { ... }`. Loops indefinitely, executing the code in braces in - each iteration. Invoking `react` inside the loop body causes the - actor to react to a message as usual. Subsequently, execution - continues with the next iteration of the same loop. - -- `loopWhile (c) { ... }`. Executes the code in braces while the - condition `c` returns `true`. Invoking `react` in the loop body has - the same effect as in the case of `loop`. - -- `continue`. Continues with the execution of the current continuation - closure. Invoking `continue` inside the body of a `loop` or - `loopWhile` will cause the actor to finish the current iteration and - continue with the next iteration. If the current continuation has - been registered using `andThen`, execution continues with the - closure passed as the second argument to `andThen`. - -The control structures can be used anywhere in the body of a `Reactor`'s -`act` method and in the bodies of methods (transitively) called by -`act`. For actors created using the `actor { ... }` shorthand the control -structures can be imported from the `Actor` object. - -#### Futures - -The `ReplyReactor` and `Actor` traits support result-bearing message -send operations (the `!!` methods) that immediately return a -*future*. A future, that is, an instance of the `Future` trait, is a -handle that can be used to retrieve the response to such a message -send-with-future. - -The sender of a message send-with-future can wait for the future's -response by *applying* the future. For example, sending a message using -`val fut = a !! msg` allows the sender to wait for the result of the -future as follows: `val res = fut()`. - -In addition, a `Future` can be queried to find out whether its result -is available without blocking using the `isSet` method. - -A message send-with-future is not the only way to obtain a -future. Futures can also be created from computations directly. -In the following example, the computation body is started to -run concurrently, returning a future for its result: - - val fut = Future { body } - // ... - fut() // wait for future - -What makes futures special in the context of actors is the possibility -to retrieve their result using the standard actor-based receive -operations, such as `receive` etc. Moreover, it is possible to use the -event-based operations `react` and `reactWithin`. This enables an actor to -wait for the result of a future without blocking its underlying -thread. - -The actor-based receive operations are made available through the -future's `inputChannel`. For a future of type `Future[T]`, its type is -`InputChannel[T]`. Example: - - val fut = a !! msg - // ... - fut.inputChannel.react { - case Response => // ... - } - -## Channels - -Channels can be used to simplify the handling of messages that have -different types but that are sent to the same actor. The hierarchy of -channels is divided into `OutputChannel`s and `InputChannel`s. - -`OutputChannel`s can be sent messages. An `OutputChannel` `out` -supports the following operations. - -- `out ! msg`. Asynchronously sends `msg` to `out`. A reference to the - sending actor is transferred as in the case where `msg` is sent - directly to an actor. - -- `out forward msg`. Asynchronously forwards `msg` to `out`. The - sending actor is determined as in the case where `msg` is forwarded - directly to an actor. - -- `out.receiver`. Returns the unique actor that is receiving messages - sent to the `out` channel. - -- `out.send(msg, from)`. Asynchronously sends `msg` to `out` supplying - `from` as the sender of the message. - -Note that the `OutputChannel` trait has a type parameter that specifies -the type of messages that can be sent to the channel (using `!`, -`forward`, and `send`). The type parameter is contravariant: - - trait OutputChannel[-Msg] - -Actors can receive messages from `InputChannel`s. Like `OutputChannel`, -the `InputChannel` trait has a type parameter that specifies the type of -messages that can be received from the channel. The type parameter is -covariant: - - trait InputChannel[+Msg] - -An `InputChannel[Msg]` `in` supports the following operations. - -- `in.receive { case Pat1 => ... ; case Patn => ... }` (and similarly, - `in.receiveWithin`). Receives a message from `in`. Invoking - `receive` on an input channel has the same semantics as the standard - `receive` operation for actors. The only difference is that the - partial function passed as an argument has type - `PartialFunction[Msg, R]` where `R` is the return type of `receive`. - -- `in.react { case Pat1 => ... ; case Patn => ... }` (and similarly, - `in.reactWithin`). Receives a message from `in` using the - event-based `react` operation. Like `react` for actors, the return - type is `Nothing`, indicating that invocations of this method never - return. Like the `receive` operation above, the partial function - passed as an argument has a more specific type: - - PartialFunction[Msg, Unit] - -### Creating and sharing channels - -Channels are created using the concrete `Channel` class. It extends both -`InputChannel` and `OutputChannel`. A channel can be shared either by -making the channel visible in the scopes of multiple actors, or by -sending it in a message. - -The following example demonstrates scope-based sharing. - - actor { - var out: OutputChannel[String] = null - val child = actor { - react { - case "go" => out ! "hello" - } - } - val channel = new Channel[String] - out = channel - child ! "go" - channel.receive { - case msg => println(msg.length) - } - } - -Running this example prints the string `"5"` to the console. Note that -the `child` actor has only access to `out` which is an -`OutputChannel[String]`. The `channel` reference, which can also be used -to receive messages, is hidden. However, care must be taken to ensure -the output channel is initialized to a concrete channel before the -`child` sends messages to it. This is done using the `"go"` message. When -receiving from `channel` using `channel.receive` we can make use of the -fact that `msg` is of type `String`; therefore, it provides a `length` -member. - -An alternative way to share channels is by sending them in -messages. The following example demonstrates this. - - case class ReplyTo(out: OutputChannel[String]) - - val child = actor { - react { - case ReplyTo(out) => out ! "hello" - } - } - - actor { - val channel = new Channel[String] - child ! ReplyTo(channel) - channel.receive { - case msg => println(msg.length) - } - } - -The `ReplyTo` case class is a message type that we use to distribute a -reference to an `OutputChannel[String]`. When the `child` actor receives a -`ReplyTo` message it sends a string to its output channel. The second -actor receives a message on that channel as before. - -## Schedulers - -A `Reactor` (or an instance of a subtype) is executed using a -*scheduler*. The `Reactor` trait introduces the `scheduler` member which -returns the scheduler used to execute its instances: - - def scheduler: IScheduler - -The run-time system executes actors by submitting tasks to the -scheduler using one of the `execute` methods defined in the `IScheduler` -trait. Most of the trait's other methods are only relevant when -implementing a new scheduler from scratch, which is rarely necessary. - -The default schedulers used to execute instances of `Reactor` and `Actor` -detect the situation when all actors have finished their -execution. When this happens, the scheduler shuts itself down -(terminating any threads used by the scheduler). However, some -schedulers, such as the `SingleThreadedScheduler` (in package `scheduler`) -have to be shut down explicitly by invoking their `shutdown` method. - -The easiest way to create a custom scheduler is by extending -`SchedulerAdapter`, implementing the following abstract member: - - def execute(fun: => Unit): Unit - -Typically, a concrete implementation would use a thread pool to -execute its by-name argument `fun`. - -## Remote Actors - -This section describes the remote actors API. Its main interface is -the [`RemoteActor`](http://www.scala-lang.org/api/2.9.1/scala/actors/remote/RemoteActor$.html) object in package `scala.actors.remote`. This object -provides methods to create and connect to remote actor instances. In -the code snippets shown below we assume that all members of -`RemoteActor` have been imported; the full list of imports that we use -is as follows: - - import scala.actors._ - import scala.actors.Actor._ - import scala.actors.remote._ - import scala.actors.remote.RemoteActor._ - -### Starting remote actors - -A remote actor is uniquely identified by a [`Symbol`](http://www.scala-lang.org/api/2.9.1/scala/Symbol.html). This symbol is -unique to the JVM instance on which the remote actor is executed. A -remote actor identified with name `'myActor` can be created as follows. - - class MyActor extends Actor { - def act() { - alive(9000) - register('myActor, self) - // ... - } - } - -Note that a name can only be registered with a single (alive) actor at -a time. For example, to register an actor *A* as `'myActor`, and then -register another actor *B* as `'myActor`, one would first have to wait -until *A* terminated. This requirement applies across all ports, so -simply registering *B* on a different port as *A* is not sufficient. - -### Connecting to remote actors - -Connecting to a remote actor is just as simple. To obtain a remote -reference to a remote actor running on machine `myMachine`, on port -8000, with name `'anActor`, use `select` in the following manner: - - val myRemoteActor = select(Node("myMachine", 8000), 'anActor) - -The actor returned from `select` has type `AbstractActor` which provides -essentially the same interface as a regular actor, and thus supports -the usual message send operations: - - myRemoteActor ! "Hello!" - receive { - case response => println("Response: " + response) - } - myRemoteActor !? "What is the meaning of life?" match { - case 42 => println("Success") - case oops => println("Failed: " + oops) - } - val future = myRemoteActor !! "What is the last digit of PI?" - -Note that `select` is lazy; it does not actually initiate any network -connections. It simply creates a new `AbstractActor` instance which is -ready to initiate a new network connection when needed (for instance, -when `!` is invoked). - diff --git a/overviews/core/_posts/2010-12-15-architecture-of-scala-collections.md b/overviews/core/_posts/2010-12-15-architecture-of-scala-collections.md deleted file mode 100644 index 7d7eb5dc1c..0000000000 --- a/overviews/core/_posts/2010-12-15-architecture-of-scala-collections.md +++ /dev/null @@ -1,1039 +0,0 @@ ---- -layout: overview -title: The Architecture of Scala Collections -overview: architecture-of-scala-collections -languages: [zh-cn] ---- - -**Martin Odersky and Lex Spoon** - -These pages describe the architecture of the Scala collections -framework in detail. Compared to -[the Scala 2.8 Collections API]({{ site.baseurl }}/overviews/collections/introduction.html) you -will find out more about the internal workings of the framework. You -will also learn how this architecture helps you define your own -collections in a few lines of code, while reusing the overwhelming -part of collection functionality from the framework. - -[The Scala 2.8 Collections API]({{ site.baseurl }}/overviews/collections/introduction.html) -contains a large number of collection -operations, which exist uniformly on many different collection -implementations. Implementing every collection operation anew for -every collection type would lead to an enormous amount of code, most -of which would be copied from somewhere else. Such code duplication -could lead to inconsistencies over time, when an operation is added or -modified in one part of the collection library but not in others. The -principal design objective of the new collections framework was to -avoid any duplication, defining every operation in as few places as -possible. (Ideally, everything should be defined in one place only, -but there are a few exceptions where things needed to be redefined.) -The design approach was to implement most operations in collection -"templates" that can be flexibly inherited from individual base -classes and implementations. The following pages explain these -templates and other classes and traits that constitute the "building -blocks" of the framework, as well as the construction principles they -support. - -## Builders ## - -An outline of the `Builder` trait: - - package scala.collection.mutable - - trait Builder[-Elem, +To] { - def +=(elem: Elem): this.type - def result(): To - def clear(): Unit - def mapResult[NewTo](f: To => NewTo): Builder[Elem, NewTo] = ... - } - -Almost all collection operations are implemented in terms of -*traversals* and *builders*. Traversals are handled by `Traversable`'s -`foreach` method, and building new collections is handled by instances -of class `Builder`. The listing above presents a slightly abbreviated -outline of this trait. - -You can add an element `x` to a builder `b` with `b += x`. There's also -syntax to add more than one element at once, for instance `b += (x, y)`. -Adding another collection with `b ++= xs` works as for buffers (in fact, -buffers are an enriched -version of builders). The `result()` method returns a collection from a -builder. The state of the builder is undefined after taking its -result, but it can be reset into a new empty state using -`clear()`. Builders are generic in both the element type, `Elem`, and in -the type, `To`, of collections they return. - -Often, a builder can refer to some other builder for assembling the -elements of a collection, but then would like to transform the result -of the other builder, for example to give it a different type. This -task is simplified by method `mapResult` in class `Builder`. Suppose for -instance you have an array buffer `buf`. Array buffers are builders for -themselves, so taking the `result()` of an array buffer will return the -same buffer. If you want to use this buffer to produce a builder that -builds arrays, you can use `mapResult` like this: - - scala> val buf = new ArrayBuffer[Int] - buf: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer() - - scala> val bldr = buf mapResult (_.toArray) - bldr: scala.collection.mutable.Builder[Int,Array[Int]] - = ArrayBuffer() - -The result value, `bldr`, is a builder that uses the array buffer, `buf`, -to collect elements. When a result is demanded from `bldr`, the result -of `buf` is computed, which yields the array buffer `buf` itself. This -array buffer is then mapped with `_.toArray` to an array. So the end -result is that `bldr` is a builder for arrays. - -## Factoring out common operations ## - -### Outline of trait TraversableLike ### - - package scala.collection - - trait TraversableLike[+Elem, +Repr] { - def newBuilder: Builder[Elem, Repr] // deferred - def foreach[U](f: Elem => U): Unit // deferred - ... - def filter(p: Elem => Boolean): Repr = { - val b = newBuilder - foreach { elem => if (p(elem)) b += elem } - b.result - } - } - -The main design objectives of the collection library redesign were to -have, at the same time, natural types and maximal sharing of -implementation code. In particular, Scala's collections follow the -"same-result-type" principle: wherever possible, a transformation -method on a collection will yield a collection of the same type. For -instance, the `filter` operation should yield, on every collection type, -an instance of the same collection type. Applying `filter` on a `List` -should give a `List`; applying it on a `Map` should give a `Map`, and so -on. In the rest of this section, you will find out how this is -achieved. - -The Scala collection library avoids code duplication and achieves the -"same-result-type" principle by using generic builders and traversals -over collections in so-called *implementation traits*. These traits are -named with a `Like` suffix; for instance, `IndexedSeqLike` is the -implementation trait for `IndexedSeq`, and similarly, `TraversableLike` is -the implementation trait for `Traversable`. Collection traits such as -`Traversable` or `IndexedSeq` inherit all their concrete method -implementations from these traits. Implementation traits have two type -parameters instead of one for normal collections. They parameterize -not only over the collection's element type, but also over the -collection's *representation type*, i.e., the type of the underlying -collection, such as `Seq[T]` or `List[T]`. For instance, here is the -header of trait `TraversableLike`: - - trait TraversableLike[+Elem, +Repr] { ... } - -The type parameter, `Elem`, stands for the element type of the -traversable whereas the type parameter `Repr` stands for its -representation. There are no constraints on `Repr`. In particular `Repr` -might be instantiated to a type that is itself not a subtype of -`Traversable`. That way, classes outside the collections hierarchy such -as `String` and `Array` can still make use of all operations defined in a -collection implementation trait. - -Taking `filter` as an example, this operation is defined once for all -collection classes in the trait `TraversableLike`. An outline of the -relevant code is shown in the above [outline of trait -`TraversableLike`](#outline-of-trait-traversablelike). The trait declares -two abstract methods, `newBuilder` -and `foreach`, which are implemented in concrete collection classes. The -`filter` operation is implemented in the same way for all collections -using these methods. It first constructs a new builder for the -representation type `Repr`, using `newBuilder`. It then traverses all -elements of the current collection, using `foreach`. If an element `x` -satisfies the given predicate `p` (i.e., `p(x)` is `true`), it is added to -the builder. Finally, the elements collected in the builder are -returned as an instance of the `Repr` collection type by calling the -builder's `result` method. - -A bit more complicated is the `map` operation on collections. For -instance, if `f` is a function from `String` to `Int`, and `xs` is a -`List[String]`, then `xs map f` should give a `List[Int]`. Likewise, -if `ys` is an `Array[String]`, then `ys map f` should give an -`Array[Int]`. The question is how do we achieve that without duplicating -the definition of the `map` method in lists and arrays. The -`newBuilder`/`foreach` framework shown in -[trait `TraversableLike`](#outline-of-trait-traversablelike) is -not sufficient for this because it only allows creation of new -instances of the same collection *type* whereas `map` needs an -instance of the same collection *type constructor*, but possibly with -a different element type. - -What's more, even the result type constructor of a function like `map` -might depend in non-trivial ways on the other argument types. Here is -an example: - - scala> import collection.immutable.BitSet - import collection.immutable.BitSet - - scala> val bits = BitSet(1, 2, 3) - bits: scala.collection.immutable.BitSet = BitSet(1, 2, 3) - - scala> bits map (_ * 2) - res13: scala.collection.immutable.BitSet = BitSet(2, 4, 6) - - scala> bits map (_.toFloat) - res14: scala.collection.immutable.Set[Float] - = Set(1.0, 2.0, 3.0) - -If you `map` the doubling function `_ * 2` over a bit set you obtain -another bit set. However, if you map the function `(_.toFloat)` over the -same bit set, the result is a general `Set[Float]`. Of course, it can't -be a bit set because bit sets contain `Int`s, not `Float`s. - -Note that `map`'s result type depends on the type of function that's -passed to it. If the result type of that function argument is again an -`Int`, the result of `map` is a `BitSet`, but if the result type of the -function argument is something else, the result of `map` is just a -`Set`. You'll find out soon how this type-flexibility is achieved in -Scala. - -The problem with `BitSet` is not an isolated case. Here are two more -interactions with the interpreter that both map a function over a `Map`: - - scala> Map("a" -> 1, "b" -> 2) map { case (x, y) => (y, x) } - res3: scala.collection.immutable.Map[Int,java.lang.String] - = Map(1 -> a, 2 -> b) - - scala> Map("a" -> 1, "b" -> 2) map { case (x, y) => y } - res4: scala.collection.immutable.Iterable[Int] - = List(1, 2) - -The first function swaps two arguments of a key/value pair. The result -of mapping this function is again a map, but now going in the other -direction. In fact, the first expression yields the inverse of the -original map, provided it is invertible. The second function, however, -maps the key/value pair to an integer, namely its value component. In -that case, we cannot form a `Map` from the results, but we can still -form an `Iterable`, a supertrait of `Map`. - -You might ask, why not restrict `map` so that it can always return the -same kind of collection? For instance, on bit sets `map` could accept -only `Int`-to-`Int` functions and on `Map`s it could only accept -pair-to-pair functions. Not only are such restrictions undesirable -from an object-oriented modelling point of view, they are illegal -because they would violate the Liskov substitution principle: A `Map` *is* -an `Iterable`. So every operation that's legal on an `Iterable` must also -be legal on a `Map`. - -Scala solves this problem instead with overloading: not the simple -form of overloading inherited by Java (that would not be flexible -enough), but the more systematic form of overloading that's provided -by implicit parameters. - -Implementation of `map` in `TraversableLike`: - - def map[B, That](f: Elem => B) - (implicit bf: CanBuildFrom[Repr, B, That]): That = { - val b = bf(this) - this.foreach(x => b += f(x)) - b.result - } - -The listing above shows trait `TraversableLike`'s implementation of -`map`. It's quite similar to the implementation of `filter` shown in [trait -`TraversableLike`](#outline-of-trait-traversablelike). -The principal difference is that where `filter` used -the `newBuilder` method, which is abstract in `TraversableLike`, `map` -uses a *builder factory* that's passed as an additional implicit -parameter of type `CanBuildFrom`. - -The `CanBuildFrom` trait: - - package scala.collection.generic - - trait CanBuildFrom[-From, -Elem, +To] { - // Creates a new builder - def apply(from: From): Builder[Elem, To] - } - -The listing above shows the definition of the trait `CanBuildFrom`, -which represents builder factories. It has three type parameters: `From` indicates -the type for which this builder factory applies, `Elem` indicates the element -type of the collection to be built, and `To` indicates the type of -collection to build. By defining the right implicit -definitions of builder factories, you can tailor the right typing -behavior as needed. Take class `BitSet` as an example. Its companion -object would contain a builder factory of type `CanBuildFrom[BitSet, Int, BitSet]`. -This means that when operating on a `BitSet` you can -construct another `BitSet` provided the element type of the collection to build -is `Int`. If this is not the case, the compiler will check the superclasses, and -fall back to the implicit builder factory defined in -`mutable.Set`'s companion object. The type of this more general builder -factory, where `A` is a generic type parameter, is: - - CanBuildFrom[Set[_], A, Set[A]] - -This means that when operating on an arbitrary `Set` (expressed by the -existential type `Set[_]`) you can build a `Set` again, no matter what the -element type `A` is. Given these two implicit instances of `CanBuildFrom`, -you can then rely on Scala's rules for implicit resolution to pick the -one that's appropriate and maximally specific. - -So implicit resolution provides the correct static types for tricky -collection operations such as `map`. But what about the dynamic types? -Specifically, say you map some function over a `List` value that has -`Iterable` as its static type: - - scala> val xs: Iterable[Int] = List(1, 2, 3) - xs: Iterable[Int] = List(1, 2, 3) - - scala> val ys = xs map (x => x * x) - ys: Iterable[Int] = List(1, 4, 9) - -The static type of `ys` above is `Iterable`, as expected. But its dynamic -type is (and should still be) `List`! This behavior is achieved by one -more indirection. The `apply` method in `CanBuildFrom` is passed the -source collection as argument. Most builder factories for generic -traversables (in fact all except builder factories for leaf classes) -forward the call to a method `genericBuilder` of a collection. The -`genericBuilder` method in turn calls the builder that belongs to the -collection in which it is defined. So Scala uses static implicit -resolution to resolve constraints on the types of `map`, and virtual -dispatch to pick the best dynamic type that corresponds to these -constraints. - -In the current example, the static implicit resolution will pick the -`Iterable`'s `CanBuildFrom`, which calls `genericBuilder` on the value it -received as argument. But at runtime, because of virtual dispatch, it is -`List.genericBuilder` that gets called rather than `Iterable.genericBuilder`, -and so map builds a `List`. - -## Integrating a new collection: RNA sequences ## - -What needs to be done if you want to integrate a new collection class, -so that it can profit from all predefined operations with the right -types? In the next few sections you'll be walked through two examples -that do this, namely sequences of RNA bases and prefix maps implemented -with Patricia tries. - -To start with the first example, we define the four RNA Bases: - - abstract class Base - case object A extends Base - case object U extends Base - case object G extends Base - case object C extends Base - - object Base { - val fromInt: Int => Base = Array(A, U, G, C) - val toInt: Base => Int = Map(A -> 0, U -> 1, G -> 2, C -> 3) - } - -Say you want to create a new sequence type for RNA strands, which are -sequences of bases A (adenine), U (uracil), G (guanine), and C -(cytosine). The definitions for bases are easily set up as shown in the -listing of RNA bases above. - -Every base is defined as a case object that inherits from a common -abstract class `Base`. The `Base` class has a companion object that -defines two functions that map between bases and the integers 0 to -3. You can see in the examples two different ways to use collections -to implement these functions. The `toInt` function is implemented as a -`Map` from `Base` values to integers. The reverse function, `fromInt`, is -implemented as an array. This makes use of the fact that both maps and -arrays *are* functions because they inherit from the `Function1` trait. - -The next task is to define a class for strands of RNA. Conceptually, a -strand of RNA is simply a `Seq[Base]`. However, RNA strands can get -quite long, so it makes sense to invest some work in a compact -representation. Because there are only four bases, a base can be -identified with two bits, and you can therefore store sixteen bases as -two-bit values in an integer. The idea, then, is to construct a -specialized subclass of `Seq[Base]`, which uses this packed -representation. - -### First version of RNA strands class ### - - import collection.IndexedSeqLike - import collection.mutable.{Builder, ArrayBuffer} - import collection.generic.CanBuildFrom - - final class RNA1 private (val groups: Array[Int], - val length: Int) extends IndexedSeq[Base] { - - import RNA1._ - - def apply(idx: Int): Base = { - if (idx < 0 || length <= idx) - throw new IndexOutOfBoundsException - Base.fromInt(groups(idx / N) >> (idx % N * S) & M) - } - } - - object RNA1 { - - // Number of bits necessary to represent group - private val S = 2 - - // Number of groups that fit in an Int - private val N = 32 / S - - // Bitmask to isolate a group - private val M = (1 << S) - 1 - - def fromSeq(buf: Seq[Base]): RNA1 = { - val groups = new Array[Int]((buf.length + N - 1) / N) - for (i <- 0 until buf.length) - groups(i / N) |= Base.toInt(buf(i)) << (i % N * S) - new RNA1(groups, buf.length) - } - - def apply(bases: Base*) = fromSeq(bases) - } - -The [RNA strands class listing](#first-version-of-rna-strands-class) above -presents the first version of this -class. It will be refined later. The class `RNA1` has a constructor that -takes an array of `Int`s as its first argument. This array contains the -packed RNA data, with sixteen bases in each element, except for the -last array element, which might be partially filled. The second -argument, `length`, specifies the total number of bases on the array -(and in the sequence). Class `RNA1` extends `IndexedSeq[Base]`. Trait -`IndexedSeq`, which comes from package `scala.collection.immutable`, -defines two abstract methods, `length` and `apply`. These need to be -implemented in concrete subclasses. Class `RNA1` implements `length` -automatically by defining a parametric field of the same name. It -implements the indexing method `apply` with the code given in [class -`RNA1`](#first-version-of-rna-strands-class). Essentially, `apply` first -extracts an integer value from the -`groups` array, then extracts the correct two-bit number from that -integer using right shift (`>>`) and mask (`&`). The private constants `S`, -`N`, and `M` come from the `RNA1` companion object. `S` specifies the size of -each packet (i.e., two); `N` specifies the number of two-bit packets per -integer; and `M` is a bit mask that isolates the lowest `S` bits in a -word. - -Note that the constructor of class `RNA1` is `private`. This means that -clients cannot create `RNA1` sequences by calling `new`, which makes -sense, because it hides the representation of `RNA1` sequences in terms -of packed arrays from the user. If clients cannot see what the -representation details of RNA sequences are, it becomes possible to -change these representation details at any point in the future without -affecting client code. In other words, this design achieves a good -decoupling of the interface of RNA sequences and its -implementation. However, if constructing an RNA sequence with `new` is -impossible, there must be some other way to create new RNA sequences, -else the whole class would be rather useless. In fact there are two -alternatives for RNA sequence creation, both provided by the `RNA1` -companion object. The first way is method `fromSeq`, which converts a -given sequence of bases (i.e., a value of type `Seq[Base]`) into an -instance of class `RNA1`. The `fromSeq` method does this by packing all -the bases contained in its argument sequence into an array, then -calling `RNA1`'s private constructor with that array and the length of -the original sequence as arguments. This makes use of the fact that a -private constructor of a class is visible in the class's companion -object. - -The second way to create an `RNA1` value is provided by the `apply` method -in the `RNA1` object. It takes a variable number of `Base` arguments and -simply forwards them as a sequence to `fromSeq`. Here are the two -creation schemes in action: - - scala> val xs = List(A, G, U, A) - xs: List[Product with Serializable with Base] = List(A, G, U, A) - - scala> RNA1.fromSeq(xs) - res1: RNA1 = RNA1(A, G, U, A) - - scala> val rna1 = RNA1(A, U, G, G, C) - rna1: RNA1 = RNA1(A, U, G, G, C) - -### Adapting the result type of RNA methods ### - -Here are some more interactions with the `RNA1` abstraction: - - scala> rna1.length - res2: Int = 5 - - scala> rna1.last - res3: Base = C - - scala> rna1.take(3) - res4: IndexedSeq[Base] = Vector(A, U, G) - -The first two results are as expected, but the last result of taking -the first three elements of `rna1` might not be. In fact, you see a -`IndexedSeq[Base]` as static result type and a `Vector` as the dynamic -type of the result value. You might have expected to see an `RNA1` value -instead. But this is not possible because all that was done in [class -`RNA1`](#first-version-of-rna-strands-class) was making `RNA1` extend -`IndexedSeq`. Class `IndexedSeq`, on the other -hand, has a `take` method that returns an `IndexedSeq`, and that's -implemented in terms of `IndexedSeq`'s default implementation, -`Vector`. So that's what you were seeing on the last line of the -previous interaction. - -Now that you understand why things are the way they are, the next -question should be what needs to be done to change them? One way to do -this would be to override the `take` method in class `RNA1`, maybe like -this: - - def take(count: Int): RNA1 = RNA1.fromSeq(super.take(count)) - -This would do the job for `take`. But what about `drop`, or `filter`, or -`init`? In fact there are over fifty methods on sequences that return -again a sequence. For consistency, all of these would have to be -overridden. This looks less and less like an attractive -option. Fortunately, there is a much easier way to achieve the same -effect, as shown in the next section. - - -### Second version of RNA strands class ### - - final class RNA2 private ( - val groups: Array[Int], - val length: Int - ) extends IndexedSeq[Base] with IndexedSeqLike[Base, RNA2] { - - import RNA2._ - - override def newBuilder: Builder[Base, RNA2] = - new ArrayBuffer[Base] mapResult fromSeq - - def apply(idx: Int): Base = // as before - } - -The RNA class needs to inherit not only from `IndexedSeq`, but -also from its implementation trait `IndexedSeqLike`. This is shown in -the above listing of class `RNA2`. The new implementation differs from -the previous one in only two aspects. First, class `RNA2` now also -extends `IndexedSeqLike[Base, RNA2]`. Second, it provides a builder for -RNA strands. The `IndexedSeqLike` trait -implements all concrete methods of `IndexedSeq` in an extensible -way. For instance, the return type of methods like `take`, `drop`, `filter`, -or `init` is the second type parameter passed to class `IndexedSeqLike`, -i.e., in class `RNA2` it is `RNA2` itself. - -To be able to do this, `IndexedSeqLike` bases itself on the `newBuilder` -abstraction, which creates a builder of the right kind. Subclasses of -trait `IndexedSeqLike` have to override `newBuilder` to return collections -of their own kind. In class `RNA2`, the `newBuilder` method returns a -builder of type `Builder[Base, RNA2]`. - -To construct this builder, it first creates an `ArrayBuffer`, which -itself is a `Builder[Base, ArrayBuffer]`. It then transforms the -`ArrayBuffer` builder by calling its `mapResult` method to an `RNA2` -builder. The `mapResult` method expects a transformation function from -`ArrayBuffer` to `RNA2` as its parameter. The function given is simply -`RNA2.fromSeq`, which converts an arbitrary base sequence to an `RNA2` -value (recall that an array buffer is a kind of sequence, so -`RNA2.fromSeq` can be applied to it). - -If you had left out the `newBuilder` definition, you would have gotten -an error message like the following: - - RNA2.scala:5: error: overriding method newBuilder in trait - TraversableLike of type => scala.collection.mutable.Builder[Base,RNA2]; - method newBuilder in trait GenericTraversableTemplate of type - => scala.collection.mutable.Builder[Base,IndexedSeq[Base]] has - incompatible type - class RNA2 private (val groups: Array[Int], val length: Int) - ^ - one error found - -The error message is quite long and complicated, which reflects the -intricate way the collection libraries are put together. It's best to -ignore the information about where the methods come from, because in -this case it detracts more than it helps. What remains is that a -method `newBuilder` with result type `Builder[Base, RNA2]` needed to be -defined, but a method `newBuilder` with result type -`Builder[Base,IndexedSeq[Base]]` was found. The latter does not override -the former. The first method, whose result type is `Builder[Base, RNA2]`, -is an abstract method that got instantiated at this type in -[class `RNA2`](#second-version-of-rna-strands-class) by passing the -`RNA2` type parameter to `IndexedSeqLike`. The -second method, of result type `Builder[Base,IndexedSeq[Base]]`, is -what's provided by the inherited `IndexedSeq` class. In other words, the -`RNA2` class is invalid without a definition of `newBuilder` with the -first result type. - -With the refined implementation of the [`RNA2` class](#second-version-of-rna-strands-class), -methods like `take`, -`drop`, or `filter` work now as expected: - - scala> val rna2 = RNA2(A, U, G, G, C) - rna2: RNA2 = RNA2(A, U, G, G, C) - - scala> rna2 take 3 - res5: RNA2 = RNA2(A, U, G) - - scala> rna2 filter (U !=) - res6: RNA2 = RNA2(A, G, G, C) - -### Dealing with map and friends ### - -However, there is another class of methods in collections that are not -dealt with yet. These methods do not always return the collection type -exactly. They might return the same kind of collection, but with a -different element type. The classical example of this is the `map` -method. If `s` is a `Seq[Int]`, and `f` is a function from `Int` to `String`, -then `s.map(f)` would return a `Seq[String]`. So the element type changes -between the receiver and the result, but the kind of collection stays -the same. - -There are a number of other methods that behave like `map`. For some of -them you would expect this (e.g., `flatMap`, `collect`), but for others -you might not. For instance, the append method, `++`, also might return -a result of different type as its arguments--appending a list of -`String` to a list of `Int` would give a list of `Any`. How should these -methods be adapted to RNA strands? The desired behavior would be to get -back an RNA strand when mapping bases to bases or appending two RNA strands -with `++`: - - scala> val rna = RNA(A, U, G, G, C) - rna: RNA = RNA(A, U, G, G, C) - - scala> rna map { case A => U case b => b } - res7: RNA = RNA(U, U, G, G, C) - - scala> rna ++ rna - res8: RNA = RNA(A, U, G, G, C, A, U, G, G, C) - -On the other hand, mapping bases to some other type over an RNA strand -cannot yield another RNA strand because the new elements have the -wrong type. It has to yield a sequence instead. In the same vein -appending elements that are not of type `Base` to an RNA strand can -yield a general sequence, but it cannot yield another RNA strand. - - scala> rna map Base.toInt - res2: IndexedSeq[Int] = Vector(0, 1, 2, 2, 3) - - scala> rna ++ List("missing", "data") - res3: IndexedSeq[java.lang.Object] = - Vector(A, U, G, G, C, missing, data) - -This is what you'd expect in the ideal case. But this is not what the -[`RNA2` class](#second-version-of-rna-strands-class) provides. In fact, all -examples will return instances of `Vector`, not just the last two. If you run -the first three commands above with instances of this class you obtain: - - scala> val rna2 = RNA2(A, U, G, G, C) - rna2: RNA2 = RNA2(A, U, G, G, C) - - scala> rna2 map { case A => U case b => b } - res0: IndexedSeq[Base] = Vector(U, U, G, G, C) - - scala> rna2 ++ rna2 - res1: IndexedSeq[Base] = Vector(A, U, G, G, C, A, U, G, G, C) - -So the result of `map` and `++` is never an RNA strand, even if the -element type of the generated collection is `Base`. To see how to do -better, it pays to have a close look at the signature of the `map` -method (or of `++`, which has a similar signature). The `map` method is -originally defined in class `scala.collection.TraversableLike` with the -following signature: - - def map[B, That](f: A => B) - (implicit cbf: CanBuildFrom[Repr, B, That]): That - -Here `A` is the type of elements of the collection, and `Repr` is the type -of the collection itself, that is, the second type parameter that gets -passed to implementation classes such as `TraversableLike` and -`IndexedSeqLike`. The `map` method takes two more type parameters, `B` and -`That`. The `B` parameter stands for the result type of the mapping -function, which is also the element type of the new collection. The -`That` appears as the result type of `map`, so it represents the type of -the new collection that gets created. - -How is the `That` type determined? In fact it is linked to the other -types by an implicit parameter `cbf`, of type `CanBuildFrom[Repr, B, That]`. -These `CanBuildFrom` implicits are defined by the individual -collection classes. Recall that an implicit value of type -`CanBuildFrom[Repr, B, That]` says: "Here is a way, given a collection -of type `Repr` and new elements of type `B`, to build a collection of type -`That` containing those elements". - -Now the behavior of `map` and `++` on `RNA2` sequences becomes -clearer. There is no `CanBuildFrom` instance that creates `RNA2` -sequences, so the next best available `CanBuildFrom` was found in the -companion object of the inherited trait `IndexedSeq`. That implicit -creates `Vector`s (recall that `Vector` is the default implementation -of `IndexedSeq`), and that's what you saw when applying `map` to -`rna2`. - - -### Final version of RNA strands class ### - - final class RNA private (val groups: Array[Int], val length: Int) - extends IndexedSeq[Base] with IndexedSeqLike[Base, RNA] { - - import RNA._ - - // Mandatory re-implementation of `newBuilder` in `IndexedSeq` - override protected[this] def newBuilder: Builder[Base, RNA] = - RNA.newBuilder - - // Mandatory implementation of `apply` in `IndexedSeq` - def apply(idx: Int): Base = { - if (idx < 0 || length <= idx) - throw new IndexOutOfBoundsException - Base.fromInt(groups(idx / N) >> (idx % N * S) & M) - } - - // Optional re-implementation of foreach, - // to make it more efficient. - override def foreach[U](f: Base => U): Unit = { - var i = 0 - var b = 0 - while (i < length) { - b = if (i % N == 0) groups(i / N) else b >>> S - f(Base.fromInt(b & M)) - i += 1 - } - } - } - -### Final version of RNA companion object ### - - object RNA { - - private val S = 2 // number of bits in group - private val M = (1 << S) - 1 // bitmask to isolate a group - private val N = 32 / S // number of groups in an Int - - def fromSeq(buf: Seq[Base]): RNA = { - val groups = new Array[Int]((buf.length + N - 1) / N) - for (i <- 0 until buf.length) - groups(i / N) |= Base.toInt(buf(i)) << (i % N * S) - new RNA(groups, buf.length) - } - - def apply(bases: Base*) = fromSeq(bases) - - def newBuilder: Builder[Base, RNA] = - new ArrayBuffer mapResult fromSeq - - implicit def canBuildFrom: CanBuildFrom[RNA, Base, RNA] = - new CanBuildFrom[RNA, Base, RNA] { - def apply(): Builder[Base, RNA] = newBuilder - def apply(from: RNA): Builder[Base, RNA] = newBuilder - } - } - -To address this shortcoming, you need to define an implicit instance -of `CanBuildFrom` in the companion object of the RNA class. That -instance should have type `CanBuildFrom[RNA, Base, RNA]`. Hence, this -instance states that, given an RNA strand and a new element type `Base`, -you can build another collection which is again an RNA strand. The two -listings above of [class `RNA`](#final-version-of-rna-strands-class) and -[its companion object](#final-version-of-rna-companion-object) show the -details. Compared to [class `RNA2`](#second-version-of-rna-strands-class) -there are two important -differences. First, the `newBuilder` implementation has moved from the -RNA class to its companion object. The `newBuilder` method in class `RNA` -simply forwards to this definition. Second, there is now an implicit -`CanBuildFrom` value in object `RNA`. To create this value you need to -define two `apply` methods in the `CanBuildFrom` trait. Both create a new -builder for an `RNA` collection, but they differ in their argument -list. The `apply()` method simply creates a new builder of the right -type. By contrast, the `apply(from)` method takes the original -collection as argument. This can be useful to adapt the dynamic type -of builder's return type to be the same as the dynamic type of the -receiver. In the case of `RNA` this does not come into play because `RNA` -is a final class, so any receiver of static type `RNA` also has `RNA` as -its dynamic type. That's why `apply(from)` also simply calls `newBuilder`, -ignoring its argument. - -That is it. The final [`RNA` class](#final-version-of-rna-strands-class) -implements all collection methods at -their expected types. Its implementation requires a little bit of -protocol. In essence, you need to know where to put the `newBuilder` -factories and the `canBuildFrom` implicits. On the plus side, with -relatively little code you get a large number of methods automatically -defined. Also, if you don't intend to do bulk operations like `take`, -`drop`, `map`, or `++` on your collection you can choose to not go the extra -length and stop at the implementation shown in for [class `RNA1`](#first-version-of-rna-strands-class). - -The discussion so far centered on the minimal amount of definitions -needed to define new sequences with methods that obey certain -types. But in practice you might also want to add new functionality to -your sequences or to override existing methods for better -efficiency. An example of this is the overridden `foreach` method in -class `RNA`. `foreach` is an important method in its own right because it -implements loops over collections. Furthermore, many other collection -methods are implemented in terms of `foreach`. So it makes sense to -invest some effort optimizing the method's implementation. The -standard implementation of `foreach` in `IndexedSeq` will simply select -every `i`'th element of the collection using `apply`, where `i` ranges from -0 to the collection's length minus one. So this standard -implementation selects an array element and unpacks a base from it -once for every element in an RNA strand. The overriding `foreach` in -class `RNA` is smarter than that. For every selected array element it -immediately applies the given function to all bases contained in -it. So the effort for array selection and bit unpacking is much -reduced. - -## Integrating a new prefix map ## - -As a second example you'll learn how to integrate a new kind of map -into the collection framework. The idea is to implement a mutable map -with `String` as the type of keys by a "Patricia trie". The term -*Patricia* is in fact an abbreviation for "Practical Algorithm to -Retrieve Information Coded in Alphanumeric" and *trie* comes from -re*trie*val (a trie is also called a radix tree or prefix tree). -The idea is to store a set or a map as a tree where subsequent -characters in a search key -uniquely determine a path through the tree. For instance a Patricia trie -storing the strings "abc", "abd", "al", "all" and "xy" would look -like this: - -A sample patricia trie: - - -To find the node corresponding to the string "abc" in this trie, -simply follow the subtree labeled "a", proceed from there to the -subtree labelled "b", to finally reach its subtree labelled "c". If -the Patricia trie is used as a map, the value that's associated with a -key is stored in the nodes that can be reached by the key. If it is a -set, you simply store a marker saying that the node is present in the -set. - -Patricia tries support very efficient lookups and updates. Another -nice feature is that they support selecting a subcollection by giving -a prefix. For instance, in the patricia tree above you can obtain the -sub-collection of all keys that start with an "a" simply by following -the "a" link from the root of the tree. - -Based on these ideas we will now walk you through the implementation -of a map that's implemented as a Patricia trie. We call the map a -`PrefixMap`, which means that it provides a method `withPrefix` that -selects a submap of all keys starting with a given prefix. We'll first -define a prefix map with the keys shown in the running example: - - scala> val m = PrefixMap("abc" -> 0, "abd" -> 1, "al" -> 2, - "all" -> 3, "xy" -> 4) - m: PrefixMap[Int] = Map((abc,0), (abd,1), (al,2), (all,3), (xy,4)) - -Then calling `withPrefix` on `m` will yield another prefix map: - - scala> m withPrefix "a" - res14: PrefixMap[Int] = Map((bc,0), (bd,1), (l,2), (ll,3)) - -### Patricia trie implementation ### - - import collection._ - - class PrefixMap[T] - extends mutable.Map[String, T] - with mutable.MapLike[String, T, PrefixMap[T]] { - - var suffixes: immutable.Map[Char, PrefixMap[T]] = Map.empty - var value: Option[T] = None - - def get(s: String): Option[T] = - if (s.isEmpty) value - else suffixes get (s(0)) flatMap (_.get(s substring 1)) - - def withPrefix(s: String): PrefixMap[T] = - if (s.isEmpty) this - else { - val leading = s(0) - suffixes get leading match { - case None => - suffixes = suffixes + (leading -> empty) - case _ => - } - suffixes(leading) withPrefix (s substring 1) - } - - override def update(s: String, elem: T) = - withPrefix(s).value = Some(elem) - - override def remove(s: String): Option[T] = - if (s.isEmpty) { val prev = value; value = None; prev } - else suffixes get (s(0)) flatMap (_.remove(s substring 1)) - - def iterator: Iterator[(String, T)] = - (for (v <- value.iterator) yield ("", v)) ++ - (for ((chr, m) <- suffixes.iterator; - (s, v) <- m.iterator) yield (chr +: s, v)) - - def += (kv: (String, T)): this.type = { update(kv._1, kv._2); this } - - def -= (s: String): this.type = { remove(s); this } - - override def empty = new PrefixMap[T] - } - - -The previous listing shows the definition of `PrefixMap`. The map has -keys of type `String` and the values are of parametric type `T`. It extends -`mutable.Map[String, T]` and `mutable.MapLike[String, T, PrefixMap[T]]`. -You have seen this pattern already for sequences in the -RNA strand example; then as now inheriting an implementation class -such as `MapLike` serves to get the right result type for -transformations such as `filter`. - -A prefix map node has two mutable fields: `suffixes` and `value`. The -`value` field contains an optional value that's associated with the -node. It is initialized to `None`. The `suffixes` field contains a map -from characters to `PrefixMap` values. It is initialized to the empty -map. - -You might ask why we picked an immutable map as the implementation -type for `suffixes`? Would not a mutable map have been more standard, -since `PrefixMap` as a whole is also mutable? The answer is that -immutable maps that contain only a few elements are very efficient in -both space and execution time. For instance, maps that contain fewer -than 5 elements are represented as a single object. By contrast, the -standard mutable map is a `HashMap`, which typically occupies around 80 -bytes, even if it is empty. So if small collections are common, it's -better to pick immutable over mutable. In the case of Patricia tries, -we'd expect that most nodes except the ones at the very top of the -tree would contain only a few successors. So storing these successors -in an immutable map is likely to be more efficient. - -Now have a look at the first method that needs to be implemented for a -map: `get`. The algorithm is as follows: To get the value associated -with the empty string in a prefix map, simply select the optional -`value` stored in the root of the tree (the current map). -Otherwise, if the key string is -not empty, try to select the submap corresponding to the first -character of the string. If that yields a map, follow up by looking up -the remainder of the key string after its first character in that -map. If the selection fails, the key is not stored in the map, so -return with `None`. The combined selection over an option value `opt` is -elegantly expressed using `opt.flatMap(x => f(x))`. When applied to an -optional value that is `None`, it returns `None`. Otherwise `opt` is -`Some(x)` and the function `f` is applied to the encapsulated value `x`, -yielding a new option, which is returned by the flatmap. - -The next two methods to implement for a mutable map are `+=` and `-=`. In -the implementation of `PrefixMap`, these are defined in terms of two -other methods: `update` and `remove`. - -The `remove` method is very similar to `get`, except that before returning -any associated value, the field containing that value is set to -`None`. The `update` method first calls `withPrefix` to navigate to the tree -node that needs to be updated, then sets the `value` field of that node -to the given value. The `withPrefix` method navigates through the tree, -creating sub-maps as necessary if some prefix of characters is not yet -contained as a path in the tree. - -The last abstract method to implement for a mutable map is -`iterator`. This method needs to produce an iterator that yields all -key/value pairs stored in the map. For any given prefix map this -iterator is composed of the following parts: First, if the map -contains a defined value, `Some(x)`, in the `value` field at its root, -then `("", x)` is the first element returned from the -iterator. Furthermore, the iterator needs to traverse the iterators of -all submaps stored in the `suffixes` field, but it needs to add a -character in front of every key string returned by those -iterators. More precisely, if `m` is the submap reached from the root -through a character `chr`, and `(s, v)` is an element returned from -`m.iterator`, then the root's iterator will return `(chr +: s, v)` -instead. This logic is implemented quite concisely as a concatenation -of two `for` expressions in the implementation of the `iterator` method in -`PrefixMap`. The first `for` expression iterates over `value.iterator`. This -makes use of the fact that `Option` values define an iterator method -that returns either no element, if the option value is `None`, or -exactly one element `x`, if the option value is `Some(x)`. - -Note that there is no `newBuilder` method defined in `PrefixMap`. There is -no need to, because maps and sets come with default builders, which -are instances of class `MapBuilder`. For a mutable map the default -builder starts with an empty map and then adds successive elements -using the map's `+=` method. For immutable maps, the non-destructive -element addition method `+` is used instead of method `+=`. Sets work -in the same way. - -However, in all these cases, to build the right kind of colletion -you need to start with an empty collection of that kind. This is -provided by the `empty` method, which is the last method defined in -`PrefixMap`. This method simply returns a fresh `PrefixMap`. - - -### The companion object for prefix maps ### - - import scala.collection.mutable.{Builder, MapBuilder} - import scala.collection.generic.CanBuildFrom - - object PrefixMap extends { - def empty[T] = new PrefixMap[T] - - def apply[T](kvs: (String, T)*): PrefixMap[T] = { - val m: PrefixMap[T] = empty - for (kv <- kvs) m += kv - m - } - - def newBuilder[T]: Builder[(String, T), PrefixMap[T]] = - new MapBuilder[String, T, PrefixMap[T]](empty) - - implicit def canBuildFrom[T] - : CanBuildFrom[PrefixMap[_], (String, T), PrefixMap[T]] = - new CanBuildFrom[PrefixMap[_], (String, T), PrefixMap[T]] { - def apply(from: PrefixMap[_]) = newBuilder[T] - def apply() = newBuilder[T] - } - } - -We'll now turn to the companion object `PrefixMap`. In fact it is not -strictly necessary to define this companion object, as class `PrefixMap` -can stand well on its own. The main purpose of object `PrefixMap` is to -define some convenience factory methods. It also defines a -`CanBuildFrom` implicit to make typing work out better. - -The two convenience methods are `empty` and `apply`. The same methods are -present for all other collections in Scala's collection framework so -it makes sense to define them here, too. With the two methods, you can -write `PrefixMap` literals like you do for any other collection: - - scala> PrefixMap("hello" -> 5, "hi" -> 2) - res0: PrefixMap[Int] = Map(hello -> 5, hi -> 2) - - scala> PrefixMap.empty[String] - res2: PrefixMap[String] = Map() - -The other member in object `PrefixMap` is an implicit `CanBuildFrom` -instance. It has the same purpose as the `CanBuildFrom` definition in -the [`RNA` companion object](#final-version-of-rna-companion-object) -from the last section: to make methods like `map` return the best possible -type. For instance, consider mapping a function over the key/value -pairs of a `PrefixMap`. As long as that function produces pairs of -strings and some second type, the result collection will again be a -`PrefixMap`. Here's an example: - - scala> res0 map { case (k, v) => (k + "!", "x" * v) } - res8: PrefixMap[String] = Map(hello! -> xxxxx, hi! -> xx) - -The given function argument takes the key/value bindings of the prefix -map `res0` and produces pairs of strings. The result of the `map` is a -`PrefixMap`, this time with value type `String` instead of `Int`. Without -the `canBuildFrom` implicit in `PrefixMap` the result would just have been -a general mutable map, not a prefix map. For convenience, the `PrefixMap` -object defines a `newBuilder` method, but it still just uses the -default `MapBuilder`. - -## Summary ## - -To summarize, if you want to fully integrate a new collection class -into the framework you need to pay attention to the following points: - -1. Decide whether the collection should be mutable or immutable. -2. Pick the right base traits for the collection. -3. Inherit from the right implementation trait to implement most - collection operations. -4. If you want `map` and similar operations to return instances of your - collection type, provide an implicit `CanBuildFrom` in your class's - companion object. - -You have now seen how Scala's collections are built and how you can -add new kinds of collections. Because of Scala's rich support for -abstraction, each new collection type has a large number of -methods without having to reimplement them all over again. - -### Acknowledgement ### - -These pages contain material adapted from the 2nd edition of -[Programming in Scala](http://www.artima.com/shop/programming_in_scala) by -Odersky, Spoon and Venners. We thank Artima for graciously agreeing to its -publication. - diff --git a/overviews/core/_posts/2012-03-27-parallel-collections.md b/overviews/core/_posts/2012-03-27-parallel-collections.md deleted file mode 100644 index a4fc0f9c0b..0000000000 --- a/overviews/core/_posts/2012-03-27-parallel-collections.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -layout: overview -title: Scala's Parallel Collections Library -discourse: true -partof: parallel-collections -overview: parallel-collections -languages: [es, ja] ---- diff --git a/overviews/core/_posts/2012-09-20-futures.md b/overviews/core/_posts/2012-09-20-futures.md deleted file mode 100644 index 946e060262..0000000000 --- a/overviews/core/_posts/2012-09-20-futures.md +++ /dev/null @@ -1,1072 +0,0 @@ ---- -layout: overview -title: Futures and Promises -label-color: success -label-text: New in 2.10 -overview: futures -languages: [ja, zh-cn] ---- - -**By: Philipp Haller, Aleksandar Prokopec, Heather Miller, Viktor Klang, Roland Kuhn, and Vojin Jovanovic** - -## Introduction - -Futures provide a way to reason about performing many operations -in parallel-- in an efficient and non-blocking way. -A [`Future`](http://www.scala-lang.org/api/current/index.html#scala.concurrent.Future) -is a placeholder object for a value that may not yet exist. -Generally, the value of the Future is supplied concurrently and can subsequently be used. -Composing concurrent tasks in this way tends to result in faster, asynchronous, non-blocking parallel code. - -By default, futures and promises are non-blocking, making use of -callbacks instead of typical blocking operations. -To simplify the use of callbacks both syntactically and conceptually, -Scala provides combinators such as `flatMap`, `foreach`, and `filter` used to compose -futures in a non-blocking way. -Blocking is still possible - for cases where it is absolutely -necessary, futures can be blocked on (although this is discouraged). - - - -A typical future looks like this: - - val inverseFuture: Future[Matrix] = Future { - fatMatrix.inverse() // non-blocking long lasting computation - }(executionContext) - - -Or with the more idiomatic: - - implicit val ec: ExecutionContext = ... - val inverseFuture : Future[Matrix] = Future { - fatMatrix.inverse() - } // ec is implicitly passed - - -Both code snippets delegate the execution of `fatMatrix.inverse()` to an `ExecutionContext` and embody the result of the computation in `inverseFuture`. - - -## Execution Context - -Future and Promises revolve around [`ExecutionContext`s](http://www.scala-lang.org/api/current/index.html#scala.concurrent.ExecutionContext), responsible for executing computations. - -An `ExecutionContext` is similar to an [Executor](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Executor.html): -it is free to execute computations in a new thread, in a pooled thread or in the current thread -(although executing the computation in the current thread is discouraged -- more on that below). - -The `scala.concurrent` package comes out of the box with an `ExecutionContext` implementation, a global static thread pool. -It is also possible to convert an `Executor` into an `ExecutionContext`. -Finally, users are free to extend the `ExecutionContext` trait to implement their own execution contexts, -although this should only be done in rare cases. - - -### The Global Execution Context - -`ExecutionContext.global` is an `ExecutionContext` backed by a [ForkJoinPool](http://docs.oracle.com/javase/tutorial/essential/concurrency/forkjoin.html). -It should be sufficient for most situations but requires some care. -A `ForkJoinPool` manages a limited amount of threads (the maximum amount of thread being referred to as *parallelism level*). -The number of concurrently blocking computations can exceed the parallelism level -only if each blocking call is wrapped inside a `blocking` call (more on that below). -Otherwise, there is a risk that the thread pool in the global execution context is starved, -and no computation can proceed. - -By default the `ExecutionContext.global` sets the parallelism level of its underlying fork-join pool to the amount of available processors -([Runtime.availableProcessors](http://docs.oracle.com/javase/7/docs/api/java/lang/Runtime.html#availableProcessors%28%29)). -This configuration can be overridden by setting one (or more) of the following VM attributes: - - * scala.concurrent.context.minThreads - defaults to `Runtime.availableProcessors` - * scala.concurrent.context.numThreads - can be a number or a multiplier (N) in the form 'xN' ; defaults to `Runtime.availableProcessors` - * scala.concurrent.context.maxThreads - defaults to `Runtime.availableProcessors` - -The parallelism level will be set to `numThreads` as long as it remains within `[minThreads; maxThreads]`. - -As stated above the `ForkJoinPool` can increase the amount of threads beyond its `parallelismLevel` in the presence of blocking computation. -As explained in the `ForkJoinPool` API, this is only possible if the pool is explicitly notified: - - import scala.concurrent.Future - import scala.concurrent.forkjoin._ - - // the following is equivalent to `implicit val ec = ExecutionContext.global` - import ExecutionContext.Implicits.global - - Future { - ForkJoinPool.managedBlock( - new ManagedBlocker { - var done = false - - def block(): Boolean = { - try { - myLock.lock() - // ... - } finally { - done = true - } - true - } - - def isReleasable: Boolean = done - } - ) - } - - -Fortunately the concurrent package provides a convenient way for doing so: - - import scala.concurrent.Future - import scala.concurrent.blocking - - Future { - blocking { - myLock.lock() - // ... - } - } - -Note that `blocking` is a general construct that will be discussed more in depth [below](#in_a_future). - -Last but not least, you must remember that the `ForkJoinPool` is not designed for long lasting blocking operations. -Even when notified with `blocking` the pool might not spawn new workers as you would expect, -and when new workers are created they can be as many as 32767. -To give you an idea, the following code will use 32000 threads: - - implicit val ec = ExecutionContext.global - - for( i <- 1 to 32000 ) { - Future { - blocking { - Thread.sleep(999999) - } - } - } - - -If you need to wrap long lasting blocking operations we recommend using a dedicated `ExecutionContext`, for instance by wrapping a Java `Executor`. - - -### Adapting a Java Executor - -Using the `ExecutionContext.fromExecutor` method you can wrap a Java `Executor` into an `ExecutionContext`. -For instance: - - ExecutionContext.fromExecutor(new ThreadPoolExecutor( /* your configuration */ )) - - -### Synchronous Execution Context - -One might be tempted to have an `ExecutionContext` that runs computations within the current thread: - - val currentThreadExecutionContext = ExecutionContext.fromExecutor( - new Executor { - // Do not do this! - def execute(runnable: Runnable) { runnable.run() } - }) - -This should be avoided as it introduces non-determinism in the execution of your future. - - Future { - doSomething - }(ExecutionContext.global).map { - doSomethingElse - }(currentThreadExecutionContext) - -The `doSomethingElse` call might either execute in `doSomething`'s thread or in the main thread, and therefore be either asynchronous or synchronous. -As explained [here](http://blog.ometer.com/2011/07/24/callbacks-synchronous-and-asynchronous/) a callback should not be both. - - - -## Futures - -A `Future` is an object holding a value which may become available at some point. -This value is usually the result of some other computation: - -1. If the computation has not yet completed, we say that the `Future` is **not completed.** -2. If the computation has completed with a value or with an exception, we say that the `Future` is **completed**. - -Completion can take one of two forms: - -1. When a `Future` is completed with a value, we say that the future was **successfully completed** with that value. -2. When a `Future` is completed with an exception thrown by the computation, we say that the `Future` was **failed** with that exception. - -A `Future` has an important property that it may only be assigned -once. -Once a `Future` object is given a value or an exception, it becomes -in effect immutable-- it can never be overwritten. - -The simplest way to create a future object is to invoke the `Future.apply` -method which starts an asynchronous computation and returns a -future holding the result of that computation. -The result becomes available once the future completes. - -Note that `Future[T]` is a type which denotes future objects, whereas -`Future.apply` is a method which creates and schedules an asynchronous -computation, and then returns a future object which will be completed -with the result of that computation. - -This is best shown through an example. - -Let's assume that we want to use a hypothetical API of some -popular social network to obtain a list of friends for a given user. -We will open a new session and then send -a request to obtain a list of friends of a particular user: - - import scala.concurrent._ - import ExecutionContext.Implicits.global - - val session = socialNetwork.createSessionFor("user", credentials) - val f: Future[List[Friend]] = Future { - session.getFriends() - } - -Above, we first import the contents of the `scala.concurrent` package -to make the type `Future` visible. -We will explain the second import shortly. - -We then initialize a session variable which we will use to send -requests to the server, using a hypothetical `createSessionFor` -method. -To obtain the list of friends of a user, a request -has to be sent over a network, which can take a long time. -This is illustrated with the call to the method `getFriends` that returns `List[Friend]`. -To better utilize the CPU until the response arrives, we should not -block the rest of the program-- this computation should be scheduled -asynchronously. The `Future.apply` method does exactly that-- it performs -the specified computation block concurrently, in this case sending -a request to the server and waiting for a response. - -The list of friends becomes available in the future `f` once the server -responds. - -An unsuccessful attempt may result in an exception. In -the following example, the `session` value is incorrectly -initialized, so the computation in the `Future` block will throw a `NullPointerException`. -This future `f` is then failed with this exception instead of being completed successfully: - - val session = null - val f: Future[List[Friend]] = Future { - session.getFriends - } - -The line `import ExecutionContext.Implicits.global` above imports -the default global execution context. -Execution contexts execute tasks submitted to them, and -you can think of execution contexts as thread pools. -They are essential for the `Future.apply` method because -they handle how and when the asynchronous computation is executed. -You can define your own execution contexts and use them with `Future`, -but for now it is sufficient to know that -you can import the default execution context as shown above. - -Our example was based on a hypothetical social network API where -the computation consists of sending a network request and waiting -for a response. -It is fair to offer an example involving an asynchronous computation -which you can try out of the box. Assume you have a text file and -you want to find the position of the first occurrence of a particular keyword. -This computation may involve blocking while the file contents -are being retrieved from the disk, so it makes sense to perform it -concurrently with the rest of the computation. - - val firstOccurrence: Future[Int] = Future { - val source = scala.io.Source.fromFile("myText.txt") - source.toSeq.indexOfSlice("myKeyword") - } - - -### Callbacks - -We now know how to start an asynchronous computation to create a new -future value, but we have not shown how to use the result once it -becomes available, so that we can do something useful with it. -We are often interested in the result of the computation, not just its -side-effects. - -In many future implementations, once the client of the future becomes interested -in its result, it has to block its own computation and wait until the future is completed-- -only then can it use the value of the future to continue its own computation. -Although this is allowed by the Scala `Future` API as we will show later, -from a performance point of view a better way to do it is in a completely -non-blocking way, by registering a callback on the future. -This callback is called asynchronously once the future is completed. If the -future has already been completed when registering the callback, then -the callback may either be executed asynchronously, or sequentially on -the same thread. - -The most general form of registering a callback is by using the `onComplete` -method, which takes a callback function of type `Try[T] => U`. -The callback is applied to the value -of type `Success[T]` if the future completes successfully, or to a value -of type `Failure[T]` otherwise. - -The `Try[T]` is similar to `Option[T]` or `Either[T, S]`, in that it is a monad -potentially holding a value of some type. -However, it has been specifically designed to either hold a value or -some throwable object. -Where an `Option[T]` could either be a value (i.e. `Some[T]`) or no value -at all (i.e. `None`), `Try[T]` is a `Success[T]` when it holds a value -and otherwise `Failure[T]`, which holds an exception. `Failure[T]` holds -more information than just a plain `None` by saying why the value is not -there. -In the same time, you can think of `Try[T]` as a special version -of `Either[Throwable, T]`, specialized for the case when the left -value is a `Throwable`. - -Coming back to our social network example, let's assume we want to -fetch a list of our own recent posts and render them to the screen. -We do so by calling a method `getRecentPosts` which returns -a `List[String]`-- a list of recent textual posts: - - import scala.util.{Success, Failure} - - val f: Future[List[String]] = Future { - session.getRecentPosts - } - - f onComplete { - case Success(posts) => for (post <- posts) println(post) - case Failure(t) => println("An error has occured: " + t.getMessage) - } - -The `onComplete` method is general in the sense that it allows the -client to handle the result of both failed and successful future -computations. To handle only successful results, the `onSuccess` -callback is used (which takes a partial function): - - val f: Future[List[String]] = Future { - session.getRecentPosts - } - - f onSuccess { - case posts => for (post <- posts) println(post) - } - -To handle failed results, the `onFailure` callback is used: - - val f: Future[List[String]] = Future { - session.getRecentPosts - } - - f onFailure { - case t => println("An error has occured: " + t.getMessage) - } - - f onSuccess { - case posts => for (post <- posts) println(post) - } - -The `onFailure` callback is only executed if the future fails, that -is, if it contains an exception. - -Since partial functions have the `isDefinedAt` method, the -`onFailure` method only triggers the callback if it is defined for a -particular `Throwable`. In the following example the registered `onFailure` -callback is never triggered: - - val f = Future { - 2 / 0 - } - - f onFailure { - case npe: NullPointerException => - println("I'd be amazed if this printed out.") - } - -Coming back to the previous example with searching for the first -occurrence of a keyword, you might want to print the position -of the keyword to the screen: - - val firstOccurrence: Future[Int] = Future { - val source = scala.io.Source.fromFile("myText.txt") - source.toSeq.indexOfSlice("myKeyword") - } - - firstOccurrence onSuccess { - case idx => println("The keyword first appears at position: " + idx) - } - - firstOccurrence onFailure { - case t => println("Could not process file: " + t.getMessage) - } - -The `onComplete`, `onSuccess`, and -`onFailure` methods have result type `Unit`, which means invocations -of these methods cannot be chained. Note that this design is intentional, -to avoid suggesting that chained -invocations may imply an ordering on the execution of the registered -callbacks (callbacks registered on the same future are unordered). - -That said, we should now comment on **when** exactly the callback -gets called. Since it requires the value in the future to be available, -it can only be called after the future is completed. -However, there is no guarantee it will be called by the thread -that completed the future or the thread which created the callback. -Instead, the callback is executed by some thread, at some time -after the future object is completed. -We say that the callback is executed **eventually**. - -Furthermore, the order in which the callbacks are executed is -not predefined, even between different runs of the same application. -In fact, the callbacks may not be called sequentially one after the other, -but may concurrently execute at the same time. -This means that in the following example the variable `totalA` may not be set -to the correct number of lower case and upper case `a` characters from the computed -text. - - @volatile var totalA = 0 - - val text = Future { - "na" * 16 + "BATMAN!!!" - } - - text onSuccess { - case txt => totalA += txt.count(_ == 'a') - } - - text onSuccess { - case txt => totalA += txt.count(_ == 'A') - } - -Above, the two callbacks may execute one after the other, in -which case the variable `totalA` holds the expected value `18`. -However, they could also execute concurrently, so `totalA` could -end up being either `16` or `2`, since `+=` is not an atomic -operation (i.e. it consists of a read and a write step which may -interleave arbitrarily with other reads and writes). - -For the sake of completeness the semantics of callbacks are listed here: - -1. Registering an `onComplete` callback on the future -ensures that the corresponding closure is invoked after -the future is completed, eventually. - -2. Registering an `onSuccess` or `onFailure` callback has the same -semantics as `onComplete`, with the difference that the closure is only called -if the future is completed successfully or fails, respectively. - -3. Registering a callback on the future which is already completed -will result in the callback being executed eventually (as implied by -1). - -4. In the event that multiple callbacks are registered on the future, -the order in which they are executed is not defined. In fact, the -callbacks may be executed concurrently with one another. -However, a particular `ExecutionContext` implementation may result -in a well-defined order. - -5. In the event that some of the callbacks throw an exception, the -other callbacks are executed regardless. - -6. In the event that some of the callbacks never complete (e.g. the -callback contains an infinite loop), the other callbacks may not be -executed at all. In these cases, a potentially blocking callback must -use the `blocking` construct (see below). - -7. Once executed, the callbacks are removed from the future object, -thus being eligible for GC. - - -### Functional Composition and For-Comprehensions - -The callback mechanism we have shown is sufficient to chain future -results with subsequent computations. -However, it is sometimes inconvenient and results in bulky code. -We show this with an example. Assume we have an API for -interfacing with a currency trading service. Suppose we want to buy US -dollars, but only when it's profitable. We first show how this could -be done using callbacks: - - val rateQuote = Future { - connection.getCurrentValue(USD) - } - - rateQuote onSuccess { case quote => - val purchase = Future { - if (isProfitable(quote)) connection.buy(amount, quote) - else throw new Exception("not profitable") - } - - purchase onSuccess { - case _ => println("Purchased " + amount + " USD") - } - } - -We start by creating a future `rateQuote` which gets the current exchange -rate. -After this value is obtained from the server and the future successfully -completed, the computation proceeds in the `onSuccess` callback and we are -ready to decide whether to buy or not. -We therefore create another future `purchase` which makes a decision to buy only if it's profitable -to do so, and then sends a request. -Finally, once the purchase is completed, we print a notification message -to the standard output. - -This works, but is inconvenient for two reasons. First, we have to use -`onSuccess`, and we have to nest the second `purchase` future within -it. Imagine that after the `purchase` is completed we want to sell -some other currency. We would have to repeat this pattern within the -`onSuccess` callback, making the code overly indented, bulky and hard -to reason about. - -Second, the `purchase` future is not in the scope with the rest of -the code-- it can only be acted upon from within the `onSuccess` -callback. This means that other parts of the application do not -see the `purchase` future and cannot register another `onSuccess` -callback to it, for example, to sell some other currency. - -For these two reasons, futures provide combinators which allow a -more straightforward composition. One of the basic combinators -is `map`, which, given a future and a mapping function for the value of -the future, produces a new future that is completed with the -mapped value once the original future is successfully completed. -You can reason about mapping futures in the same way you reason -about mapping collections. - -Let's rewrite the previous example using the `map` combinator: - - val rateQuote = Future { - connection.getCurrentValue(USD) - } - - val purchase = rateQuote map { quote => - if (isProfitable(quote)) connection.buy(amount, quote) - else throw new Exception("not profitable") - } - - purchase onSuccess { - case _ => println("Purchased " + amount + " USD") - } - -By using `map` on `rateQuote` we have eliminated one `onSuccess` callback and, -more importantly, the nesting. -If we now decide to sell some other currency, it suffices to use -`map` on `purchase` again. - -But what happens if `isProfitable` returns `false`, hence causing -an exception to be thrown? -In that case `purchase` is failed with that exception. -Furthermore, imagine that the connection was broken and that -`getCurrentValue` threw an exception, failing `rateQuote`. -In that case we'd have no value to map, so the `purchase` would -automatically be failed with the same exception as `rateQuote`. - -In conclusion, if the original future is -completed successfully then the returned future is completed with a -mapped value from the original future. If the mapping function throws -an exception the future is completed with that exception. If the -original future fails with an exception then the returned future also -contains the same exception. This exception propagating semantics is -present in the rest of the combinators, as well. - -One of the design goals for futures was to enable their use in for-comprehensions. -For this reason, futures also have the `flatMap`, `filter` and -`foreach` combinators. The `flatMap` method takes a function that maps the value -to a new future `g`, and then returns a future which is completed once -`g` is completed. - -Lets assume that we want to exchange US dollars for Swiss francs -(CHF). We have to fetch quotes for both currencies, and then decide on -buying based on both quotes. -Here is an example of `flatMap` and `withFilter` usage within for-comprehensions: - - val usdQuote = Future { connection.getCurrentValue(USD) } - val chfQuote = Future { connection.getCurrentValue(CHF) } - - val purchase = for { - usd <- usdQuote - chf <- chfQuote - if isProfitable(usd, chf) - } yield connection.buy(amount, chf) - - purchase onSuccess { - case _ => println("Purchased " + amount + " CHF") - } - -The `purchase` future is completed only once both `usdQuote` -and `chfQuote` are completed-- it depends on the values -of both these futures so its own computation cannot begin -earlier. - -The for-comprehension above is translated into: - - val purchase = usdQuote flatMap { - usd => - chfQuote - .withFilter(chf => isProfitable(usd, chf)) - .map(chf => connection.buy(amount, chf)) - } - -which is a bit harder to grasp than the for-comprehension, but -we analyze it to better understand the `flatMap` operation. -The `flatMap` operation maps its own value into some other future. -Once this different future is completed, the resulting future -is completed with its value. -In our example, `flatMap` uses the value of the `usdQuote` future -to map the value of the `chfQuote` into a third future which -sends a request to buy a certain amount of Swiss francs. -The resulting future `purchase` is completed only once this third -future returned from `map` completes. - -This can be mind-boggling, but fortunately the `flatMap` operation -is seldom used outside for-comprehensions, which are easier to -use and understand. - -The `filter` combinator creates a new future which contains the value -of the original future only if it satisfies some predicate. Otherwise, -the new future is failed with a `NoSuchElementException`. For futures -calling `filter` has exactly the same effect as does calling `withFilter`. - -The relationship between the `collect` and `filter` combinator is similar -to the relationship of these methods in the collections API. - -It is important to note that calling the `foreach` combinator does not -block to traverse the value once it becomes available. -Instead, the function for the `foreach` gets asynchronously -executed only if the future is completed successfully. This means that -the `foreach` has exactly the same semantics as the `onSuccess` -callback. - -Since the `Future` trait can conceptually contain two types of values -(computation results and exceptions), there exists a need for -combinators which handle exceptions. - -Let's assume that based on the `rateQuote` we decide to buy a certain -amount. The `connection.buy` method takes an `amount` to buy and the expected -`quote`. It returns the amount bought. If the -`quote` has changed in the meanwhile, it will throw a -`QuoteChangedException` and it will not buy anything. If we want our -future to contain `0` instead of the exception, we use the `recover` -combinator: - - val purchase: Future[Int] = rateQuote map { - quote => connection.buy(amount, quote) - } recover { - case QuoteChangedException() => 0 - } - -The `recover` combinator creates a new future which holds the same -result as the original future if it completed successfully. If it did -not then the partial function argument is applied to the `Throwable` -which failed the original future. If it maps the `Throwable` to some -value, then the new future is successfully completed with that value. -If the partial function is not defined on that `Throwable`, then the -resulting future is failed with the same `Throwable`. - -The `recoverWith` combinator creates a new future which holds the -same result as the original future if it completed successfully. -Otherwise, the partial function is applied to the `Throwable` which -failed the original future. If it maps the `Throwable` to some future, -then this future is completed with the result of that future. -Its relation to `recover` is similar to that of `flatMap` to `map`. - -Combinator `fallbackTo` creates a new future which holds the result -of this future if it was completed successfully, or otherwise the -successful result of the argument future. In the event that both this -future and the argument future fail, the new future is completed with -the exception from this future, as in the following example which -tries to print US dollar value, but prints the Swiss franc value in -the case it fails to obtain the dollar value: - - val usdQuote = Future { - connection.getCurrentValue(USD) - } map { - usd => "Value: " + usd + "$" - } - val chfQuote = Future { - connection.getCurrentValue(CHF) - } map { - chf => "Value: " + chf + "CHF" - } - - val anyQuote = usdQuote fallbackTo chfQuote - - anyQuote onSuccess { println(_) } - -The `andThen` combinator is used purely for side-effecting purposes. -It returns a new future with exactly the same result as the current -future, regardless of whether the current future failed or not. -Once the current future is completed with the result, the closure -corresponding to the `andThen` is invoked and then the new future is -completed with the same result as this future. This ensures that -multiple `andThen` calls are ordered, as in the following example -which stores the recent posts from a social network to a mutable set -and then renders all the posts to the screen: - - val allposts = mutable.Set[String]() - - Future { - session.getRecentPosts - } andThen { - case Success(posts) => allposts ++= posts - } andThen { - case _ => - clearAll() - for (post <- allposts) render(post) - } - -In summary, the combinators on futures are purely functional. -Every combinator returns a new future which is related to the -future it was derived from. - - -### Projections - -To enable for-comprehensions on a result returned as an exception, -futures also have projections. If the original future fails, the -`failed` projection returns a future containing a value of type -`Throwable`. If the original future succeeds, the `failed` projection -fails with a `NoSuchElementException`. The following is an example -which prints the exception to the screen: - - val f = Future { - 2 / 0 - } - for (exc <- f.failed) println(exc) - -The following example does not print anything to the screen: - - val f = Future { - 4 / 2 - } - for (exc <- f.failed) println(exc) - - - - - - - -### Extending Futures - -Support for extending the Futures API with additional utility methods is planned. -This will allow external frameworks to provide more specialized utilities. - - -## Blocking - -Futures are generally asynchronous and do not block the underlying execution threads. -However, in certain cases, it is necessary to block. -We distinguish two forms of blocking the execution thread: -invoking arbitrary code that blocks the thread from within the future, -and blocking from outside another future, waiting until that future gets completed. - - -### Blocking inside a Future - -As seen with the global `ExecutionContext`, it is possible to notify an `ExecutionContext` of a blocking call with the `blocking` construct. -The implementation is however at the complete discretion of the `ExecutionContext`. While some `ExecutionContext` such as `ExecutionContext.global` -implement `blocking` by means of a `ManagedBlocker`, some execution contexts such as the fixed thread pool: - - ExecutionContext.fromExecutor(Executors.newFixedThreadPool(x)) - -will do nothing, as shown in the following: - - implicit val ec = ExecutionContext.fromExecutor( - Executors.newFixedThreadPool(4)) - Future { - blocking { blockingStuff() } - } - -Has the same effect as - - Future { blockingStuff() } - -The blocking code may also throw an exception. In this case, the exception is forwarded to the caller. - - -### Blocking outside the Future - -As mentioned earlier, blocking on a future is strongly discouraged -for the sake of performance and for the prevention of deadlocks. -Callbacks and combinators on futures are a preferred way to use their results. -However, blocking may be necessary in certain situations and is supported by -the Futures and Promises API. - -In the currency trading example above, one place to block is at the -end of the application to make sure that all of the futures have been completed. -Here is an example of how to block on the result of a future: - - import scala.concurrent._ - import scala.concurrent.duration._ - - def main(args: Array[String]) { - val rateQuote = Future { - connection.getCurrentValue(USD) - } - - val purchase = rateQuote map { quote => - if (isProfitable(quote)) connection.buy(amount, quote) - else throw new Exception("not profitable") - } - - Await.result(purchase, 0 nanos) - } - -In the case that the future fails, the caller is forwarded the -exception that the future is failed with. This includes the `failed` -projection-- blocking on it results in a `NoSuchElementException` -being thrown if the original future is completed successfully. - -Alternatively, calling `Await.ready` waits until the future becomes -completed, but does not retrieve its result. In the same way, calling -that method will not throw an exception if the future is failed. - -The `Future` trait implements the `Awaitable` trait with methods -method `ready()` and `result()`. These methods cannot be called directly -by the clients-- they can only be called by the execution context. - - - -## Exceptions - -When asynchronous computations throw unhandled exceptions, futures -associated with those computations fail. Failed futures store an -instance of `Throwable` instead of the result value. `Future`s provide -the `onFailure` callback method, which accepts a `PartialFunction` to -be applied to a `Throwable`. The following special exceptions are -treated differently: - -1. `scala.runtime.NonLocalReturnControl[_]` -- this exception holds a value -associated with the return. Typically, `return` constructs in method -bodies are translated to `throw`s with this exception. Instead of -keeping this exception, the associated value is stored into the future or a promise. - -2. `ExecutionException` - stored when the computation fails due to an -unhandled `InterruptedException`, `Error` or a -`scala.util.control.ControlThrowable`. In this case the -`ExecutionException` has the unhandled exception as its cause. The rationale -behind this is to prevent propagation of critical and control-flow related -exceptions normally not handled by the client code and at the same time inform -the client in which future the computation failed. - -Fatal exceptions (as determined by `NonFatal`) are rethrown in the thread executing -the failed asynchronous computation. This informs the code managing the executing -threads of the problem and allows it to fail fast, if necessary. See -[`NonFatal`](http://www.scala-lang.org/api/current/index.html#scala.util.control.NonFatal$) -for a more precise description of the semantics. - -## Promises - -So far we have only considered `Future` objects created by -asynchronous computations started using the `future` method. -However, futures can also be created using *promises*. - -While futures are defined as a type of read-only placeholder object -created for a result which doesn't yet exist, a promise can be thought -of as a writable, single-assignment container, which completes a -future. That is, a promise can be used to successfully complete a -future with a value (by "completing" the promise) using the `success` -method. Conversely, a promise can also be used to complete a future -with an exception, by failing the promise, using the `failure` method. - -A promise `p` completes the future returned by `p.future`. This future -is specific to the promise `p`. Depending on the implementation, it -may be the case that `p.future eq p`. - -Consider the following producer-consumer example, in which one computation -produces a value and hands it off to another computation which consumes -that value. This passing of the value is done using a promise. - - import scala.concurrent.{ Future, Promise } - import scala.concurrent.ExecutionContext.Implicits.global - - val p = Promise[T]() - val f = p.future - - val producer = Future { - val r = produceSomething() - p success r - continueDoingSomethingUnrelated() - } - - val consumer = Future { - startDoingSomething() - f onSuccess { - case r => doSomethingWithResult() - } - } - -Here, we create a promise and use its `future` method to obtain the -`Future` that it completes. Then, we begin two asynchronous -computations. The first does some computation, resulting in a value -`r`, which is then used to complete the future `f`, by fulfilling -the promise `p`. The second does some computation, and then reads the result `r` -of the completed future `f`. Note that the `consumer` can obtain the -result before the `producer` task is finished executing -the `continueDoingSomethingUnrelated()` method. - -As mentioned before, promises have single-assignment semantics. As -such, they can be completed only once. Calling `success` on a -promise that has already been completed (or failed) will throw an -`IllegalStateException`. - -The following example shows how to fail a promise. - - val p = Promise[T]() - val f = p.future - - val producer = Future { - val r = someComputation - if (isInvalid(r)) - p failure (new IllegalStateException) - else { - val q = doSomeMoreComputation(r) - p success q - } - } - -Here, the `producer` computes an intermediate result `r`, and checks -whether it's valid. In the case that it's invalid, it fails the -promise by completing the promise `p` with an exception. In this case, -the associated future `f` is failed. Otherwise, the `producer` -continues its computation, and finally completes the future `f` with a -valid result, by completing promise `p`. - -Promises can also be completed with a `complete` method which takes -a potential value `Try[T]`-- either a failed result of type `Failure[Throwable]` or a -successful result of type `Success[T]`. - -Analogous to `success`, calling `failure` and `complete` on a promise that has already -been completed will throw an `IllegalStateException`. - -One nice property of programs written using promises with operations -described so far and futures which are composed through monadic -operations without side-effects is that these programs are -deterministic. Deterministic here means that, given that no exception -is thrown in the program, the result of the program (values observed -in the futures) will always be the same, regardless of the execution -schedule of the parallel program. - -In some cases the client may want to complete the promise only if it -has not been completed yet (e.g., there are several HTTP requests being -executed from several different futures and the client is interested only -in the first HTTP response - corresponding to the first future to -complete the promise). For these reasons methods `tryComplete`, -`trySuccess` and `tryFailure` exist on future. The client should be -aware that using these methods results in programs which are not -deterministic, but depend on the execution schedule. - -The method `completeWith` completes the promise with another -future. After the future is completed, the promise gets completed with -the result of that future as well. The following program prints `1`: - - val f = Future { 1 } - val p = Promise[Int]() - - p completeWith f - - p.future onSuccess { - case x => println(x) - } - -When failing a promise with an exception, three subtypes of `Throwable`s -are handled specially. If the `Throwable` used to break the promise is -a `scala.runtime.NonLocalReturnControl`, then the promise is completed with -the corresponding value. If the `Throwable` used to break the promise is -an instance of `Error`, `InterruptedException`, or -`scala.util.control.ControlThrowable`, the `Throwable` is wrapped as -the cause of a new `ExecutionException` which, in turn, is failing -the promise. - -Using promises, the `onComplete` method of the futures and the `future` construct -you can implement any of the functional composition combinators described earlier. -Let's assume you want to implement a new combinator `first` which takes -two futures `f` and `g` and produces a third future which is completed by either -`f` or `g` (whichever comes first), but only given that it is successful. - -Here is an example of how to do it: - - def first[T](f: Future[T], g: Future[T]): Future[T] = { - val p = promise[T] - - f onSuccess { - case x => p.trySuccess(x) - } - - g onSuccess { - case x => p.trySuccess(x) - } - - p.future - } - -Note that in this implementation, if neither `f` nor `g` succeeds, then `first(f, g)` never completes (either with a value or with an exception). - - - -## Utilities - -To simplify handling of time in concurrent applications `scala.concurrent` - introduces a `Duration` abstraction. `Duration` is not supposed to be yet another - general time abstraction. It is meant to be used with concurrency libraries and - resides in `scala.concurrent` package. - -`Duration` is the base class representing length of time. It can be either finite or infinite. - Finite duration is represented with `FiniteDuration` class which is constructed from `Long` length and - `java.util.concurrent.TimeUnit`. Infinite durations, also extended from `Duration`, - exist in only two instances , `Duration.Inf` and `Duration.MinusInf`. Library also - provides several `Duration` subclasses for implicit conversion purposes and those should - not be used. - -Abstract `Duration` contains methods that allow : - -1. Conversion to different time units (`toNanos`, `toMicros`, `toMillis`, -`toSeconds`, `toMinutes`, `toHours`, `toDays` and `toUnit(unit: TimeUnit)`). -2. Comparison of durations (`<`, `<=`, `>` and `>=`). -3. Arithmetic operations (`+`, `-`, `*`, `/` and `unary_-`). -4. Minimum and maximum between `this` duration and the one supplied in the argument (`min`, `max`). -5. Check if the duration is finite (`isFinite`). - -`Duration` can be instantiated in the following ways: - -1. Implicitly from types `Int` and `Long`. For example `val d = 100 millis`. -2. By passing a `Long` length and a `java.util.concurrent.TimeUnit`. -For example `val d = Duration(100, MILLISECONDS)`. -3. By parsing a string that represent a time period. For example `val d = Duration("1.2 µs")`. - -Duration also provides `unapply` methods so it can be used in pattern matching constructs. -Examples: - - import scala.concurrent.duration._ - import java.util.concurrent.TimeUnit._ - - // instantiation - val d1 = Duration(100, MILLISECONDS) // from Long and TimeUnit - val d2 = Duration(100, "millis") // from Long and String - val d3 = 100 millis // implicitly from Long, Int or Double - val d4 = Duration("1.2 µs") // from String - - // pattern matching - val Duration(length, unit) = 5 millis - - - diff --git a/overviews/core/_posts/2012-09-20-implicit-classes.md b/overviews/core/_posts/2012-09-20-implicit-classes.md deleted file mode 100644 index c37f49361e..0000000000 --- a/overviews/core/_posts/2012-09-20-implicit-classes.md +++ /dev/null @@ -1,93 +0,0 @@ ---- -layout: overview -title: Implicit Classes -overview: implicit-classes -label-color: success -label-text: Available -languages: [zh-cn] ---- - -**Josh Suereth** - -## Introduction - -Scala 2.10 introduced a new feature called *implicit classes*. An *implicit class* is a class -marked with the `implicit` keyword. This keyword makes the class' primary constructor available -for implicit conversions when the class is in scope. - -Implicit classes were proposed in [SIP-13](http://docs.scala-lang.org/sips/pending/implicit-classes.html). - -## Usage - -To create an implicit class, simply place the `implicit` keyword in front of an appropriate -class. Here's an example: - - object Helpers { - implicit class IntWithTimes(x: Int) { - def times[A](f: => A): Unit = { - def loop(current: Int): Unit = - if(current > 0) { - f - loop(current - 1) - } - loop(x) - } - } - } - -This example creates the implicit class `IntWithTimes`. This class wraps an `Int` value and provides -a new method, `times`. To use this class, just import it into scope and call the `times` method. -Here's an example: - - scala> import Helpers._ - import Helpers._ - - scala> 5 times println("HI") - HI - HI - HI - HI - HI - -For an implicit class to work, its name must be in scope and unambiguous, like any other implicit -value or conversion. - - -## Restrictions - -Implicit classes have the following restrictions: - -**1. They must be defined inside of another `trait`/`class`/`object`.** - - - object Helpers { - implicit class RichInt(x: Int) // OK! - } - implicit class RichDouble(x: Double) // BAD! - - -**2. They may only take one non-implicit argument in their constructor.** - - - implicit class RichDate(date: java.util.Date) // OK! - implicit class Indexer[T](collecton: Seq[T], index: Int) // BAD! - implicit class Indexer[T](collecton: Seq[T])(implicit index: Index) // OK! - - -While it's possible to create an implicit class with more than one non-implicit argument, such classes -aren't used during implicit lookup. - - -**3. There may not be any method, member or object in scope with the same name as the implicit class.** - -*Note: This means an implicit class cannot be a case class*. - - object Bar - implicit class Bar(x: Int) // BAD! - - val x = 5 - implicit class x(y: Int) // BAD! - - implicit case class Baz(x: Int) // BAD! - - diff --git a/overviews/core/_posts/2012-09-21-string-interpolation.md b/overviews/core/_posts/2012-09-21-string-interpolation.md deleted file mode 100644 index eae10a497d..0000000000 --- a/overviews/core/_posts/2012-09-21-string-interpolation.md +++ /dev/null @@ -1,136 +0,0 @@ ---- -layout: overview -title: String Interpolation -discourse: true -label-color: success -label-text: New in 2.10 -overview: string-interpolation -languages: [es, ja, zh-cn] ---- - -**Josh Suereth** - -## Introduction - -Starting in Scala 2.10.0, Scala offers a new mechanism to create strings from your data: String Interpolation. -String Interpolation allows users to embed variable references directly in *processed* string literals. Here's an example: - - val name = "James" - println(s"Hello, $name") // Hello, James - -In the above, the literal `s"Hello, $name"` is a *processed* string literal. This means that the compiler does some additional -work to this literal. A processed string literal is denoted by a set of characters preceding the `"`. String interpolation -was introduced by [SIP-11](http://docs.scala-lang.org/sips/pending/string-interpolation.html), which contains all details of the implementation. - -## Usage - -Scala provides three string interpolation methods out of the box: `s`, `f` and `raw`. - -### The `s` String Interpolator - -Prepending `s` to any string literal allows the usage of variables directly in the string. You've already seen an example here: - - val name = "James" - println(s"Hello, $name") // Hello, James - -Here `$name` is nested inside an `s` processed string. The `s` interpolator knows to insert the value of the `name` variable at this location -in the string, resulting in the string `Hello, James`. With the `s` interpolator, any name that is in scope can be used within a string. - -String interpolators can also take arbitrary expressions. For example: - - println(s"1 + 1 = ${1 + 1}") - -will print the string `1 + 1 = 2`. Any arbitrary expression can be embedded in `${}`. - - -### The `f` Interpolator - -Prepending `f` to any string literal allows the creation of simple formatted strings, similar to `printf` in other languages. When using the `f` -interpolator, all variable references should be followed by a `printf`-style format string, like `%d`. Let's look at an example: - - val height = 1.9d - val name = "James" - println(f"$name%s is $height%2.2f meters tall") // James is 1.90 meters tall - -The `f` interpolator is typesafe. If you try to pass a format string that only works for integers but pass a double, the compiler will issue an -error. For example: - - val height: Double = 1.9d - - scala> f"$height%4d" - :9: error: type mismatch; - found : Double - required: Int - f"$height%4d" - ^ - -The `f` interpolator makes use of the string format utilities available from Java. The formats allowed after the `%` character are outlined in the -[Formatter javadoc](http://docs.oracle.com/javase/1.6.0/docs/api/java/util/Formatter.html#detail). If there is no `%` character after a variable -definition a formatter of `%s` (`String`) is assumed. - - -### The `raw` Interpolator - -The raw interpolator is similar to the `s` interpolator except that it performs no escaping of literals within the string. Here's an example processed string: - - scala> s"a\nb" - res0: String = - a - b - -Here the `s` string interpolator replaced the characters `\n` with a return character. The `raw` interpolator will not do that. - - scala> raw"a\nb" - res1: String = a\nb - -The raw interpolator is useful when you want to avoid having expressions like `\n` turn into a return character. - - -In addition to the three default string interpolators, users can define their own. - -## Advanced Usage - -In Scala, all processed string literals are simple code transformations. Anytime the compiler encounters a string literal of the form: - - id"string content" - -it transforms it into a method call (`id`) on an instance of [StringContext](http://www.scala-lang.org/api/current/index.html#scala.StringContext). -This method can also be available on implicit scope. To define our own string interpolation, we simply need to create an implicit class that adds a new method -to `StringContext`. Here's an example: - - // Note: We extends AnyVal to prevent runtime instantiation. See - // value class guide for more info. - implicit class JsonHelper(val sc: StringContext) extends AnyVal { - def json(args: Any*): JSONObject = sys.error("TODO - IMPLEMENT") - } - - def giveMeSomeJson(x: JSONObject): Unit = ... - - giveMeSomeJson(json"{ name: $name, id: $id }") - -In this example, we're attempting to create a JSON literal syntax using string interpolation. The JsonHelper implicit class must be in scope to use this syntax, and the json method would need a complete implementation. However, the result of such a formatted string literal would not be a string, but a `JSONObject`. - -When the compiler encounters the literal `json"{ name: $name, id: $id }"` it rewrites it to the following expression: - - new StringContext("{ name: ", ", id: ", " }").json(name, id) - -The implicit class is then used to rewrite it to the following: - - new JsonHelper(new StringContext("{ name: ", ", id: ", " }")).json(name, id) - -So, the `json` method has access to the raw pieces of strings and each expression as a value. A simple (buggy) implementation of this method could be: - - implicit class JsonHelper(val sc: StringContext) extends AnyVal { - def json(args: Any*): JSONObject = { - val strings = sc.parts.iterator - val expressions = args.iterator - var buf = new StringBuffer(strings.next) - while(strings.hasNext) { - buf append expressions.next - buf append strings.next - } - parseJson(buf) - } - } - -Each of the string portions of the processed string are exposed in the `StringContext`'s `parts` member. Each of the expression values is passed into the `json` method's `args` parameter. The `json` method takes this and generates a big string which it then parses into JSON. A more sophisticated implementation could avoid having to generate this string and simply construct the JSON directly from the raw strings and expression values. diff --git a/overviews/core/_posts/2012-11-03-value-classes.md b/overviews/core/_posts/2012-11-03-value-classes.md deleted file mode 100644 index aea31a6553..0000000000 --- a/overviews/core/_posts/2012-11-03-value-classes.md +++ /dev/null @@ -1,265 +0,0 @@ ---- -layout: overview -title: Value Classes and Universal Traits -label-color: success -label-text: New in 2.10 -overview: value-classes -languages: [ja, zh-cn] ---- - -**Mark Harrah** - -## Introduction - -Value classes are a new mechanism in Scala to avoid allocating runtime objects. -This is accomplished through the definition of new `AnyVal` subclasses. -They were proposed in [SIP-15](http://docs.scala-lang.org/sips/pending/value-classes.html). -The following shows a very minimal value class definition: - - class Wrapper(val underlying: Int) extends AnyVal - -It has a single, public `val` parameter that is the underlying runtime representation. -The type at compile time is `Wrapper`, but at runtime, the representation is an `Int`. -A value class can define `def`s, but no `val`s, `var`s, or nested `traits`s, `class`es or `object`s: - - class Wrapper(val underlying: Int) extends AnyVal { - def foo: Wrapper = new Wrapper(underlying * 19) - } - -A value class can only extend *universal traits* and cannot be extended itself. -A *universal trait* is a trait that extends `Any`, only has `def`s as members, and does no initialization. -Universal traits allow basic inheritance of methods for value classes, but they *incur the overhead of allocation*. -For example, - - trait Printable extends Any { - def print(): Unit = println(this) - } - class Wrapper(val underlying: Int) extends AnyVal with Printable - - val w = new Wrapper(3) - w.print() // actually requires instantiating a Wrapper instance - -The remaining sections of this documentation show use cases, details on when allocations do and do not occur, and concrete examples of limitations of value classes. - -## Extension methods - -One use case for value classes is to combine them with implicit classes ([SIP-13](http://docs.scala-lang.org/sips/pending/implicit-classes.html)) for allocation-free extension methods. Using an implicit class provides a more convenient syntax for defining extension methods, while value classes remove the runtime overhead. A good example is the `RichInt` class in the standard library. `RichInt` extends the `Int` type with several methods. Because it is a value class, an instance of `RichInt` doesn't need to be created when using `RichInt` methods. - -The following fragment of `RichInt` shows how it extends `Int` to allow the expression `3.toHexString`: - - implicit class RichInt(val self: Int) extends AnyVal { - def toHexString: String = java.lang.Integer.toHexString(self) - } - -At runtime, this expression `3.toHexString` is optimised to the equivalent of a method call on a static object -(`RichInt$.MODULE$.extension$toHexString(3)`), rather than a method call on a newly instantiated object. - -## Correctness - -Another use case for value classes is to get the type safety of a data type without the runtime allocation overhead. -For example, a fragment of a data type that represents a distance might look like: - - class Meter(val value: Double) extends AnyVal { - def +(m: Meter): Meter = new Meter(value + m.value) - } - -Code that adds two distances, such as - - val x = new Meter(3.4) - val y = new Meter(4.3) - val z = x + y - -will not actually allocate any `Meter` instances, but will only use primitive doubles at runtime. - -*Note: You can use case classes and/or extension methods for cleaner syntax in practice.* - -## When Allocation Is Necessary - -Because the JVM does not support value classes, Scala sometimes needs to actually instantiate a value class. -Full details may be found in [SIP-15](http://docs.scala-lang.org/sips/pending/value-classes.html). - -### Allocation Summary - -A value class is actually instantiated when: - -1. a value class is treated as another type. -2. a value class is assigned to an array. -3. doing runtime type tests, such as pattern matching. - -### Allocation Details - -Whenever a value class is treated as another type, including a universal trait, an instance of the actual value class must be instantiated. -As an example, consider the `Meter` value class: - - trait Distance extends Any - case class Meter(val value: Double) extends AnyVal with Distance - -A method that accepts a value of type `Distance` will require an actual `Meter` instance. -In the following example, the `Meter` classes are actually instantiated: - - def add(a: Distance, b: Distance): Distance = ... - add(Meter(3.4), Meter(4.3)) - -If the signature of `add` were instead: - - def add(a: Meter, b: Meter): Meter = ... - -then allocations would not be necessary. -Another instance of this rule is when a value class is used as a type argument. -For example, the actual Meter instance must be created for even a call to identity: - - def identity[T](t: T): T = t - identity(Meter(5.0)) - -Another situation where an allocation is necessary is when assigning to an array, even if it is an array of that value class. -For example, - - val m = Meter(5.0) - val array = Array[Meter](m) - -The array here contains actual `Meter` instances and not just the underlying double primitives. - -Lastly, type tests such as those done in pattern matching or `asInstanceOf` require actual value class instances: - - case class P(val i: Int) extends AnyVal - - val p = new P(3) - p match { // new P instantiated here - case P(3) => println("Matched 3") - case P(x) => println("Not 3") - } - -## Limitations - -Value classes currently have several limitations, in part because the JVM does not natively support the concept of value classes. -Full details on the implementation of value classes and their limitations may be found in [SIP-15](http://docs.scala-lang.org/sips/pending/value-classes.html). - -### Summary of Limitations - -A value class ... - -1. ... must have only a primary constructor with exactly one public, val parameter whose type is not a value class. (From Scala 2.11.0, the parameter may be non-public.) -2. ... may not have specialized type parameters. -3. ... may not have nested or local classes, traits, or objects -4. ... may not define a equals or hashCode method. -5. ... must be a top-level class or a member of a statically accessible object -6. ... can only have defs as members. In particular, it cannot have lazy vals, vars, or vals as members. -7. ... cannot be extended by another class. - -### Examples of Limitations - -This section provides many concrete consequences of these limitations not already described in the necessary allocations section. - -Multiple constructor parameters are not allowed: - - class Complex(val real: Double, val imag: Double) extends AnyVal - -and the Scala compiler will generate the following error message: - - Complex.scala:1: error: value class needs to have exactly one public val parameter - class Complex(val real: Double, val imag: Double) extends AnyVal - ^ - -Because the constructor parameter must be a `val`, it cannot be a by-name parameter: - - NoByName.scala:1: error: `val' parameters may not be call-by-name - class NoByName(val x: => Int) extends AnyVal - ^ - -Scala doesn't allow lazy val constructor parameters, so that isn't allowed either. -Multiple constructors are not allowed: - - class Secondary(val x: Int) extends AnyVal { - def this(y: Double) = this(y.toInt) - } - - Secondary.scala:2: error: value class may not have secondary constructors - def this(y: Double) = this(y.toInt) - ^ - -A value class cannot have lazy vals or vals as members and cannot have nested classes, traits, or objects: - - class NoLazyMember(val evaluate: () => Double) extends AnyVal { - val member: Int = 3 - lazy val x: Double = evaluate() - object NestedObject - class NestedClass - } - - Invalid.scala:2: error: this statement is not allowed in value class: private[this] val member: Int = 3 - val member: Int = 3 - ^ - Invalid.scala:3: error: this statement is not allowed in value class: lazy private[this] var x: Double = NoLazyMember.this.evaluate.apply() - lazy val x: Double = evaluate() - ^ - Invalid.scala:4: error: value class may not have nested module definitions - object NestedObject - ^ - Invalid.scala:5: error: value class may not have nested class definitions - class NestedClass - ^ - -Note that local classes, traits, and objects are not allowed either, as in the following: - - class NoLocalTemplates(val x: Int) extends AnyVal { - def aMethod = { - class Local - ... - } - } - -A current implementation restriction is that value classes cannot be nested: - - class Outer(val inner: Inner) extends AnyVal - class Inner(val value: Int) extends AnyVal - - Nested.scala:1: error: value class may not wrap another user-defined value class - class Outer(val inner: Inner) extends AnyVal - ^ - -Additionally, structural types cannot use value classes in method parameter or return types: - - class Value(val x: Int) extends AnyVal - object Usage { - def anyValue(v: { def value: Value }): Value = - v.value - } - - Struct.scala:3: error: Result type in structural refinement may not refer to a user-defined value class - def anyValue(v: { def value: Value }): Value = - ^ - -A value class may not extend a non-universal trait and a value class may not itself be extended: - - trait NotUniversal - class Value(val x: Int) extends AnyVal with notUniversal - class Extend(x: Int) extends Value(x) - - Extend.scala:2: error: illegal inheritance; superclass AnyVal - is not a subclass of the superclass Object - of the mixin trait NotUniversal - class Value(val x: Int) extends AnyVal with NotUniversal - ^ - Extend.scala:3: error: illegal inheritance from final class Value - class Extend(x: Int) extends Value(x) - ^ - -The second error messages shows that although the `final` modifier is not explicitly specified for a value class, it is assumed. - -Another limitation that is a result of supporting only one parameter to a class is that a value class must be top-level or a member of a statically accessible object. -This is because a nested value class would require a second parameter that references the enclosing class. -So, this is not allowed: - - class Outer { - class Inner(val x: Int) extends AnyVal - } - - Outer.scala:2: error: value class may not be a member of another class - class Inner(val x: Int) extends AnyVal - ^ - -but this is allowed because the enclosing object is top-level: - - object Outer { - class Inner(val x: Int) extends AnyVal - } diff --git a/overviews/core/_posts/2012-11-08-actors-migration-guide.md b/overviews/core/_posts/2012-11-08-actors-migration-guide.md deleted file mode 100644 index 021e1a7455..0000000000 --- a/overviews/core/_posts/2012-11-08-actors-migration-guide.md +++ /dev/null @@ -1,583 +0,0 @@ ---- -layout: overview -title: The Scala Actors Migration Guide -label-color: success -label-text: New in 2.10 -overview: actors-migration-guide -languages: [zh-cn] ---- - -**Vojin Jovanovic and Philipp Haller** - -## Introduction - -Starting with Scala 2.11.0, the Scala -[Actors](http://docs.scala-lang.org/overviews/core/actors.html) -library is deprecated. Already in Scala 2.10.0 the default actor library is -[Akka](http://akka.io). - -To ease the migration from Scala Actors to Akka we are providing the -Actor Migration Kit (AMK). The AMK consists of an extension to Scala -Actors which is enabled by including the `scala-actors-migration.jar` -on a project's classpath. In addition, Akka 2.1 includes features, -such as the `ActorDSL` singleton, which enable a simpler conversion of -code using Scala Actors to Akka. The purpose of this document is to -guide users through the migration process and explain how to use the -AMK. - -This guide has the following structure. In Section "Limitations of the -Migration Kit" we outline the main limitations of the migration -kit. In Section "Migration Overview" we describe the migration process -and talk about changes in the [Scala -distribution](http://www.scala-lang.org/downloads) that make the -migration possible. Finally, in Section "Step by Step Guide for -Migrating to Akka" we show individual steps, with working examples, -that are recommended when migrating from Scala Actors to Akka's -actors. - -A disclaimer: concurrent code is notorious for bugs that are hard to -debug and fix. Due to differences between the two actor -implementations it is possible that errors appear. It is recommended -to thoroughly test the code after each step of the migration process. - -## Limitations of the Migration Kit - -Due to differences in Akka and Scala actor models the complete functionality can not be migrated smoothly. The following list explains parts of the behavior that are hard to migrate: - -1. Relying on termination reason and bidirectional behavior with `link` method - Scala and Akka actors have different fault-handling and actor monitoring models. -In Scala linked actors terminate if one of the linked parties terminates abnormally. If termination is tracked explicitly (by `self.trapExit`) the actor receives -the termination reason from the failed actor. This functionality can not be migrated to Akka with the AMK. The AMK allows migration only for the -[Akka monitoring](http://doc.akka.io/docs/akka/2.1.0/general/supervision.html#What_Lifecycle_Monitoring_Means) -mechanism. Monitoring is different than linking because it is unidirectional and the termination reason is now known. If monitoring support is not enough, the migration -of `link` must be postponed until the last possible moment (Step 5 of migration). -Then, when moving to Akka, users must create an [supervision hierarchy](http://doc.akka.io/docs/akka/2.1.0/general/supervision.html) that will handle faults. - -2. Usage of the `restart` method - Akka does not provide explicit restart of actors so we can not provide the smooth migration for this use-case. -The user must change the system so there are no usages of the `restart` method. - -3. Usage of method `getState` - Akka actors do not have explicit state so this functionality can not be migrated. The user code must not -have `getState` invocations. - -4. Not starting actors right after instantiation - Akka actors are automatically started when instantiated. Users will have to -reshape their system so it starts all the actors right after their instantiation. - -5. Method `mailboxSize` does not exist in Akka and therefore can not be migrated. This method is seldom used and can easily be removed. - - -## Migration Overview - -### Migration Kit -In Scala 2.10.0 actors reside inside the [Scala distribution](http://www.scala-lang.org/downloads) as a separate jar ( *scala-actors.jar* ), and -the their interface is deprecated. The distribution also includes Akka actors in the *akka-actor.jar*. -The AMK resides both in the Scala actors and in the *akka-actor.jar*. Future major releases of Scala will not contain Scala actors and the AMK. - -To start the migration user needs to add the *scala-actors.jar* and the *scala-actors-migration.jar* to the build of their projects. -Addition of *scala-actors.jar* and *scala-actors-migration.jar* enables the usage of the AMK described below. - These artifacts reside in the [Scala Tools](https://oss.sonatype.org/content/groups/scala-tools/org/scala-lang/) - repository and in the [Scala distribution](http://www.scala-lang.org/downloads). - -### Step by Step Migration -Actor Migration Kit should be used in 5 steps. Each step is designed to introduce minimal changes -to the code base and allows users to run all system tests after it. In the first four steps of the migration -the code will use the Scala actors implementation. However, the methods and class signatures will be transformed to closely resemble Akka. -The migration kit on the Scala side introduces a new actor type (`ActWithStash`) and enforces access to actors through the `ActorRef` interface. - -It also enforces creation of actors through special methods on the `ActorDSL` object. In these steps it will be possible to migrate one -actor at a time. This reduces the possibility of complex errors that are caused by several bugs introduced at the same time. - -After the migration on the Scala side is complete the user should change import statements and change -the library used to Akka. On the Akka side, the `ActorDSL` and the `ActWithStash` allow - modeling the `react` construct of Scala Actors and their life cycle. This step migrates all actors to the Akka back-end and could introduce bugs in the system. Once code is migrated to Akka, users will be able to use all the features of Akka. - -## Step by Step Guide for Migrating to Akka - -In this chapter we will go through 5 steps of the actor migration. After each step the code can be tested for possible errors. In the first 4 - steps one can migrate one actor at a time and test the functionality. However, the last step migrates all actors to Akka and it can be tested -only as a whole. After this step the system should have the same functionality as before, however it will use the Akka actor library. - -### Step 1 - Everything as an Actor -The Scala actors library provides public access to multiple types of actors. They are organized in the class hierarchy and each subclass -provides slightly richer functionality. To make further steps of the migration easier we will first change each actor in the system to be of type `Actor`. -This migration step is straightforward since the `Actor` class is located at the bottom of the hierarchy and provides the broadest functionality. - -The Actors from the Scala library should be migrated according to the following rules: - -1. `class MyServ extends Reactor[T]` -> `class MyServ extends Actor` - - Note that `Reactor` provides an additional type parameter which represents the type of the messages received. If user code uses -that information then one needs to: _i)_ apply pattern matching with explicit type, or _ii)_ do the downcast of a message from -`Any` to the type `T`. - -2. `class MyServ extends ReplyReactor` -> `class MyServ extends Actor` - -3. `class MyServ extends DaemonActor` -> `class MyServ extends Actor` - - To pair the functionality of the `DaemonActor` add the following line to the class definition. - - override def scheduler: IScheduler = DaemonScheduler - -### Step 2 - Instantiations - -In Akka, actors can be accessed only through the narrow interface called `ActorRef`. Instances of `ActorRef` can be acquired either -by invoking an `actor` method on the `ActorDSL` object or through the `actorOf` method on an instance of an `ActorRefFactory`. -In the Scala side of AMK we provide a subset of the Akka `ActorRef` and the `ActorDSL` which is the actual singleton object in the Akka library. - -This step of the migration makes all accesses to actors through `ActorRef`s. First, we show how to migrate common patterns for instantiating -Scala `Actor`s. Then we show how to overcome issues with the different interfaces of `ActorRef` and `Actor`, respectively. - -#### Actor Instantiation - -The translation rules for actor instantiation (the following rules require importing `scala.actors.migration._`): - -1. Constructor Call Instantiation - - val myActor = new MyActor(arg1, arg2) - myActor.start() - - should be replaced with - - ActorDSL.actor(new MyActor(arg1, arg2)) - -2. DSL for Creating Actors - - val myActor = actor { - // actor definition - } - - should be replaced with - - val myActor = ActorDSL.actor(new Actor { - def act() { - // actor definition - } - }) - -3. Object Extended from the `Actor` Trait - - object MyActor extends Actor { - // MyActor definition - } - MyActor.start() - - should be replaced with - - class MyActor extends Actor { - // MyActor definition - } - - object MyActor { - val ref = ActorDSL.actor(new MyActor) - } - - All accesses to the object `MyActor` should be replaced with accesses to `MyActor.ref`. - -Note that Akka actors are always started on instantiation. In case actors in the migrated - system are created and started at different locations, and changing this can affect the behavior of the system, -users need to change the code so actors are started right after instantiation. - -Remote actors also need to be fetched as `ActorRef`s. To get an `ActorRef` of an remote actor use the method `selectActorRef`. - -#### Different Method Signatures - -At this point we have changed all the actor instantiations to return `ActorRef`s, however, we are not done yet. -There are differences in the interface of `ActorRef`s and `Actor`s so we need to change the methods invoked on each migrated instance. -Unfortunately, some of the methods that Scala `Actor`s provide can not be migrated. For the following methods users need to find a workaround: - -1. `getState()` - actors in Akka are managed by their supervising actors and are restarted by default. -In that scenario state of an actor is not relevant. - -2. `restart()` - explicitly restarts a Scala actor. There is no corresponding functionality in Akka. - -All other `Actor` methods need to be translated to two methods that exist on the ActorRef. The translation is achieved by the rules described below. -Note that all the rules require the following imports: - - import scala.concurrent.duration._ - import scala.actors.migration.pattern.ask - import scala.actors.migration._ - import scala.concurrent._ - -Additionally rules 1-3 require an implicit `Timeout` with infinite duration defined in the scope. However, since Akka does not allow for infinite timeouts, we will use -100 years. For example: - - implicit val timeout = Timeout(36500 days) - -Rules: - -1. `!!(msg: Any): Future[Any]` gets replaced with `?`. This rule will change a return type to the `scala.concurrent.Future` which might not type check. -Since `scala.concurrent.Future` has broader functionality than the previously returned one, this type error can be easily fixed with local changes: - - actor !! message -> respActor ? message - -2. `!![A] (msg: Any, handler: PartialFunction[Any, A]): Future[A]` gets replaced with `?`. The handler can be extracted as a separate -function and then applied to the generated future result. The result of a handle should yield another future like -in the following example: - - val handler: PartialFunction[Any, T] = ... // handler - actor !! (message, handler) -> (respActor ? message) map handler - -3. `!? (msg: Any): Any` gets replaced with `?` and explicit blocking on the returned future: - - actor !? message -> - Await.result(respActor ? message, Duration.Inf) - -4. `!? (msec: Long, msg: Any): Option[Any]` gets replaced with `?` and explicit blocking on the future: - - actor !? (dur, message) -> - val res = respActor.?(message)(Timeout(dur milliseconds)) - val optFut = res map (Some(_)) recover { case _ => None } - Await.result(optFut, Duration.Inf) - -Public methods that are not mentioned here are declared public for purposes of the actors DSL. They can be used only -inside the actor definition so their migration is not relevant in this step. - -### Step 3 - `Actor`s become `ActWithStash`s - -At this point all actors inherit the `Actor` trait, we instantiate actors through special factory methods, -and all actors are accessed through the `ActorRef` interface. -Now we need to change all actors to the `ActWithStash` class from the AMK. This class behaves exactly the same like Scala `Actor` -but, additionally, provides methods that correspond to methods in Akka's `Actor` trait. This allows easy, step by step, migration to the Akka behavior. - -To achieve this all classes that extend `Actor` should extend the `ActWithStash`. Apply the -following rule: - - class MyActor extends Actor -> class MyActor extends ActWithStash - -After this change code might not compile. The `receive` method exists in `ActWithStash` and can not be used in the body of the `act` as is. To redirect the compiler to the previous method -add the type parameter to all `receive` calls in your system. For example: - - receive { case x: Int => "Number" } -> - receive[String] { case x: Int => "Number" } - -Additionally, to make the code compile, users must add the `override` keyword before the `act` method, and to create -the empty `receive` method in the code. Method `act` needs to be overridden since its implementation in `ActWithStash` -mimics the message processing loop of Akka. The changes are shown in the following example: - - class MyActor extends ActWithStash { - - // dummy receive method (not used for now) - def receive = {case _ => } - - override def act() { - // old code with methods receive changed to react. - } - } - - -`ActWithStash` instances have variable `trapExit` set to `true` by default. If that is not desired set it to `false` in the initializer of the class. - -The remote actors will not work with `ActWithStash` out of the box. The method `register('name, this)` needs to be replaced with: - - registerActorRef('name, self) - -In later steps of the migration, calls to `registerActorRef` and `alive` should be treated like any other calls. - -After this point user can run the test suite and the whole system should behave as before. The `ActWithStash` and `Actor` use the same infrastructure so the system -should behave exactly the same. - -### Step 4 - Removing the `act` Method - -In this section we describe how to remove the `act` method from `ActWithStash`s and how to -change the methods used in the `ActWithStash` to resemble Akka. Since this step can be complex, it is recommended -to do changes one actor at a time. In Scala, an actor's behavior is defined by implementing the `act` method. Logically, an actor is a concurrent process -which executes the body of its `act` method, and then terminates. In Akka, the behavior is defined by using a global message -handler which processes the messages in the actor's mailbox one by one. The message handler is a partial function, returned by the `receive` method, -which gets applied to each message. - -Since the behavior of Akka methods in the `ActWithStash` depends on the removal of the `act` method we have to do that first. Then we will give the translation -rules for translating individual methods of the `scala.actors.Actor` trait. - -#### Removal of `act` - -In the following list we present the translation rules for common message processing patterns. This list is not -exhaustive and it covers only some common patterns. However, users can migrate more complex `act` methods to Akka by looking - at existing translation rules and extending them for more complex situations. - -A note about nested `react`/`reactWithin` calls: the message handling -partial function needs to be expanded with additional constructs that -bring it closer to the Akka model. Although these changes can be -complicated, migration is possible for an arbitrary level of -nesting. See below for examples. - -A note about using `receive`/`receiveWithin` with complex control -flow: migration can be complicated since it requires refactoring the -`act` method. A `receive` call can be modeled using `react` and -`andThen` on the message processing partial function. Again, simple -examples are shown below. - -1. If there is any code in the `act` method that is being executed before the first `loop` with `react` that code -should be moved to the `preStart` method. - - def act() { - // initialization code here - loop { - react { ... } - } - } - - should be replaced with - - override def preStart() { - // initialization code here - } - - def act() { - loop { - react{ ... } - } - } - - This rule should be used in other patterns as well if there is code before the first react. - -2. When `act` is in the form of a simple `loop` with a nested `react` use the following pattern. - - def act() = { - loop { - react { - // body - } - } - } - - should be replaced with - - def receive = { - // body - } - -3. When `act` contains a `loopWhile` construct use the following translation. - - def act() = { - loopWhile(c) { - react { - case x: Int => - // do task - if (x == 42) { - c = false - } - } - } - } - - should be replaced with - - def receive = { - case x: Int => - // do task - if (x == 42) { - context.stop(self) - } - } - -4. When `act` contains nested `react`s use the following rule: - - def act() = { - var c = true - loopWhile(c) { - react { - case x: Int => - // do task - if (x == 42) { - c = false - } else { - react { - case y: String => - // do nested task - } - } - } - } - } - - should be replaced with - - def receive = { - case x: Int => - // do task - if (x == 42) { - context.stop(self) - } else { - context.become(({ - case y: String => - // do nested task - }: Receive).andThen(x => { - unstashAll() - context.unbecome() - }).orElse { case x => stash(x) }) - } - } - -5. For `reactWithin` method use the following translation rule: - - loop { - reactWithin(t) { - case TIMEOUT => // timeout processing code - case msg => // message processing code - } - } - - should be replaced with - - import scala.concurrent.duration._ - - context.setReceiveTimeout(t millisecond) - def receive = { - case ReceiveTimeout => // timeout processing code - case msg => // message processing code - } - -6. Exception handling is done in a different way in Akka. To mimic Scala actors behavior apply the following rule - - def act() = { - loop { - react { - case msg => - // work that can fail - } - } - } - - override def exceptionHandler = { - case x: Exception => println("got exception") - } - - should be replaced with - - def receive = PFCatch({ - case msg => - // work that can fail - }, { case x: Exception => println("got exception") }) - - where `PFCatch` is defined as - - class PFCatch(f: PartialFunction[Any, Unit], - handler: PartialFunction[Exception, Unit]) - extends PartialFunction[Any, Unit] { - - def apply(x: Any) = { - try { - f(x) - } catch { - case e: Exception if handler.isDefinedAt(e) => - handler(e) - } - } - - def isDefinedAt(x: Any) = f.isDefinedAt(x) - } - - object PFCatch { - def apply(f: PartialFunction[Any, Unit], - handler: PartialFunction[Exception, Unit]) = - new PFCatch(f, handler) - } - - `PFCatch` is not included in the AMK as it can stay as the permanent feature in the migrated code - and the AMK will be removed with the next major release. Once the whole migration is complete fault-handling - can also be converted to the Akka [supervision](http://doc.akka.io/docs/akka/2.1.0/general/supervision.html#What_Supervision_Means). - - - -#### Changing `Actor` Methods - -After we have removed the `act` method we should rename the methods that do not exist in Akka but have similar functionality. In the following list we present -the list of differences and their translation: - -1. `exit()`/`exit(reason)` - should be replaced with `context.stop(self)` - -2. `receiver` - should be replaced with `self` - -3. `reply(msg)` - should be replaced with `sender ! msg` - -4. `link(actor)` - In Akka, linking of actors is done partially by [supervision](http://doc.akka.io/docs/akka/2.1.0/general/supervision.html#What_Supervision_Means) -and partially by [actor monitoring](http://doc.akka.io/docs/akka/2.1.0/general/supervision.html#What_Lifecycle_Monitoring_Means). In the AMK we support -only the monitoring method so the complete Scala functionality can not be migrated. - - The difference between linking and watching is that watching actors always receive the termination notification. -However, instead of matching on the Scala `Exit` message that contains the reason of termination the Akka watching -returns the `Terminated(a: ActorRef)` message that contains only the `ActorRef`. The functionality of getting the reason - for termination is not supported by the migration. It can be done in Akka, after the Step 4, by organizing the actors in a [supervision hierarchy](http://doc.akka.io/docs/akka/2.1.0/general/supervision.html). - - If the actor that is watching does not match the `Terminated` message, and this message arrives, it will be terminated with the `DeathPactException`. -Note that this will happen even when the watched actor terminated normally. In Scala linked actors terminate, with the same termination reason, only if -one of the actors terminates abnormally. - - If the system can not be migrated solely with `watch` the user should leave invocations to `link` and `exit(reason)` as is. However since `act()` overrides the `Exit` message the following transformation -needs to be applied: - - case Exit(actor, reason) => - println("sorry about your " + reason) - ... - - should be replaced with - - case t @ Terminated(actorRef) => - println("sorry about your " + t.reason) - ... - - NOTE: There is another subtle difference between Scala and Akka actors. In Scala, `link`/`watch` to the already dead actor will not have affect. -In Akka, watching the already dead actor will result in sending the `Terminated` message. This can give unexpected behavior in the Step 5 of the migration guide. - -### Step 5 - Moving to the Akka Back-end - -At this point user code is ready to operate on Akka actors. Now we can switch the actors library from Scala to -Akka actors. To do this configure the build to exclude the `scala-actors.jar` and the `scala-actors-migration.jar`, - and to include *akka-actor.jar* and *typesafe-config.jar*. The AMK is built to work only with Akka actors version 2.1 which are included in the [Scala distribution](http://www.scala-lang.org/downloads) - and can be configured by these [instructions](http://doc.akka.io/docs/akka/2.1.0/intro/getting-started.html#Using_a_build_tool). - -After this change the compilation will fail due to different package names and slight differences in the API. We will have to change each imported actor -from scala to Akka. Following is the non-exhaustive list of package names that need to be changed: - - scala.actors._ -> akka.actor._ - scala.actors.migration.ActWithStash -> akka.actor.ActorDSL._ - scala.actors.migration.pattern.ask -> akka.pattern.ask - scala.actors.migration.Timeout -> akka.util.Timeout - -Also, method declarations `def receive =` in `ActWithStash` should be prepended with `override`. - -In Scala actors the `stash` method needs a message as a parameter. For example: - - def receive = { - ... - case x => stash(x) - } - -In Akka only the currently processed message can be stashed. Therefore replace the above example with: - - def receive = { - ... - case x => stash() - } - -#### Adding Actor Systems - -The Akka actors are organized in [Actor systems](http://doc.akka.io/docs/akka/2.1.0/general/actor-systems.html). - Each actor that is instantiated must belong to one `ActorSystem`. To achieve this add an `ActorSystem` instance to each actor instantiation call as a first argument. The following example shows the transformation. - -To achieve this transformation you need to have an actor system instantiated. The actor system is usually instantiated in Scala objects or configuration classes that are global to your system. For example: - - val system = ActorSystem("migration-system") - -Then apply the following transformation: - - ActorDSL.actor(...) -> ActorDSL.actor(system)(...) - -If many calls to `actor` use the same `ActorSystem` it can be passed as an implicit parameter. For example: - - ActorDSL.actor(...) -> - import project.implicitActorSystem - ActorDSL.actor(...) - -Finally, Scala programs are terminating when all the non-daemon threads and actors finish. With Akka the program ends when all the non-daemon threads finish and all actor systems are shut down. - Actor systems need to be explicitly terminated before the program can exit. This is achieved by invoking the `shutdown` method on an Actor system. - -#### Remote Actors - -Once the code base is moved to Akka remoting will not work any more. The methods `registerActorFor` and `alive` need to be removed. In Akka, remoting is done solely by configuration and -for further details refer to the [Akka remoting documentation](http://doc.akka.io/docs/akka/2.1.0/scala/remoting.html). - -#### Examples and Issues -All of the code snippets presented in this document can be found in the [Actors Migration test suite](http://github.com/scala/actors-migration/tree/master/src/test/) as test files with the prefix `actmig`. - -This document and the Actor Migration Kit were designed and implemented by: [Vojin Jovanovic](http://people.epfl.ch/vojin.jovanovic) and [Philipp Haller](http://lampwww.epfl.ch/~phaller/) - -If you find any issues or rough edges please report them at the [Scala Bugtracker](https://github.com/scala/actors-migration/issues). diff --git a/overviews/core/_posts/2012-12-20-macros.md b/overviews/core/_posts/2012-12-20-macros.md deleted file mode 100644 index 6768efb4cd..0000000000 --- a/overviews/core/_posts/2012-12-20-macros.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -layout: overview -title: Macros -discourse: true -partof: macros -overview: macros -languages: [ja] -label-color: important -label-text: Experimental ---- diff --git a/overviews/core/_posts/2013-01-02-reflection.md b/overviews/core/_posts/2013-01-02-reflection.md deleted file mode 100644 index fdde4a1b8a..0000000000 --- a/overviews/core/_posts/2013-01-02-reflection.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -layout: overview -title: Reflection -discourse: true -partof: reflection -overview: reflection -languages: [ja] -label-color: important -label-text: Experimental ---- diff --git a/overviews/core/_posts/2016-11-01-binary-compatibility-of-scala-releases.md b/overviews/core/_posts/2016-11-01-binary-compatibility-of-scala-releases.md deleted file mode 100644 index 2f90a74fa2..0000000000 --- a/overviews/core/_posts/2016-11-01-binary-compatibility-of-scala-releases.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -layout: overview -title: Binary Compatibility of Scala Releases -overview: binary-compatibility-of-scala-releases ---- - -When two versions of Scala are binary compatible, it is safe to compile your project on one Scala version and link against another Scala version at run time. Safe run-time linkage (only!) means that the JVM does not throw a (subclass of) [`LinkageError`](http://docs.oracle.com/javase/7/docs/api/java/lang/LinkageError.html) when executing your program in the mixed scenario, assuming that none arise when compiling and running on the same version of Scala. Concretely, this means you may have external dependencies on your run-time classpath that use a different version of Scala than the one you're compiling with, as long as they're binary compatible. In other words, separate compilation on different binary compatible versions does not introduce problems compared to compiling and running everything on the same version of Scala. - -We check binary compatibility automatically with [MiMa](https://github.com/typesafehub/migration-manager). We strive to maintain a similar invariant for the `behavior` (as opposed to just linkage) of the standard library, but this is not checked mechanically (Scala is not a proof assistant so this is out of reach for its type system). - -#### Forwards and Back -We distinguish forwards and backwards compatibility (think of these as properties of a sequence of versions, not of an individual version). Maintaining backwards compatibility means code compiled on an older version will link with code compiled with newer ones. Forwards compatibility allows you to compile on new versions and run on older ones. - -Thus, backwards compatibility precludes the removal of (non-private) methods, as older versions could call them, not knowing they would be removed, whereas forwards compatibility disallows adding new (non-private) methods, because newer programs may come to depend on them, which would prevent them from running on older versions (private methods are exempted here as well, as their definition and call sites must be in the same compilation unit). - -These are strict constraints, but they have worked well for us since Scala 2.10.x. They didn't stop us from fixing large numbers of issues in minor releases. The advantages are clear, so we will maintain this policy for future Scala major releases. - -#### Meta -Note that so far we've only talked about the jars generated by scalac for the standard library and reflection. -Our policies do not extend to the meta-issue: ensuring binary compatibility for bytecode generated from identical sources, by different version of scalac? (The same problem exists for compiling on different JDKs.) While we strive to achieve this, it's not something we can test in general. Notable examples where we know meta-binary compatibility is hard to achieve: specialisation and the optimizer. - -In short, we recommend that library authors use to [MiMa](https://github.com/typesafehub/migration-manager) to verify compatibility of minor versions before releasing. -Compiling identical sources with different versions of the scala compiler (or on different JVM versions!) could result in binary incompatible bytecode. This is rare, and we try to avoid it, but we can't guarantee it will never happen. - -#### Concretely -We guarantee forwards and backwards compatibility of the `"org.scala-lang" % "scala-library" % "2.N.x"` and `"org.scala-lang" % "scala-reflect" % "2.N.x"` artifacts, except for anything under the `scala.reflect.internal` package, as scala-reflect is still experimental. We also strongly discourage relying on the stability of `scala.concurrent.impl` and `scala.reflect.runtime`, though we will only break compatibility for severe bugs here. - -Note that we will only enforce *backwards* binary compatibility for modules (artifacts under the groupId `org.scala-lang.modules`). As they are opt-in, it's less of a burden to require having the latest version on the classpath. (Without forward compatibility, the latest version of the artifact must be on the run-time classpath to avoid linkage errors.) - -Finally, since Scala 2.11, `scala-library-all` aggregates all modules that constitute a Scala release. Note that this means it does not provide forward binary compatibility, whereas the core `scala-library` artifact does. We consider the versions of the modules that `"scala-library-all" % "2.N.x"` depends on to be the canonical ones, that are part of the official Scala distribution. (The distribution itself is defined by the `scala-dist` maven artifact.) diff --git a/overviews/index.md b/overviews/index.md deleted file mode 100644 index 30b609c217..0000000000 --- a/overviews/index.md +++ /dev/null @@ -1,84 +0,0 @@ ---- -layout: guides-index -title: Guides and Overviews -languages: [ja, zh-cn, es] ---- - -
        -

        Core The essentials...

        -
        - * Scala's Collections Library - * [Introduction](/overviews/collections/introduction.html) - * [Mutable and Immutable Collections](/overviews/collections/overview.html) - * [Trait Traversable](/overviews/collections/trait-traversable.html) - * [Trait Iterable](/overviews/collections/trait-iterable.html) - * [The sequence traits Seq, IndexedSeq, and LinearSeq](/overviews/collections/seqs.html) - * [Sets](/overviews/collections/sets.html) - * [Maps](/overviews/collections/maps.html) - * [Concrete Immutable Collection Classes](/overviews/collections/concrete-immutable-collection-classes.html) - * [Concrete Mutable Collection Classes](/overviews/collections/concrete-mutable-collection-classes.html) - * [Arrays](/overviews/collections/arrays.html) - * [Strings](/overviews/collections/strings.html) - * [Performance Characteristics](/overviews/collections/performance-characteristics.html) - * [Equality](/overviews/collections/equality.html) - * [Views](/overviews/collections/views.html) - * [Iterators](/overviews/collections/iterators.html) - * [Creating Collections From Scratch](/overviews/collections/creating-collections-from-scratch.html) - * [Conversions Between Java and Scala Collections](/overviews/collections/conversions-between-java-and-scala-collections.html) - * [Migrating from Scala 2.7](/overviews/collections/migrating-from-scala-27.html) - * [The Architecture of Scala Collections](/overviews/core/architecture-of-scala-collections.html) - * [String Interpolation](/overviews/core/string-interpolation.html) New in 2.10 - * [Implicit Classes](/overviews/core/implicit-classes.html) New in 2.10 - * [Value Classes and Universal Traits](/overviews/core/value-classes.html) New in 2.10 - * [Binary Compatibility of Scala Releases](/overviews/core/binary-compatibility-of-scala-releases.html) - -
        -

        Reference / Documentation

        -
        - * Scaladoc - * [Overview](/overviews/scaladoc/overview.html) - * [Using Scaladoc Effectively](/overviews/scaladoc/interface.html) - * [Authoring Scaladoc](/overviews/scaladoc/for-library-authors.html) - * Scala REPL - * [Overview](/overviews/repl/overview.html) - -
        -

        Parallel and Concurrent Programming

        -
        - * [Futures and Promises](/overviews/core/futures.html) New in 2.10 - * Scala's Parallel Collections Library - * [Overview](/overviews/parallel-collections/overview.html) - * [Concrete Parallel Collection Classes](/overviews/parallel-collections/concrete-parallel-collections.html) - * [Parallel Collection Conversions](/overviews/parallel-collections/conversions.html) - * [Concurrent Tries](/overviews/parallel-collections/ctries.html) - * [Architecture of the Parallel Collections Library](/overviews/parallel-collections/architecture.html) - * [Creating Custom Parallel Collections](/overviews/parallel-collections/custom-parallel-collections.html) - * [Configuring Parallel Collections](/overviews/parallel-collections/configuration.html) - * [Measuring Performance](/overviews/parallel-collections/performance.html) - * [The Scala Actors Migration Guide](/overviews/core/actors-migration-guide.html) New in 2.10 - * [The Scala Actors API](/overviews/core/actors.html) Deprecated - -
        -

        Metaprogramming

        -
        - * Reflection Experimental - * [Overview](/overviews/reflection/overview.html) - * [Environment, Universes, and Mirrors](/overviews/reflection/environment-universes-mirrors.html) - * [Symbols, Trees, and Types](/overviews/reflection/symbols-trees-types.html) - * [Annotations, Names, Scopes, and More](/overviews/reflection/annotations-names-scopes.html) - * [TypeTags and Manifests](/overviews/reflection/typetags-manifests.html) - * [Thread Safety](/overviews/reflection/thread-safety.html) - * [Changes in Scala 2.11](/overviews/reflection/changelog211.html) - * Macros Experimental - * [Use Cases](/overviews/macros/usecases.html) - * [Blackbox Vs Whitebox](/overviews/macros/blackbox-whitebox.html) - * [Def Macros](/overviews/macros/overview.html) - * [Quasiquotes](/overviews/quasiquotes/intro.html) - * [Macro Bundles](/overviews/macros/bundles.html) - * [Implicit Macros](/overviews/macros/implicits.html) - * [Extractor Macros](/overviews/macros/extractors.html) - * [Type Providers](/overviews/macros/typeproviders.html) - * [Macro Annotations](/overviews/macros/annotations.html) - * [Macro Paradise](/overviews/macros/paradise.html) - * [Roadmap](/overviews/macros/roadmap.html) - * [Changes in 2.11](/overviews/macros/changelog211.html) diff --git a/overviews/macros/annotations.md b/overviews/macros/annotations.md deleted file mode 100644 index 13acf48233..0000000000 --- a/overviews/macros/annotations.md +++ /dev/null @@ -1,110 +0,0 @@ ---- -layout: overview-large -title: Macro Annotations - -discourse: true - -partof: macros -num: 10 -outof: 13 -languages: [ja] ---- -MACRO PARADISE - -**Eugene Burmako** - -Macro annotations are only available with the macro paradise plugin (in Scala 2.10.x, 2.11.x and 2.12.x alike). -Their inclusion in official Scala might happen in Scala 2.13, but there is no certainty about it yet. -Follow the instructions at the ["Macro Paradise"](/overviews/macros/paradise.html) page to download and use our compiler plugin. - -Note that macro paradise is needed both to compile and to expand macro annotations, -which means that your users will have to also add macro paradise to their builds in order to use your macro annotations. -However, after macro annotations expand, the resulting code will no longer have any references to macro paradise -and won't require its presence at compile-time or at runtime. - -## Walkthrough - -Macro annotations bring textual abstraction to the level of definitions. Annotating any top-level or nested definition with something -that Scala recognizes as a macro will let it expand, possibly into multiple members. Unlike in the previous versions of macro paradise, -macro annotations in 2.0 are done right in the sense that they: 1) apply not just to classes and objects, but to arbitrary definitions, -2) allow expansions of classes to modify or even create companion objects. -This opens a number of new possibilities in code generation land. - -In this walkthrough we will write a silly, but very useful macro that does nothing except for logging the annottees. -As a first step, we define an annotation that inherits `StaticAnnotation` and defines a `macroTransform` macro -(the name `macroTransform` and the signature `annottees: Any*` of that macro are important as they tell the macro engine -that the enclosing annotation is a macro annotation). - - import scala.reflect.macros.Context - import scala.language.experimental.macros - import scala.annotation.StaticAnnotation - import scala.annotation.compileTimeOnly - - @compileTimeOnly("enable macro paradise to expand macro annotations") - class identity extends StaticAnnotation { - def macroTransform(annottees: Any*): Any = macro ??? - } - -First of all, note the `@compileTimeOnly` annotation. It is not mandatory, but is recommended to avoid confusion. -Macro annotations look like normal annotations to the vanilla Scala compiler, so if you forget to enable the macro paradise -plugin in your build, your annotations will silently fail to expand. The `@compileTimeOnly` annotation makes sure that -no reference to the underlying definition is present in the program code after typer, so it will prevent the aforementioned -situation from happening. - -Now, the `macroTransform` macro is supposed to take a list of untyped annottees (in the signature their type is represented as `Any` -for the lack of better notion in Scala) and produce one or several results (a single result can be returned as is, multiple -results have to be wrapped in a `Block` for the lack of better notion in the reflection API). - -At this point you might be wondering. A single annottee and a single result is understandable, but what is the many-to-many -mapping supposed to mean? There are several rules guiding the process: - -1. If a class is annotated and it has a companion, then both are passed into the macro. (But not vice versa - if an object - is annotated and it has a companion class, only the object itself is expanded). -1. If a parameter of a class, method or type member is annotated, then it expands its owner. First comes the annottee, - then the owner and then its companion as specified by the previous rule. -1. Annottees can expand into whatever number of trees of any flavor, and the compiler will then transparently - replace the input trees of the macro with its output trees. -1. If a class expands into both a class and a module having the same name, they become companions. - This way it is possible to generate a companion object for a class even if that companion was not declared explicitly. -1. Top-level expansions must retain the number of annottees, their flavors and their names, with the only exception - that a class might expand into a same-named class plus a same-named module, in which case they automatically become - companions as per previous rule. - -Here's a possible implementation of the `identity` annotation macro. The logic is a bit complicated, because it needs to -take into account the cases when `@identity` is applied to a value or type parameter. Excuse us for a low-tech solution, -but we haven't encapsulated this boilerplate in a helper, because compiler plugins cannot easily change the standard library. -(By the way, this boilerplate can be abstracted away by a suitable annotation macro, and we'll probably provide such a macro -at a later point in the future). - - object identityMacro { - def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = { - import c.universe._ - val inputs = annottees.map(_.tree).toList - val (annottee, expandees) = inputs match { - case (param: ValDef) :: (rest @ (_ :: _)) => (param, rest) - case (param: TypeDef) :: (rest @ (_ :: _)) => (param, rest) - case _ => (EmptyTree, inputs) - } - println((annottee, expandees)) - val outputs = expandees - c.Expr[Any](Block(outputs, Literal(Constant(())))) - } - } - -| Example code | Printout | -|-----------------------------------------------------------|-----------------------------------------------------------------| -| `@identity class C` | `(, List(class C))` | -| `@identity class D; object D` | `(, List(class D, object D))` | -| `class E; @identity object E` | `(, List(object E))` | -| `def twice[@identity T]`
        `(@identity x: Int) = x * 2` | `(type T, List(def twice))`
        `(val x: Int, List(def twice))` | - -In the spirit of Scala macros, macro annotations are as untyped as possible to stay flexible and -as typed as possible to remain useful. On the one hand, macro annottees are untyped, so that we can change their signatures (e.g. lists -of class members). But on the other hand, the thing about all flavors of Scala macros is integration with the typechecker, and -macro annotations are not an exceptions. During expansion we can have all the type information that's possible to have -(e.g. we can reflect against the surrounding program or perform type checks / implicit lookups in the enclosing context). - -## Blackbox vs whitebox - -Macro annotations must be [whitebox](/overviews/macros/blackbox-whitebox.html). -If you declare a macro annotation as [blackbox](/overviews/macros/blackbox-whitebox.html), it will not work. diff --git a/overviews/macros/blackbox-whitebox.md b/overviews/macros/blackbox-whitebox.md deleted file mode 100644 index 34f79c9fb5..0000000000 --- a/overviews/macros/blackbox-whitebox.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -layout: overview-large -title: Blackbox Vs Whitebox - -discourse: true - -partof: macros -num: 2 -outof: 13 ---- -EXPERIMENTAL - -**Eugene Burmako** - -Separation of macros into blackbox ones and whitebox ones is a feature of Scala 2.11.x and Scala 2.12.x. The blackbox/whitebox separation is not supported in Scala 2.10.x. It is also not supported in macro paradise for Scala 2.10.x. - -## Why macros work? - -With macros becoming a part of the official Scala 2.10 release, programmers in research and industry have found creative ways of using macros to address all sorts of problems, far extending our original expectations. - -In fact, macros became an important part of our ecosystem so quickly that just a couple months after the release of Scala 2.10, when macros were introduced in experimental capacity, we had a Scala language team meeting and decided to standardize macros and make them a full-fledged feature of Scala by 2.12. - -UPDATE It turned out that it was not that simple to stabilize macros by Scala 2.12. Our research into that has resulted in establishing a new metaprogramming foundation for Scala, called [scala.meta](http://scalameta.org), whose first beta is expected to be released simultaneously with Scala 2.12 and might later be included in future versions of Scala. In the meanwhile, Scala 2.12 is not going to see any changes to reflection and macros - everything is going to stay experimental as it was in Scala 2.10 and Scala 2.11, and no features are going to be removed. However, even though circumstances under which this document has been written have changed, the information still remains relevant, so please continue reading. - -Macro flavors are plentiful, so we decided to carefully examine them to figure out which ones should be put in the standard. This entails answering a few important questions. Why are macros working so well? Why do people use them? - -Our hypothesis is that this happens because the hard to comprehend notion of metaprogramming expressed in def macros piggybacks on the familiar concept of a typed method call. Thanks to that, the code that users write can absorb more meaning without becoming bloated or losing -compehensibility. - -## Blackbox and whitebox macros - -However sometimes def macros transcend the notion of "just a regular method". For example, it is possible for a macro expansion to yield an expression of a type that is more specific than the return type of a macro. In Scala 2.10, such expansion will retain its precise type as highlighted in the ["Static return type of Scala macros"](http://stackoverflow.com/questions/13669974/static-return-type-of-scala-macros) article at Stack Overflow. - -This curious feature provides additional flexibility, enabling [fake type providers](http://meta.plasm.us/posts/2013/07/11/fake-type-providers-part-2/), [extended vanilla materialization](/sips/pending/source-locations.html), [fundep materialization](/overviews/macros/implicits.html#fundep_materialization) and [extractor macros](https://github.com/scala/scala/commit/84a335916556cb0fe939d1c51f27d80d9cf980dc), but it also sacrifices clarity - both for humans and for machines. - -To concretize the crucial distinction between macros that behave just like normal methods and macros that refine their return types, we introduce the notions of blackbox macros and whitebox macros. Macros that faithfully follow their type signatures are called **blackbox macros** as their implementations are irrelevant to understanding their behaviour (could be treated as black boxes). Macros that can't have precise signatures in Scala's type system are called **whitebox macros** (whitebox def macros do have signatures, but these signatures are only approximations). - -We recognize the importance of both blackbox and whitebox macros, however we feel more confidence in blackbox macros, because they are easier to explain, specify and support. Therefore our plans to standardize macros only include blackbox macros. Later on, we might also include whitebox macros into our plans, but it's too early to tell. - -## Codifying the distinction - -In the 2.11 release, we take first step of standardization by expressing the distinction between blackbox and whitebox macros in signatures of def macros, so that `scalac` can treat such macros differently. This is just a preparatory step, so both blackbox and whitebox macros remain experimental in Scala 2.11. - -We express the distinction by replacing `scala.reflect.macros.Context` with `scala.reflect.macros.blackbox.Context` and `scala.reflect.macros.whitebox.Context`. If a macro impl is defined with `blackbox.Context` as its first argument, then macro defs that are using it are considered blackbox, and analogously for `whitebox.Context`. Of course, the vanilla `Context` is still there for compatibility reasons, but it issues a deprecation warning encouraging to choose between blackbox and whitebox macros. - -Blackbox def macros are treated differently from def macros of Scala 2.10. The following restrictions are applied to them by the Scala typechecker: - -1. When an application of a blackbox macro expands into tree `x`, the expansion is wrapped into a type ascription `(x: T)`, where `T` is the declared return type of the blackbox macro with type arguments and path dependencies applied in consistency with the particular macro application being expanded. This invalidates blackbox macros as an implementation vehicle of [type providers](http://meta.plasm.us/posts/2013/07/11/fake-type-providers-part-2/). -1. When an application of a blackbox macro still has undetermined type parameters after Scala's type inference algorithm has finished working, these type parameters are inferred forcedly, in exactly the same manner as type inference happens for normal methods. This makes it impossible for blackbox macros to influence type inference, prohibiting [fundep materialization](/overviews/macros/implicits.html#fundep_materialization). -1. When an application of a blackbox macro is used as an implicit candidate, no expansion is performed until the macro is selected as the result of the implicit search. This makes it impossible to [dynamically calculate availability of implicit macros](/sips/rejected/source-locations.html). -1. When an application of a blackbox macro is used as an extractor in a pattern match, it triggers an unconditional compiler error, preventing [customizations of pattern matching](https://github.com/paulp/scala/commit/84a335916556cb0fe939d1c51f27d80d9cf980dc) implemented with macros. - -Whitebox def macros work exactly like def macros used to work in Scala 2.10. No restrictions of any kind get applied, so everything that could be done with macros in 2.10 should be possible in 2.11 and 2.12. diff --git a/overviews/macros/bundles.md b/overviews/macros/bundles.md deleted file mode 100644 index d2abe2227e..0000000000 --- a/overviews/macros/bundles.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -layout: overview-large -title: Macro Bundles - -discourse: true - -partof: macros -num: 5 -outof: 13 -languages: [ja] ---- -EXPERIMENTAL - -**Eugene Burmako** - -Macro bundles are a feature of Scala 2.11.x and Scala 2.12.x. Macro bundles are not supported in Scala 2.10.x. They are also not supported in macro paradise for Scala 2.10.x. - -## Macro bundles - -In Scala 2.10.x, macro implementations are represented with functions. Once the compiler sees an application of a macro definition, -it calls the macro implementation - as simple as that. However practice shows that just functions are often not enough due to the -following reasons: - -1. Being limited to functions makes modularizing complex macros awkward. It's quite typical to see macro logic concentrate in helper -traits outside macro implementations, turning implementations into trivial wrappers, which just instantiate and call helpers. - -2. Moreover, since macro parameters are path-dependent on the macro context, [special incantations](/overviews/macros/overview.html#writing_bigger_macros) are required to wire implementations and helpers together. - -Macro bundles provide a solution to these problems by allowing macro implementations to be declared in classes that take -`c: scala.reflect.macros.blackbox.Context` or `c: scala.reflect.macros.whitebox.Context` as their constructor parameters, relieving macro implementations from having -to declare the context in their signatures, which simplifies modularization. Referencing macro implementations defined in bundles -works in the same way as with impls defined in objects. You specify a bundle name and then select a method from it, -providing type arguments if necessary. - - import scala.reflect.macros.blackbox.Context - - class Impl(val c: Context) { - def mono = c.literalUnit - def poly[T: c.WeakTypeTag] = c.literal(c.weakTypeOf[T].toString) - } - - object Macros { - def mono = macro Impl.mono - def poly[T] = macro Impl.poly[T] - } - -## Blackbox vs whitebox - -Macro bundles can be used to implement both [blackbox](/overviews/macros/blackbox-whitebox.html) and [whitebox](/overviews/macros/blackbox-whitebox.html) macros. Give the macro bundle constructor parameter the type of `scala.reflect.macros.blackbox.Context` to define a blackbox macro and the type of `scala.reflect.macros.whitebox.Context` to define a whitebox macro. diff --git a/overviews/macros/changelog211.md b/overviews/macros/changelog211.md deleted file mode 100644 index 75196015f2..0000000000 --- a/overviews/macros/changelog211.md +++ /dev/null @@ -1,185 +0,0 @@ ---- -layout: overview-large -title: Changes in Scala 2.11 - -discourse: true - -partof: macros -num: 13 -outof: 13 ---- - -EXPERIMENTAL - -**Eugene Burmako** - -This document lists all major changes to reflection and macros during the development cycle of Scala 2.11.0. First, we provide summaries of the most important fixes and newly introduced features, and then, later in the document, we explain how these changes are going to affect compatibility with Scala 2.10.x, and how it's possible to make your reflection-based code work in both 2.10.x and 2.11.0. - -### Quasiquotes - -Quasiquotes is the single most impressive upgrade for reflection and macros in Scala 2.11.0. Implemented by Denys Shabalin, they have significantly simplified the life of Scala metaprogrammers around the globe. Visit [the dedicated documentation page](/overviews/quasiquotes/intro.html) to learn more about quasiquotes. - -### New macro powers - -1) **[Fundep materialization](http://docs.scala-lang.org/overviews/macros/implicits.html#fundep_materialization)**. Since Scala 2.10.2, implicit whitebox macros can be used to materialize instances of type classes, however such materialized instances can't guide type inference. In Scala 2.11.0, materializers can also affect type inference, helping scalac to infer type arguments for enclosing method applications, something that's used with great success in Shapeless. Even more, with the fix of [SI-3346](https://issues.scala-lang.org/browse/SI-3346), this inference guiding capability can affect both normal methods and implicit conversions alike. Please note, however, that fundep materialization doesn't let one change how Scala's type inference works, but merely provides a way to throw more type constraints into the mix, so it's, for example, impossible to make type inference flow from right to left using fundep materializers. - -2) **[Extractor macros](https://github.com/paulp/scala/commit/84a335916556cb0fe939d1c51f27d80d9cf980dc)**. A prominent new feature in Scala 2.11.0 is [name-based extractors](https://github.com/scala/scala/pull/2848) implemented by Paul Phillips. And as usual, when there's a Scala feature, it's very likely that macros can make use of it. Indeed, with the help of structural types, whitebox macros can be used to write extractors than refine the types of extractees on case-by-case basis. This is the technique that we use internally to implement quasiquotes. - -3) **[Named and default arguments in macros](https://github.com/scala/scala/pull/3543)**. This is something that strictly speaking shouldn't belong to this changelog, because this feature was reverted shortly after being merged into Scala 2.11.0-RC1 due to a tiny mistake that led to a regression, but we've got a patch that makes the macro engine understand named/default arguments in macro applications. Even though the code freeze won't let us bring this change in Scala 2.11.0, we expect to merge it in Scala 2.11.1 at an earliest opportunity. - -4) **[Type macros](http://docs.scala-lang.org/overviews/macros/typemacros.html) and [macro annotations](http://docs.scala-lang.org/overviews/macros/annotations.html)**. Neither type macros, not macro annotations are included of Scala 2.11.0. It is highly unlikely that type macros will ever be included in Scala, but we still deliberate on macro annotations. However, macro annotations are available both for Scala 2.10.x and for Scala 2.11.0 via the [macro paradise plugin](http://docs.scala-lang.org/overviews/macros/annotations.html). - -5) **@compileTimeOnly**. Standard library now features a new `scala.annotations.compileTimeOnly` annotation that tells scalac that its annottees should not be referred to after type checking (which includes macro expansion). The main use case for this annotation is marking helper methods that are only supposed be used only together with an enclosing macro call to indicate parts of arguments of that macro call that need special treatment (e.g. `await` in scala/async or `value` in sbt's new macro-based DSL). For example, scala/async's `await` marked as `@compileTimeOnly` only makes sense inside an `async { ... }` block that compiles it away during its transformation, and using it outside of `async` is a compile-time error thanks to the new annotation. - -### Changes to the macro engine - -6) **[Blackbox/whitebox separation](http://docs.scala-lang.org/overviews/macros/blackbox-whitebox.html)**. Macros whose macro implementations use `scala.reflect.macros.blackbox.Context` (new in Scala 2.11.0) are called blackbox, have reduced power in comparison to macros in 2.10.x, better support in IDEs and better perspectives in becoming part of Scala. Macros whose macro implementations use `scala.reflect.macros.whitebox.Context` (new in Scala 2.11.0) or `scala.reflect.macros.Context` (the only context in Scala 2.10.x, deprecated in Scala 2.11.0) are called whitebox and have at least the same power as macros in 2.10.x. - -7) **[Macro bundles](http://docs.scala-lang.org/overviews/macros/bundles.html)**. It is well-known that path-dependent nature of the current reflection API (that's there in both Scala 2.10.x and Scala 2.11.0) makes it difficult to modularize macros. There are [design patterns](http://docs.scala-lang.org/overviews/macros/overview.html#writing_bigger_macros) that help to overcome this difficulty, but that just leads to proliferation of boilerplate. One of the approaches to dealing with this problem is doing away with cakes altogether, and that's what we're pursing in Project Palladium, but that was too big of a change to pull off in Scala 2.11.0, so we've come up with a workaround that would alleviate the problem until the real solution arrives. Macro bundles are classes that have a single public field of type `Context` and any public method inside a bundle can be referred to as a macro implementation. Such macro implementations can then easily call into other methods of the same class or its superclasses without having to carry the context around, because the bundle already carries the context that everyone inside it can see and refer to. This significantly simplifies writing and maintaining complex macros. - -8) **Relaxed requirements for signatures of macro implementations**. With the advent of quasiquotes, reify is quickly growing out of favor as being too clunky and inflexible. To recognize that we now allow both arguments and return types of macro implementations to be of type `c.Tree` rather than `c.Expr[Something]`. There's no longer a need to write huge type signatures and then spend time and lines of code trying to align your macro implementations with those types. Just take trees in and return trees back - the boilerplate is gone. - -9) **Inference of macro def return types is being phased out**. Given the new scheme of things, where macro implementations can return `c.Tree` instead of `c.Expr[Something]`, it's no longer possible to robustly infer return types of macro defs from return types of macro impls (if a macro impl returns `c.Tree`, what's going to be the type of that tree then?). Therefore, we're phasing out this language mechanism. Macro impls that return `c.Expr[T]` can still be used to infer return types of their macro defs, but that will produce a deprecation warning, whereas trying to use macro impls that return `c.Tree` to infer the return type of a macro def will lead to a compilation error. - -10) **[Changes to how macro expansions typecheck](https://github.com/scala/scala/pull/3495)**. In Scala 2.10.x, macro expansions were typechecked twice: first against the return type of the corresponding macro def (so called innerPt) and second against expected type derived from the enclosing program (so called outerPt). This led to certain rare issues, when the return type misguided type inference and macro expansions ended up having imprecise types. In Scala 2.11.0, the typechecking scheme is changed. Blackbox macros are still typechecked against innerPt and then outerPt, but whitebox macros are first typed without any expected type (i.e. against WildcardType), and only then against innerPt and outerPt. - -11) **Duplication of everything that comes in and goes out**. Unfortunately, data structures central to the reflection API (trees, symbols, types) are either mutable themselves or are transitively mutable. This makes the APIs brittle as it's easy to inadvertently change someone's state in ways that are going to be incompatible with its future clients. We don't have a complete solution for that yet, but we've applied a number of safeguards to our macro engine to somewhat contain the potential for mutation. In particular, we now duplicate all the arguments and return values of macro implementations, as well as all the ins and outs of possibly mutating APIs such as `Context.typeCheck`. - -### Changes to the reflection API - -12) **[Introduction of Universe.internal and Context.internal](https://github.com/xeno-by/scala/commit/114c99691674873393223a11a9aa9168c3f41d77)**. Feedback from the users of the Scala 2.10.x reflection API has given us two important insights. First, certain functionality that we exposed was too low-level and were very out of place in the public API. Second, certain low-level functionality was very important in getting important macros operational. In order to somewhat resolve the tension created by these two development vectors, we've created internal subsections of the public APIs that are: a) clearly demarcated from the blessed parts of the reflection API, b) available to those who know what they are doing and want to implement practically important use cases that we want to support. Follow migration and compatibility notes in the bottom of the document to learn more. - -13) **[Thread safety for runtime reflection](http://docs.scala-lang.org/overviews/reflection/thread-safety.html)**. The most pressing problem in reflection for Scala 2.10.x was its thread unsafety. Attempts to use runtime reflection (e.g. type tags) from multiple threads resulted in weird crashes documented above. We believe to have fixed this problem in Scala 2.11.0-RC1 by introducing a number of locks in critical places of our implementation. On the one hand, the strategy we are using at the moment is sub-optimal in the sense that certain frequently used operations (such as `Symbol.typeSignature` or `TypeSymbol.typeParams`) are hidden behind a global lock, but we plan to optimize that in the future. On the other hand, most of the typical APIs (e.g. `Type.members` or `Type.<:<`) either use thread-local state or don't require synchronization at all, so it's definitely worth a try. - -14) **[Type.typeArgs](https://github.com/xeno-by/scala/commit/0f4e95574081bd9a945fb5b32d157a32af840cd3)**. It is now dead simple to obtain type arguments of a given type. What required a pattern match in Scala 2.10.x is now a simple method invocation. The `typeArgs` method is also joined by `typeParams`, `paramLists`, and `resultType`, making it very easy to perform common type inspection tasks. - -15) **[symbolOf[T]](https://issues.scala-lang.org/browse/SI-8194)**. Scala 2.11.0 introduces a shortcut for a very common `typeOf[T].typeSymbol` operation, making it easier to figure out metadata (annotations, flags, visibility, etc) of given classes and objects. - -16) **[knownDirectSubclasses is deemed to be officially broken](https://issues.scala-lang.org/browse/SI-7046)**. A lot of users who tried to traverse sealed hierarchies of classes have noticed that `ClassSymbol.knownDirectSubclasses` only works if invocations of their macros come after the definitions of those hierarchies in Scala's compilation order. For instance, if a sealed hierarchy is defined in the bottom of a source file, and a macro application is written in the top of the file, then knownDirectSubclasses will return an empty list. This is an issue that is deeply rooted in Scala's internal architecture, and we can't provide a fix for it in the near future. - -17) **showCode**. Along with `Tree.toString` that prints Scala-ish source code and `showRaw(tree)` that prints internal structures of trees, we now have `showCode` that prints compileable Scala source code corresponding to the provided tree, courtesy of Vladimir Nikolaev, who's done an amazing work of bringing this to life. We plan to eventually replace `Tree.toString` with `showCode`, but in Scala 2.11.0 these are two different methods. - -18) **[It is now possible to typecheck in type and pattern modes](https://issues.scala-lang.org/browse/SI-6814)**. A very convenient `Context.typeCheck` and `ToolBox.typeCheck` functionality of Scala 2.10.x had a significant inconvenience - it only worked for expressions, and typechecking something as a type or as a pattern required building dummy expressions. Now `typeCheck` has the mode parameter that take case of that difficulty. - -19) **[Reflective invocations now support value classes](https://github.com/scala/scala/pull/3409)**. Runtime reflection now correctly deals with value classes in parameters of methods and constructors and also correctly unboxes and boxes inputs and outputs to reflective invocations such as `FieldMirror.get`, `FieldMirror.set` and `MethodMirror.apply`. - -20) **[Reflective invocations have become faster](https://github.com/scala/scala/pull/1821)**. With the help of the newly introduced `FieldMirror.bind` and `MethodMirror.bind` APIs, it is now possible to quickly create new mirrors from pre-existing ones, avoiding the necessity to undergo costly mirror initialization. In our tests, invocation-heavy scenarios exhibit up to 20x performance boosts thanks to these new APIs. - -21) **Context.introduceTopLevel**. The `Context.introduceTopLevel` API, which used to be available in early milestone builds of Scala 2.11.0 as a stepping stone towards type macros, was removed from the final release, because type macros were rejected for including in Scala and discontinued in macro paradise. - -### How to make your 2.10.x macros work in 2.11.0 - -22) **Blackbox/whitebox**. All macros in Scala 2.10.x are whitebox and will behave accordingly, being able to refine the advertised return type of their macro defs in their expansions. See the subsequent section of the document for information on how to make macros in Scala 2.10.x behave exactly like blackbox macros in Scala 2.11.0. - -23) **Macro bundles**. Scala 2.11.0 now recognizes certain new shapes of references to macro implementations in right-hand sides of macro defs, and in some very rare situations this might change how existing code is compiled. First of all, no runtime behavior is going to be affected in this case - if a Scala 2.10.x macro def compiles in Scala 2.11.0, then it's going to bind to the same macro impl as before. Secondly, in some cases macro impl references might become ambiguous and fail compilation, but that should be fixable in a backward compatible fashion by simple renaming suggested by the error message. - -24) **Inference of macro def return types**. In Scala 2.11.0, macro defs, whose return types are inferred from associated macro impls, will work consistently with Scala 2.10.x. A deprecation warning will be emitted for such macro defs, but no compilation errors or behavior discrepancies are going to happen. - -25) **Changes to how macro expansions typecheck**. Scala 2.11.0 changes the sequence of expected types used to typecheck whitebox macro expansions (and since all macros in Scala 2.10.x are whitebox, they all can potentially be affected). In rare situations, when a Scala 2.10.x macro expansion relied on a specific shape of an expected type to get its type arguments inferred, it might stop working. In such cases, specifying such type arguments explicitly will fix the problem in a way compatible with both Scala 2.10.x and Scala 2.11.0: [example](https://github.com/milessabin/shapeless/commit/7b192b8d89b3654e58d7bac89427ebbcc5d5adf1#diff-c6aebd4b374deb66f0d5cdeff8484338L69). - -26) **Duplication of everything that comes in and goes out**. In Scala 2.11.0, we consistently duplicate trees that cross boundaries between userland (macro implementation code) and kernel (compiler internals), limiting the scope of mutations of those trees. In extremely rare cases, Scala 2.10.x macros might be relying on such mutations to operate correctly. Such macros will be broken and will have to be rewritten. Don't worry much about this though, because we haven't yet encountered such macros in the wild, so it's most likely that your macros are going to be fine. - -27) **Introduction of Universe.internal and Context.internal**. The following 51 APIs available in Scala 2.10.x have been moved into the `internal` submodule of the reflection cake. There are two ways of fixing these source incompatibilities. The easy one is writing `import compat._` after `import scala.reflect.runtime.universe._` or `import c.universe._`. The hard one is the easy one + applying all migration suggestions provided by deprecating warnings on methods imported from compat. - -| | | | -| ------------------------- | ------------------------------ | ------------------------- | -| typeTagToManifest | Tree.pos_= | Symbol.isSkolem | -| manifestToTypeTag | Tree.setPos | Symbol.deSkolemize | -| newScopeWith | Tree.tpe_= | Symbol.attachments | -| BuildApi.setTypeSignature | Tree.setType | Symbol.updateAttachment | -| BuildApi.flagsFromBits | Tree.defineType | Symbol.removeAttachment | -| BuildApi.emptyValDef | Tree.symbol_= | Symbol.setTypeSignature | -| BuildApi.This | Tree.setSymbol | Symbol.setAnnotations | -| BuildApi.Select | TypeTree.setOriginal | Symbol.setName | -| BuildApi.Ident | Symbol.isFreeTerm | Symbol.setPrivateWithin | -| BuildApi.TypeTree | Symbol.asFreeTerm | captureVariable | -| Tree.freeTerms | Symbol.isFreeType | referenceCapturedVariable | -| Tree.freeTypes | Symbol.asFreeType | capturedVariableType | -| Tree.substituteSymbols | Symbol.newTermSymbol | singleType | -| Tree.substituteTypes | Symbol.newModuleAndClassSymbol | refinedType | -| Tree.substituteThis | Symbol.newMethodSymbol | typeRef | -| Tree.attachments | Symbol.newTypeSymbol | intersectionType | -| Tree.updateAttachment | Symbol.newClassSymbol | polyType | -| Tree.removeAttachment | Symbol.isErroneous | existentialAbstraction | - -28) **Official brokenness of knownDirectSubclasses**. There's nothing that can be done here from your side apart from being aware of limitations of that API. Macros that use `knownDirectSubclasses` will continue to work in Scala 2.11.0 exactly like they did in Scala 2.10.x, without any deprecation warnings. - -29) **Deprecation of Context.enclosingTree-style APIs**. Existing enclosing tree macro APIs face both technical and philosophical problems, so we've made a hard decision to phase them out, deprecating them in Scala 2.11.0 and removing them in Scala 2.12.0. There's no direct replacement for these APIs, just the newly introduced c.internal.enclosingOwner that only covers a subset of their functionality. Follow the discussion at [https://github.com/scala/scala/pull/3354](https://github.com/scala/scala/pull/3354) for more information. - -30) **Other deprecations**. Some of you have -Xfatal-warnings turned on in your builds, so any deprecation might fail compilation. This guide has covered all controversial deprecations, and the rest can be fixed by straightforwardly following deprecation messages. - -31) **Removal of resetAllAttrs**. resetAllAttrs is a very dangerous API and shouldn't have been exposed in the first place. That's why we have removed it without going through a deprecation cycle. There is however a publicly available replacement called `resetLocalAttrs` that should be sufficient in almost all cases, and we recommend using it instead. In an exceptional case when `resetLocalAttrs` doesn't cut it, go for [https://github.com/scalamacros/resetallattrs](https://github.com/scalamacros/resetallattrs). - -32) **Removal of isLocal**. `Symbol.isLocal` wasn't doing what is was advertising, and there was no way to fix it. Therefore we have removed it without any deprecation warnings and are recommending using `Symbol.isPrivateThis` and/or `Symbol.isProtectedThis` instead. - -33) **Removal of isOverride**. Same story as with `Symbol.isLocal`. This method was broken beyond repair, which is why it was removed from the public API. `Symbol.allOverriddenSymbols` (or its newly introduced alias `Symbol.overrides`) should be used instead. - -### How to make your 2.11.0 macros work in 2.10.x - -34) **Quasiquotes**. We don't plan to release quasiquotes as part of the Scala 2.10.x distribution, but they can still be used in Scala 2.10.x by the virtue of the macro paradise plugin. Read [paradise documentation](http://docs.scala-lang.org/overviews/macros/paradise.html) to learn more about what's required to use the compiler plugin, what are the binary compatibility consequences and what are the support guarantees. - -35) **Most of the new functionality doesn't have equivalents in 2.10.x**. We don't plan to backport any of the new functionality, e.g. fundep materialization or macro bundles, to Scala 2.10.x (except for maybe thread safety for runtime reflection). Consult [the roadmap of macro paradise for Scala 2.10.x](http://docs.scala-lang.org/overviews/macros/roadmap.html) to see what features are supported in paradise. - -36) **Blackbox/whitebox**. If you're determined to have your macros blackbox, it's going to require additional effort to have those macros working consistently in both 2.10.x and 2.11.0, because in 2.10.x all macros are whitebox. First of all, make sure that you're not actually using any of [whitebox powers](http://docs.scala-lang.org/overviews/macros/blackbox-whitebox.html#codifying_the_distinction), otherwise you'll have to rewrite your macros first. Secondly, before returning from your macros impls, explicitly upcast the expansions to the type required by their macro defs. (Of course, none of this applies to whitebox macros. If don't mind your macros being whitebox, then you don't have to do anything to ensure cross-compatibility). - - object Macros { - def impl(c: Context) = { - import c.universe._ - q"new { val x = 2 }" - } - - def foo: Any = macro impl - } - - object Test extends App { - // works in Scala 2.10.x and Scala 2.11.0 if foo is whitebox - // doesn't work in Scala 2.11.0 if foo is blackbox - println(Macros.foo.x) - } - - object Macros { - def impl(c: Context) = { - import c.universe._ - q"(new { val x = 2 }): Any" // note the explicit type ascription - } - - def foo: Any = macro impl - } - - object Test extends App { - // consistently doesn't work in Scala 2.10.x and Scala 2.11.0 - // regardless of whether foo is whitebox or blackbox - println(Macros.foo.x) - } - -37) **@compileTimeOnly**. The `compileTimeOnly` annotation is secretly available in `scala-reflect.jar` as `scala.reflect.internal.compileTimeOnly` since Scala 2.10.1 (To the contrast, in Scala 2.11.0 `compileTimeOnly` lives in `scala-library.jar` under the name of `scala.annotations.compileTimeOnly`). If you don't mind the users of your API having to transitively depend on `scala-reflect.jar`, go ahead and use `compileTimeOnly` even in Scala 2.10.x - it will behave in the same fashion as in Scala 2.11.0. - -38) **Changes to how macro expansions typecheck**. Scala 2.11.0 changes the sequence of expected types used to typecheck whitebox macro expansions (and since all macros in Scala 2.10.x are whitebox, they all can potentially be affected), which can theoretically cause type inference problems. This is unlikely to become a problem even when migrating from 2.10.x to 2.11.0, but going in reverse direction has almost non-existent chances of causing issues. If you encounter difficulties, then like with any type inference glitch, try providing an explicit type annotation by upcasting macro expansions to the type that you want. - -39) **Introduction of Universe.internal and Context.internal**. Even though it's hard to imagine how this could work, it is possible to have a macro using internal APIs compileable with both Scala 2.10.x and 2.11.0. Big thanks to Jason Zaugg for showing us the way: - - // scala.reflect.macros.Context is available both in 2.10 and 2.11 - // in Scala 2.11.0 it is deprecated - // and aliased to scala.reflect.macros.whitebox.Context - import scala.reflect.macros.Context - import scala.language.experimental.macros - - // provides a source compatibility stub - // in Scala 2.10.x, it will make `import compat._` compile just fine, - // even though `c.universe` doesn't have `compat` - // in Scala 2.11.0, it will be ignored, becase `import c.universe._` - // brings its own `compat` in scope and that one takes precedence - private object HasCompat { val compat = ??? }; import HasCompat._ - - object Macros { - def impl(c: Context): c.Expr[Int] = { - import c.universe._ - // enables Tree.setType that's been removed in Scala 2.11.0 - import compat._ - c.Expr[Int](Literal(Constant(42)) setType definitions.IntTpe) - } - - def ultimateAnswer: Int = macro impl - } - -40) **Use macro-compat**. [Macro-compat](https://github.com/milessabin/macro-compat) is a small library which allows you to compile macros with Scala 2.10.x which are written to the Scala 2.11/2 macro API. It brings to Scala 2.10: type aliases for the blackbox and whitebox Context types, support for macro bundles, forwarders for the 2.11 API and support for using Tree in the macro def type signatures. diff --git a/overviews/macros/extractors.md b/overviews/macros/extractors.md deleted file mode 100644 index 9169b259b3..0000000000 --- a/overviews/macros/extractors.md +++ /dev/null @@ -1,94 +0,0 @@ ---- -layout: overview-large -title: Extractor Macros - -discourse: true - -partof: macros -num: 7 -outof: 13 ---- -EXPERIMENTAL - -**Eugene Burmako** - -Extractor macros are a feature of Scala 2.11.x and Scala 2.12.x, enabled by name-based extractors introduced by Paul Phillips in Scala 2.11.0-M5. Extractor macros are not supported in Scala 2.10.x. They are also not supported in macro paradise for Scala 2.10.x. - -## The pattern - -In a nutshell, given an unapply method (for simplicity, in this -example the scrutinee is of a concrete type, but it's also possible -to have the extractor be polymorphic, as demonstrated in the tests): - - def unapply(x: SomeType) = ??? - -One can write a macro that generates extraction signatures for unapply -on per-call basis, using the target of the calls (`c.prefix`) and the type -of the scrutinee (that comes with `x`), and then communicate these signatures -to the typechecker. - -For example, here's how one can define a macro that simply passes -the scrutinee back to the pattern match (for information on how to -express signatures that involve multiple extractees, visit -[scala/scala#2848](https://github.com/scala/scala/pull/2848)). - - def unapply(x: SomeType) = macro impl - def impl(c: Context)(x: c.Tree) = { - q""" - new { - class Match(x: SomeType) { - def isEmpty = false - def get = x - } - def unapply(x: SomeType) = new Match(x) - }.unapply($x) - """ - } - -In addition to the matcher, which implements domain-specific -matching logic, there's quite a bit of boilerplate here, but -every part of it looks necessary to arrange a non-frustrating dialogue -with the typer. Maybe something better can be done in this department, -but I can't see how, without introducing modifications to the typechecker. - -Even though the pattern uses structural types, somehow no reflective calls -are being generated (as verified by `-Xlog-reflective-calls` and then -by manual examination of the produced code). That's a mystery to me, but -that's also good news, since that means that extractor macros aren't -going to induce performance penalties. - -Almost. Unfortunately, I couldn't turn matchers into value classes -because one can't declare value classes local. Nevertheless, -I'm leaving a canary in place ([neg/t5903e](https://github.com/scala/scala/blob/00624a39ed84c3fd245dd9df7454d4cec4399e13/test/files/neg/t5903e/Macros_1.scala#L1)) that will let us know -once this restriction is lifted. - -## Use cases - -In particular, the pattern can be used to implement shapeshifting -pattern matchers for string interpolators without resorting to dirty -tricks. For example, quasiquote unapplications can be unhardcoded now: - - def doTypedApply(tree: Tree, fun0: Tree, args: List[Tree], ...) = { - ... - fun.tpe match { - case ExtractorType(unapply) if mode.inPatternMode => - // this hardcode in Typers.scala is no longer necessary - if (unapply == QuasiquoteClass_api_unapply) macroExpandUnapply(...) - else doTypedUnapply(tree, fun0, fun, args, mode, pt) - } - } - -Rough implementation strategy here would involve writing an extractor -macro that destructures `c.prefix`, analyzes parts of `StringContext` and -then generates an appropriate matcher as outlined above. - -Follow our test cases at [run/t5903a](https://github.com/scala/scala/tree/00624a39ed84c3fd245dd9df7454d4cec4399e13/test/files/run/t5903a), -[run/t5903b](https://github.com/scala/scala/tree/00624a39ed84c3fd245dd9df7454d4cec4399e13/test/files/run/t5903b), -[run/t5903c](https://github.com/scala/scala/tree/00624a39ed84c3fd245dd9df7454d4cec4399e13/test/files/run/t5903c), -[run/t5903d](https://github.com/scala/scala/tree/00624a39ed84c3fd245dd9df7454d4cec4399e13/test/files/run/t5903d) to see implementations -of this and other use cases for extractor macros. - -## Blackbox vs whitebox - -Extractor macros must be [whitebox](/overviews/macros/blackbox-whitebox.html). -If you declare an extractor macro as [blackbox](/overviews/macros/blackbox-whitebox.html), it will not work. diff --git a/overviews/macros/implicits.md b/overviews/macros/implicits.md deleted file mode 100644 index eedec2916c..0000000000 --- a/overviews/macros/implicits.md +++ /dev/null @@ -1,161 +0,0 @@ ---- -layout: overview-large -title: Implicit Macros - -discourse: true - -partof: macros -num: 6 -outof: 13 -languages: [ja] ---- -EXPERIMENTAL - -**Eugene Burmako** - -Implicit macros are shipped as an experimental feature of Scala since version 2.10.0, including the upcoming 2.11.0, -but require a critical bugfix in 2.10.2 to become fully operational. Implicit macros do not need macro paradise to work, -neither in 2.10.x, nor in 2.11. - -An extension to implicit macros, -called fundep materialization, is unavailable in 2.10.0 through 2.10.4, but has been implemented in -[macro paradise](/overviews/macros/paradise.html), Scala 2.10.5 and Scala 2.11.x. -Note that in 2.10.0 through 2.10.4, expansion of fundep materializer macros requires macro paradise, -which means that your users will have to add macro paradise to their builds in order to use your fundep materializers. -However, after fundep materializers expand, the resulting code will no longer have any references to macro paradise -and won't require its presence at compile-time or at runtime. Also note that in 2.10.5, expansion of -fundep materializer macros can happen without macro paradise, but then your users will have to enable -the -Yfundep-materialization compiler flag. - -## Implicit macros - -### Type classes - -The example below defines the `Showable` type class, which abstracts over a prettyprinting strategy. -The accompanying `show` method takes two parameters: an explicit one, the target, and an implicit one, -which carries the instance of `Showable`. - - trait Showable[T] { def show(x: T): String } - def show[T](x: T)(implicit s: Showable[T]) = s.show(x) - -After being declared like that, `show` can be called with only the target provided, and `scalac` -will try to infer the corresponding type class instance from the scope of the call site based -on the type of the target. If there is a matching implicit value in scope, it will be inferred -and compilation will succeed, otherwise a compilation error will occur. - - implicit object IntShowable extends Showable[Int] { - def show(x: Int) = x.toString - } - show(42) // "42" - show("42") // compilation error - -### Proliferation of boilerplate - -One of the well-known problems with type classes, in general and in particular in Scala, -is that instance definitions for similar types are frequently very similar, which leads to -proliferation of boilerplate code. - -For example, for a lot of objects prettyprinting means printing the name of their class -and the names and values of the fields. Even though this and similar recipes are very concise, -in practice it is often impossible to implement them concisely, so the programmer is forced -to repeat himself over and over again. - - class C(x: Int) - implicit def cShowable = new Showable[C] { - def show(c: C) = "C(" + c.x + ")" - } - - class D(x: Int) - implicit def dShowable = new Showable[D] { - def show(d: D) = "D(" + d.x + ")" - } - -This very use case can be implemented with runtime reflection, -but oftentimes reflection is either too imprecise because of erasure or -too slow because of the overhead it imposes. - -There also exist generic programming approaches based on type-level programming, for example, -[the `TypeClass` type class technique](http://typelevel.org/blog/2013/06/24/deriving-instances-1.html) introduced by Lars Hupel, -but they also suffer a performance hit in comparison with manually written type class instances. - -### Implicit materializers - -With implicit macros it becomes possible to eliminate the boilerplate by completely removing -the need to manually define type class instances, without sacrificing performance. - - trait Showable[T] { def show(x: T): String } - object Showable { - implicit def materializeShowable[T]: Showable[T] = macro ... - } - -Instead of writing multiple instance definitions, the programmer defines a single `materializeShowable` macro -in the companion object of the `Showable` type class. Members of a companion object belong to implicit scope -of an associated type class, which means that in cases when the programmer does not provide an explicit instance of `Showable`, -the materializer will be called. Upon being invoked, the materializer can acquire a representation of `T` and -generate the appropriate instance of the `Showable` type class. - -A nice thing about implicit macros is that they seamlessly meld into the pre-existing infrastructure of implicit search. -Such standard features of Scala implicits as multi-parametricity and overlapping instances are available to -implicit macros without any special effort from the programmer. For example, it is possible to define a non-macro -prettyprinter for lists of prettyprintable elements and have it transparently integrated with the macro-based materializer. - - implicit def listShowable[T](implicit s: Showable[T]) = - new Showable[List[T]] { - def show(x: List[T]) = { x.map(s.show).mkString("List(", ", ", ")") - } - } - show(List(42)) // prints: List(42) - -In this case, the required instance `Showable[Int]` would be generated by the materializing macro defined above. -Thus, by making macros implicit, they can be used to automate the materialization of type class instances, -while at the same time seamlessly integrating with non-macro implicits. - -## Fundep materialization - -### Problem statement - -The use case, which gave birth to fundep materializers, was provided by Miles Sabin and his [shapeless](https://github.com/milessabin/shapeless) library. In the old version of shapeless, before 2.0.0, Miles has defined the `Iso` trait, -which represents isomorphisms between types. `Iso` can be used to map case classes to tuples and vice versa -(actually, shapeless used Iso's to convert between case classes and HLists, but for simplicity let's use tuples). - - trait Iso[T, U] { - def to(t: T) : U - def from(u: U) : T - } - - case class Foo(i: Int, s: String, b: Boolean) - def conv[C, L](c: C)(implicit iso: Iso[C, L]): L = iso.to(c) - - val tp = conv(Foo(23, "foo", true)) - tp: (Int, String, Boolean) - tp == (23, "foo", true) - -If we try to write an implicit materializer for `Iso`, we will run into a wall. -When typechecking applications of methods like `conv`, scalac has to infer the type argument `L`, -which it has no clue about (and that's no wonder, since this is domain-specific knowledge). As a result, when we define an implicit -macro, which synthesizes `Iso[C, L]`, scalac will helpfully infer `L` as `Nothing` before expanding the macro and then everything will crumble. - -### Proposed solution - -As demonstrated by [https://github.com/scala/scala/pull/2499](https://github.com/scala/scala/pull/2499), the solution to the outlined -problem is extremely simple and elegant. - -In 2.10 we don't allow macro applications to expand until all their type arguments are inferred. However we don't have to do that. -The typechecker can infer as much as it possibly can (e.g. in the running example `C` will be inferred to `Foo` and -`L` will remain uninferred) and then stop. After that we expand the macro and then proceed with type inference using the type of the -expansion to help the typechecker with previously undetermined type arguments. This is how it's implemented in Scala 2.11.0. - -An illustration of this technique in action can be found in our [files/run/t5923c](https://github.com/scala/scala/tree/7b890f71ecd0d28c1a1b81b7abfe8e0c11bfeb71/test/files/run/t5923c) tests. -Note how simple everything is. The `materializeIso` implicit macro just takes its first type argument and uses it to produce an expansion. -We don't need to make sense of the second type argument (which isn't inferred yet), we don't need to interact with type inference - -everything happens automatically. - -Please note that there is [a funny caveat](https://github.com/scala/scala/blob/7b890f71ecd0d28c1a1b81b7abfe8e0c11bfeb71/test/files/run/t5923a/Macros_1.scala) with Nothings that we plan to address later. - -## Blackbox vs whitebox - -Vanilla materializers (covered in the first part of this document) can be both [blackbox](/overviews/macros/blackbox-whitebox.html) and [whitebox](/overviews/macros/blackbox-whitebox.html). - -There is a noticeable distinction between blackbox and whitebox materializers. An error in an expansion of a blackbox implicit macro (e.g. an explicit c.abort call or an expansion typecheck error) will produce a compilation error. An error in an expansion of a whitebox implicit macro will just remove the macro from the list of implicit candidates in the current implicit search, without ever reporting an actual error to the user. This creates a trade-off: blackbox implicit macros feature better error reporting, while whitebox implicit macros are more flexible, being able to dynamically turn themselves off when necessary. - -Fundep materializers must be whitebox. If you declare a fundep materializer as blackbox, it will not work. diff --git a/overviews/macros/inference.md b/overviews/macros/inference.md deleted file mode 100644 index 2d984545e1..0000000000 --- a/overviews/macros/inference.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -layout: overview-large -title: Inference-Driving Macros - -discourse: true -languages: [ja] ---- -EXPERIMENTAL - -**Eugene Burmako** - -The page has been moved to [/overviews/macros/implicits.html](/overviews/macros/implicits.html). diff --git a/overviews/macros/overview.md b/overviews/macros/overview.md deleted file mode 100644 index a098eeae04..0000000000 --- a/overviews/macros/overview.md +++ /dev/null @@ -1,367 +0,0 @@ ---- -layout: overview-large -title: Def Macros - -discourse: true - -partof: macros -num: 3 -outof: 13 -languages: [ja] ---- -EXPERIMENTAL - -**Eugene Burmako** - -Def macros are shipped as an experimental feature of Scala since version 2.10.0. -A subset of def macros, pending a thorough specification, is tentatively scheduled to become stable in one of the future versions of Scala. - -UPDATE This guide has been written for Scala 2.10.0, and now we're well into the Scala 2.11.x release cycle, -so naturally the contents of the document are outdated. Nevertheless, this guide is not obsolete - -everything written here will still work in both Scala 2.10.x and Scala 2.11.x, so it will be helpful to read it through. -After reading the guide, take a look at the docs on [quasiquotes](/overviews/quasiquotes/intro.html) -and [macro bundles](/overviews/macros/bundles.html) to familiarize yourself with latest developments -that dramatically simplify writing macros. Then it might be a good idea to follow -[our macro workshop](https://github.com/scalamacros/macrology201) for more in-depth examples. - -## Intuition - -Here is a prototypical macro definition: - - def m(x: T): R = macro implRef - -At first glance macro definitions are equivalent to normal function definitions, except for their body, which starts with the conditional keyword `macro` and is followed by a possibly qualified identifier that refers to a static macro implementation method. - -If, during type-checking, the compiler encounters an application of the macro `m(args)`, it will expand that application by invoking the corresponding macro implementation method, with the abstract-syntax trees of the argument expressions args as arguments. The result of the macro implementation is another abstract syntax tree, which will be inlined at the call site and will be type-checked in turn. - -The following code snippet declares a macro definition assert that references a macro implementation Asserts.assertImpl (definition of assertImpl is provided below): - - def assert(cond: Boolean, msg: Any) = macro Asserts.assertImpl - -A call `assert(x < 10, "limit exceeded")` would then lead at compile time to an invocation - - assertImpl(c)(<[ x < 10 ]>, <[ “limit exceeded” ]>) - -where `c` is a context argument that contains information collected by the compiler at the call site, and the other two arguments are abstract syntax trees representing the two expressions `x < 10` and `limit exceeded`. - -In this document, `<[ expr ]>` denotes the abstract syntax tree that represents the expression expr. This notation has no counterpart in our proposed extension of the Scala language. In reality, the syntax trees would be constructed from the types in trait `scala.reflect.api.Trees` and the two expressions above would look like this: - - Literal(Constant("limit exceeded")) - - Apply( - Select(Ident(TermName("x")), TermName("$less"), - List(Literal(Constant(10))))) - -Here is a possible implementation of the `assert` macro: - - import scala.reflect.macros.Context - import scala.language.experimental.macros - - object Asserts { - def raise(msg: Any) = throw new AssertionError(msg) - def assertImpl(c: Context) - (cond: c.Expr[Boolean], msg: c.Expr[Any]) : c.Expr[Unit] = - if (assertionsEnabled) - <[ if (!cond) raise(msg) ]> - else - <[ () ]> - } - -As the example shows, a macro implementation takes several parameter lists. First comes a single parameter, of type `scala.reflect.macros.Context`. This is followed by a list of parameters that have the same names as the macro definition parameters. But where the original macro parameter has type `T`, a macro implementation parameter has type `c.Expr[T]`. `Expr[T]` is a type defined in `Context` that wraps an abstract syntax tree of type `T`. The result type of the `assertImpl` macro implementation is again a wrapped tree, of type `c.Expr[Unit]`. - -Also note that macros are considered an experimental and advanced feature, -so in order to write macros you need to enable them. -Do that either with `import scala.language.experimental.macros` on per-file basis -or with `-language:experimental.macros` (providing a compiler switch) on per-compilation basis. -Your users, however, don't need to enable anything - macros look like normal methods -and can be used as normal methods, without any compiler switches or additional configurations. - -### Generic macros - -Macro definitions and macro implementations may both be generic. If a macro implementation has type parameters, actual type arguments must be given explicitly in the macro definition’s body. Type parameters in an implementation may come with `WeakTypeTag` context bounds. In that case the corresponding type tags describing the actual type arguments instantiated at the application site will be passed along when the macro is expanded. - -The following code snippet declares a macro definition `Queryable.map` that references a macro implementation `QImpl.map`: - - class Queryable[T] { - def map[U](p: T => U): Queryable[U] = macro QImpl.map[T, U] - } - - object QImpl { - def map[T: c.WeakTypeTag, U: c.WeakTypeTag] - (c: Context) - (p: c.Expr[T => U]): c.Expr[Queryable[U]] = ... - } - -Now consider a value `q` of type `Queryable[String]` and a macro call - - q.map[Int](s => s.length) - -The call is expanded to the following reflective macro invocation - - QImpl.map(c)(<[ s => s.length ]>) - (implicitly[WeakTypeTag[String]], implicitly[WeakTypeTag[Int]]) - -## A complete example - -This section provides an end-to-end implementation of a `printf` macro, which validates and applies the format string at compile-time. -For the sake of simplicity the discussion uses console Scala compiler, but as explained below macros are also supported by Maven and sbt. - -Writing a macro starts with a macro definition, which represents the facade of the macro. -Macro definition is a normal function with anything one might fancy in its signature. -Its body, though, is nothing more that a reference to an implementation. -As mentioned above, to define a macro one needs to import `scala.language.experimental.macros` -or to enable a special compiler switch, `-language:experimental.macros`. - - import scala.language.experimental.macros - def printf(format: String, params: Any*): Unit = macro printf_impl - -Macro implementation must correspond to macro definitions that use it (typically there's only one, but there might also be many). In a nutshell, every parameter of type `T` in the signature of a macro definition must correspond to a parameter of type `c.Expr[T]` in the signature of a macro implementation. The full list of rules is quite involved, but it's never a problem, because if the compiler is unhappy, it will print the signature it expects in the error message. - - import scala.reflect.macros.Context - def printf_impl(c: Context)(format: c.Expr[String], params: c.Expr[Any]*): c.Expr[Unit] = ... - -Compiler API is exposed in `scala.reflect.macros.Context`. Its most important part, reflection API, is accessible via `c.universe`. -It's customary to import `c.universe._`, because it includes a lot of routinely used functions and types - - import c.universe._ - -First of all, the macro needs to parse the provided format string. -Macros run during the compile-time, so they operate on trees, not on values. -This means that the format parameter of the `printf` macro will be a compile-time literal, not an object of type `java.lang.String`. -This also means that the code below won't work for `printf(get_format(), ...)`, because in that case `format` won't be a string literal, but rather an AST that represents a function application. - - val Literal(Constant(s_format: String)) = format.tree - -Typical macros (and this macro is not an exception) need to create ASTs (abstract syntax trees) which represent Scala code. -To learn more about generation of Scala code, take a look at [the overview of reflection](http://docs.scala-lang.org/overviews/reflection/overview.html). Along with creating ASTs the code provided below also manipulates types. -Note how we get a hold of Scala types that correspond to `Int` and `String`. -Reflection overview linked above covers type manipulations in detail. -The final step of code generation combines all the generated code into a `Block`. -Note the call to `reify`, which provides a shortcut for creating ASTs. - - val evals = ListBuffer[ValDef]() - def precompute(value: Tree, tpe: Type): Ident = { - val freshName = TermName(c.fresh("eval$")) - evals += ValDef(Modifiers(), freshName, TypeTree(tpe), value) - Ident(freshName) - } - - val paramsStack = Stack[Tree]((params map (_.tree)): _*) - val refs = s_format.split("(?<=%[\\w%])|(?=%[\\w%])") map { - case "%d" => precompute(paramsStack.pop, typeOf[Int]) - case "%s" => precompute(paramsStack.pop, typeOf[String]) - case "%%" => Literal(Constant("%")) - case part => Literal(Constant(part)) - } - - val stats = evals ++ refs.map(ref => reify(print(c.Expr[Any](ref).splice)).tree) - c.Expr[Unit](Block(stats.toList, Literal(Constant(())))) - -The snippet below represents a complete definition of the `printf` macro. -To follow the example, create an empty directory and copy the code to a new file named `Macros.scala`. - - import scala.reflect.macros.Context - import scala.collection.mutable.{ListBuffer, Stack} - - object Macros { - def printf(format: String, params: Any*): Unit = macro printf_impl - - def printf_impl(c: Context)(format: c.Expr[String], params: c.Expr[Any]*): c.Expr[Unit] = { - import c.universe._ - val Literal(Constant(s_format: String)) = format.tree - - val evals = ListBuffer[ValDef]() - def precompute(value: Tree, tpe: Type): Ident = { - val freshName = TermName(c.fresh("eval$")) - evals += ValDef(Modifiers(), freshName, TypeTree(tpe), value) - Ident(freshName) - } - - val paramsStack = Stack[Tree]((params map (_.tree)): _*) - val refs = s_format.split("(?<=%[\\w%])|(?=%[\\w%])") map { - case "%d" => precompute(paramsStack.pop, typeOf[Int]) - case "%s" => precompute(paramsStack.pop, typeOf[String]) - case "%%" => Literal(Constant("%")) - case part => Literal(Constant(part)) - } - - val stats = evals ++ refs.map(ref => reify(print(c.Expr[Any](ref).splice)).tree) - c.Expr[Unit](Block(stats.toList, Literal(Constant(())))) - } - } - -To use the `printf` macro, create another file `Test.scala` in the same directory and put the following code into it. -Note that using a macro is as simple as calling a function. It also doesn't require importing `scala.language.experimental.macros`. - - object Test extends App { - import Macros._ - printf("hello %s!", "world") - } - -An important aspect of macrology is separate compilation. To perform macro expansion, compiler needs a macro implementation in executable form. Thus macro implementations need to be compiled before the main compilation, otherwise you might see the following error: - - ~/Projects/Kepler/sandbox$ scalac -language:experimental.macros Macros.scala Test.scala - Test.scala:3: error: macro implementation not found: printf (the most common reason for that is that - you cannot use macro implementations in the same compilation run that defines them) - pointing to the output of the first phase - printf("hello %s!", "world") - ^ - one error found - - ~/Projects/Kepler/sandbox$ scalac Macros.scala && scalac Test.scala && scala Test - hello world! - -## Tips and tricks - -### Using macros with the command-line Scala compiler - -This scenario is covered in the previous section. In short, compile macros and their usages using separate invocations of `scalac`, and everything should work fine. If you use REPL, then it's even better, because REPL processes every line in a separate compilation run, so you'll be able to define a macro and use it right away. - -### Using macros with Maven or sbt - -The walkthrough in this guide uses the simplest possible command-line compilation, but macros also work with build tools such as Maven and sbt. Check out [https://github.com/scalamacros/sbt-example](https://github.com/scalamacros/sbt-example) or [https://github.com/scalamacros/maven-example](https://github.com/scalamacros/maven-example) for end-to-end examples, but in a nutshell you only need to know two things: -* Macros needs scala-reflect.jar in library dependencies. -* The separate compilation restriction requires macros to be placed in a separate project. - -### Using macros with Scala IDE or Intellij IDEA - -Both in Scala IDE and in Intellij IDEA macros are known to work fine, given they are moved to a separate project. - -### Debugging macros - -Debugging macros (i.e. the logic that drives macro expansion) is fairly straightforward. Since macros are expanded within the compiler, all that you need is to run the compiler under a debugger. To do that, you need to: 1) add all (!) the libraries from the lib directory in your Scala home (which include such jar files as `scala-library.jar`, `scala-reflect.jar` and `scala-compiler.jar`) to the classpath of your debug configuration, 2) set `scala.tools.nsc.Main` as an entry point, 3) provide the `-Dscala.usejavacp=true` system property for the JVM (very important!), 4) set command-line arguments for the compiler as `-cp Test.scala`, where `Test.scala` stands for a test file containing macro invocations to be expanded. After all that is done, you should be able to put a breakpoint inside your macro implementation and launch the debugger. - -What really requires special support in tools is debugging the results of macro expansion (i.e. the code that is generated by a macro). Since this code is never written out manually, you cannot set breakpoints there, and you won't be able to step through it. Scala IDE and Intellij IDEA teams will probably add support for this in their debuggers at some point, but for now the only way to debug macro expansions are diagnostic prints: `-Ymacro-debug-lite` (as described below), which prints out the code emitted by macros, and println to trace the execution of the generated code. - -### Inspecting generated code - -With `-Ymacro-debug-lite` it is possible to see both pseudo-Scala representation of the code generated by macro expansion and raw AST representation of the expansion. Both have their merits: the former is useful for surface analysis, while the latter is invaluable for fine-grained debugging. - - ~/Projects/Kepler/sandbox$ scalac -Ymacro-debug-lite Test.scala - typechecking macro expansion Macros.printf("hello %s!", "world") at - source-C:/Projects/Kepler/sandbox\Test.scala,line-3,offset=52 - { - val eval$1: String = "world"; - scala.this.Predef.print("hello "); - scala.this.Predef.print(eval$1); - scala.this.Predef.print("!"); - () - } - Block(List( - ValDef(Modifiers(), TermName("eval$1"), TypeTree().setType(String), Literal(Constant("world"))), - Apply( - Select(Select(This(TypeName("scala")), TermName("Predef")), TermName("print")), - List(Literal(Constant("hello")))), - Apply( - Select(Select(This(TypeName("scala")), TermName("Predef")), TermName("print")), - List(Ident(TermName("eval$1")))), - Apply( - Select(Select(This(TypeName("scala")), TermName("Predef")), TermName("print")), - List(Literal(Constant("!"))))), - Literal(Constant(()))) - -### Macros throwing unhandled exceptions - -What happens if macro throws an unhandled exception? For example, let's crash the `printf` macro by providing invalid input. -As the printout shows, nothing dramatic happens. Compiler guards itself against misbehaving macros, prints relevant part of a stack trace, and reports an error. - - ~/Projects/Kepler/sandbox$ scala - Welcome to Scala version 2.10.0-20120428-232041-e6d5d22d28 (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_25). - Type in expressions to have them evaluated. - Type :help for more information. - - scala> import Macros._ - import Macros._ - - scala> printf("hello %s!") - :11: error: exception during macro expansion: - java.util.NoSuchElementException: head of empty list - at scala.collection.immutable.Nil$.head(List.scala:318) - at scala.collection.immutable.Nil$.head(List.scala:315) - at scala.collection.mutable.Stack.pop(Stack.scala:140) - at Macros$$anonfun$1.apply(Macros.scala:49) - at Macros$$anonfun$1.apply(Macros.scala:47) - at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:237) - at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:237) - at scala.collection.IndexedSeqOptimized$class.foreach(IndexedSeqOptimized.scala:34) - at scala.collection.mutable.ArrayOps.foreach(ArrayOps.scala:39) - at scala.collection.TraversableLike$class.map(TraversableLike.scala:237) - at scala.collection.mutable.ArrayOps.map(ArrayOps.scala:39) - at Macros$.printf_impl(Macros.scala:47) - - printf("hello %s!") - ^ - -### Reporting warnings and errors - -The canonical way to interact with the user is through the methods of `scala.reflect.macros.FrontEnds`. -`c.error` reports a compilation error, `c.info` issues a warning, `c.abort` reports an error and terminates -execution of a macro. - - scala> def impl(c: Context) = - c.abort(c.enclosingPosition, "macro has reported an error") - impl: (c: scala.reflect.macros.Context)Nothing - - scala> def test = macro impl - defined term macro test: Any - - scala> test - :32: error: macro has reported an error - test - ^ - -Note that at the moment reporting facilities don't support multiple warnings or errors per position as described in -[SI-6910](https://issues.scala-lang.org/browse/SI-6910). This means that only the first error or warning per position -will be reported, and the others will be lost (also errors trump warnings at the same position, even if those are reported earlier). - -### Writing bigger macros - -When the code of a macro implementation grows big enough to warrant modularization beyond the body of the implementation method, it becomes apparent that one needs to carry around the context parameter, because most things of interest are path-dependent on the context. - -One of the approaches is to write a class that takes a parameter of type `Context` and then split the macro implementation into a series of methods of that class. This is natural and simple, except that it's hard to get it right. Here's a typical compilation error. - - scala> class Helper(val c: Context) { - | def generate: c.Tree = ??? - | } - defined class Helper - - scala> def impl(c: Context): c.Expr[Unit] = { - | val helper = new Helper(c) - | c.Expr(helper.generate) - | } - :32: error: type mismatch; - found : helper.c.Tree - (which expands to) helper.c.universe.Tree - required: c.Tree - (which expands to) c.universe.Tree - c.Expr(helper.generate) - ^ - -The problem in this snippet is in a path-dependent type mismatch. The Scala compiler does not understand that `c` in `impl` is the same object as `c` in `Helper`, even though the helper is constructed using the original `c`. - -Luckily just a small nudge is all that is needed for the compiler to figure out what's going on. One of the possible ways of doing that is using refinement types (the example below is the simplest application of the idea; for example, one could also write an implicit conversion from `Context` to `Helper` to avoid explicit instantiations and simplify the calls). - - scala> abstract class Helper { - | val c: Context - | def generate: c.Tree = ??? - | } - defined class Helper - - scala> def impl(c1: Context): c1.Expr[Unit] = { - | val helper = new { val c: c1.type = c1 } with Helper - | c1.Expr(helper.generate) - | } - impl: (c1: scala.reflect.macros.Context)c1.Expr[Unit] - -An alternative approach is to pass the identity of the context in an explicit type parameter. Note how the constructor of `Helper` uses `c.type` to express the fact that `Helper.c` is the same as the original `c`. Scala's type inference can't figure this out on its own, so we need to help it. - - scala> class Helper[C <: Context](val c: C) { - | def generate: c.Tree = ??? - | } - defined class Helper - - scala> def impl(c: Context): c.Expr[Unit] = { - | val helper = new Helper[c.type](c) - | c.Expr(helper.generate) - | } - impl: (c: scala.reflect.macros.Context)c.Expr[Unit] diff --git a/overviews/macros/paradise.md b/overviews/macros/paradise.md deleted file mode 100644 index c17fc1bd1d..0000000000 --- a/overviews/macros/paradise.md +++ /dev/null @@ -1,60 +0,0 @@ ---- -layout: overview-large -title: Macro Paradise - -discourse: true - -partof: macros -num: 11 -outof: 13 -languages: [ja] ---- -NEW - -**Eugene Burmako** - -> I have always imagined that paradise will be a kind of library. -> Jorge Luis Borges, "Poem of the Gifts" - -Macro paradise is a plugin for several versions of Scala compilers. -It is designed to reliably work with production releases of scalac, -making latest macro developments available way before they end up in future versions Scala. -Refer to the roadmap for [the list of supported features and versions](/overviews/macros/roadmap.html) -and visit [the paradise announcement](http://scalamacros.org/news/2013/08/07/roadmap-for-macro-paradise.html) -to learn more about our support guarantees. - - ~/210x $ scalac -Xplugin:paradise_*.jar -Xshow-phases - phase name id description - ---------- -- ----------- - parser 1 parse source into ASTs, perform simple desugaring - macroparadise 2 let our powers combine - namer 3 resolve names, attach symbols to trees in paradise - packageobjects 4 load package objects in paradise - typer 5 the meat and potatoes: type the trees in paradise - ... - -Some features in macro paradise bring a compile-time dependency on the macro paradise plugin, -some features do not, however none of those features need macro paradise at runtime. -Proceed to the [the feature list](/overviews/macros/roadmap.html) document for more information. - -Consult [https://github.com/scalamacros/sbt-example-paradise](https://github.com/scalamacros/sbt-example-paradise) -for an end-to-end example, but in a nutshell working with macro paradise is as easy as adding the following two lines -to your build (granted you’ve already [set up sbt](/overviews/macros/overview.html#using_macros_with_maven_or_sbt) -to use macros). - - resolvers += Resolver.sonatypeRepo("releases") - addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full) - -To use macro paradise in Maven follow the instructions provided at Stack Overflow on the page ["Enabling the macro-paradise Scala compiler plugin in Maven projects"](http://stackoverflow.com/questions/19086241/enabling-the-macro-paradise-scala-compiler-plugin-in-maven-projects) (also make sure to add the dependency on the Sonatype snapshots repository and `scala-reflect.jar`). - - - - org.scalamacros - paradise_ - 2.1.0 - - - -Sources of macro paradise are available at [https://github.com/scalamacros/paradise](https://github.com/scalamacros/paradise). -There are branches that support the latest 2.10.x release, the latest 2.11.x release, -snapshots of 2.10.x, 2.11.x and 2.12.x, as well as Scala virtualized. diff --git a/overviews/macros/quasiquotes.md b/overviews/macros/quasiquotes.md deleted file mode 100644 index f305dab270..0000000000 --- a/overviews/macros/quasiquotes.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -layout: overview-large -title: Quasiquotes - -discourse: true - -partof: macros -num: 4 -outof: 13 -languages: [ja] ---- - -Quasiquote guide has been moved to [/overviews/quasiquotes/intro.html](/overviews/quasiquotes/intro.html). \ No newline at end of file diff --git a/overviews/macros/roadmap.md b/overviews/macros/roadmap.md deleted file mode 100644 index e0264b5c5b..0000000000 --- a/overviews/macros/roadmap.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -layout: overview-large -title: Roadmap - -discourse: true - -partof: macros -num: 12 -outof: 13 -languages: [ja] ---- - -EXPERIMENTAL - -**Eugene Burmako** - -At the moment, we don't plan to introduce new reflection- or macro-related features in Scala 2.12, -so the functionality of Scala 2.12 and Paradise 2.12 is going to be the same as Scala 2.11 and Paradise 2.11 -modulo bugfixes and stability improvements. - -Feature-wise, our main effort is currently targeted at [scala.meta](http://scalameta.org), -the new foundation for metaprogramming Scala, which is simpler, more robust and much more suitable for portability -than the current system based on scala.reflect. We hope that one day scala.meta will supersede scala.reflect -and become the new standard way of doing metaprogramming in Scala. - -| Feature | Scala 2.10 | [Paradise 2.10](/overviews/macros/paradise.html) | Scala 2.11 | [Paradise 2.11](/overviews/macros/paradise.html) | Scala 2.12 | [Paradise 2.12](/overviews/macros/paradise.html) | -|-----------------------------------------------------------------------------------|---------------------------------|--------------------------------------------------|----------------------------|--------------------------------------------------|-----------------------------|--------------------------------------------------| -| [Blackbox/whitebox separation](/overviews/macros/blackbox-whitebox.html) | No | No 1 | Yes | Yes 1 | Yes | Yes 1 | -| [Def macros](/overviews/macros/overview.html) | Yes | Yes 1 | Yes | Yes 1 | Yes | Yes 1 | -| [Macro bundles](/overviews/macros/bundles.html) | No | No 1 | Yes | Yes 1 | Yes | Yes 1 | -| [Implicit macros](/overviews/macros/implicits.html) | Yes (since 2.10.2) | Yes 1 | Yes | Yes 1 | Yes | Yes 1 | -| [Fundep materialization](/overviews/macros/implicits.html#fundep_materialization) | Yes (since 2.10.5) 3 | Yes 2 | Yes | Yes 1 | Yes | Yes 1 | -| [Type providers](/overviews/macros/typeproviders.html) | Partial support (see docs) | Yes 2 | Partial support (see docs) | Yes 2 | Partial support (see docs) | Yes 2 | -| [Quasiquotes](/overviews/quasiquotes/intro.html) | No | Yes 1 | Yes | Yes 1 | Yes | Yes 1 | -| [Type macros](/overviews/macros/typemacros.html) | No | No | No | No | No | No | -| [Untyped macros](/overviews/macros/untypedmacros.html) | No | No | No | No | No | No | -| [Macro annotations](/overviews/macros/annotations.html) | No | Yes 2 | No | Yes 2 | No | Yes 2 | - -

        1 This feature doesn't bring a compile-time or a runtime dependency on macro paradise. This means that neither compiling against your bytecode that uses this feature, nor running this bytecode requires the macro paradise plugin to be present on classpath.

        -

        2 This feature brings a compile-time, but not a runtime dependency on macro paradise. This means that compiling against your bytecode that uses this feature will need the plugin to be added to your users' builds, however running this bytecode or results of macro expansions produced by this bytecode doesn't need additional classpath entries.

        -

        3 Only works under -Yfundep-materialization.

        diff --git a/overviews/macros/typemacros.md b/overviews/macros/typemacros.md deleted file mode 100644 index a7c04a6af3..0000000000 --- a/overviews/macros/typemacros.md +++ /dev/null @@ -1,92 +0,0 @@ ---- -layout: overview-large -title: Type Macros - -discourse: true -languages: [ja] ---- -OBSOLETE - -**Eugene Burmako** - -Type macros used to be available in previous versions of ["Macro Paradise"](/overviews/macros/paradise.html), -but are not supported anymore in macro paradise 2.0. -Visit [the paradise 2.0 announcement](http://scalamacros.org/news/2013/08/05/macro-paradise-2.0.0-snapshot.html) -for an explanation and suggested migration strategy. - -## Intuition - -Just as def macros make the compiler execute custom functions when it sees invocations of certain methods, type macros let one hook into the compiler when certain types are used. The snippet below shows definition and usage of the `H2Db` macro, which generates case classes representing tables in a database along with simple CRUD functionality. - - type H2Db(url: String) = macro impl - - object Db extends H2Db("coffees") - - val brazilian = Db.Coffees.insert("Brazilian", 99, 0) - Db.Coffees.update(brazilian.copy(price = 10)) - println(Db.Coffees.all) - -The full source code of the `H2Db` type macro is provided [at GitHub](https://github.com/xeno-by/typemacros-h2db), and this guide covers its most important aspects. First the macro generates the statically typed database wrapper by connecting to a database at compile-time (tree generation is explained in [the reflection overview](http://docs.scala-lang.org/overviews/reflection/overview.html)). Then it uses the NEW `c.introduceTopLevel` API ([Scaladoc](https://scala-webapps.epfl.ch/jenkins/view/misc/job/macro-paradise211-nightly-main/ws/dists/latest/doc/scala-devel-docs/api/index.html#scala.reflect.macros.Synthetics)) to insert the generated wrapper into the list of top-level definitions maintained by the compiler. Finally, the macro returns an `Apply` node, which represents a super constructor call to the generated class. NOTE that type macros are supposed to expand into `c.Tree`, unlike def macros, which expand into `c.Expr[T]`. That's because `Expr`s represent terms, while type macros expand into types. - - type H2Db(url: String) = macro impl - - def impl(c: Context)(url: c.Expr[String]): c.Tree = { - val name = c.freshName(c.enclosingImpl.name).toTypeName - val clazz = ClassDef(..., Template(..., generateCode())) - c.introduceTopLevel(c.enclosingPackage.pid.toString, clazz) - val classRef = Select(c.enclosingPackage.pid, name) - Apply(classRef, List(Literal(Constant(c.eval(url))))) - } - - object Db extends H2Db("coffees") - // equivalent to: object Db extends Db$1("coffees") - -Instead of generating a synthetic class and expanding into a reference to it, a type macro can transform its host instead by returning a `Template` tree. Inside scalac both class and object definitions are internally represented as thin wrappers over `Template` trees, so by expanding into a template, type macro has a possibility to rewrite the entire body of the affected class or object. You can see a full-fledged example of this technique [at GitHub](https://github.com/xeno-by/typemacros-lifter). - - type H2Db(url: String) = macro impl - - def impl(c: Context)(url: c.Expr[String]): c.Tree = { - val Template(_, _, existingCode) = c.enclosingTemplate - Template(..., existingCode ++ generateCode()) - } - - object Db extends H2Db("coffees") - // equivalent to: object Db { - // - // - // } - -## Details - -Type macros represent a hybrid between def macros and type members. On the one hand, they are defined like methods (e.g. they can have value arguments, type parameters with context bounds, etc). On the other hand, they belong to the namespace of types and, as such, they can only be used where types are expected (see an exhaustive example [at GitHub](https://github.com/scalamacros/kepler/blob/paradise/macros211/test/files/run/macro-typemacros-used-in-funny-places-a/Test_2.scala)), they can only override types or other type macros, etc. - -| Feature | Def macros | Type macros | Type members | -|--------------------------------|------------|-------------|--------------| -| Are split into defs and impl | Yes | Yes | No | -| Can have value parameters | Yes | Yes | No | -| Can have type parameters | Yes | Yes | Yes | -| ... with variance annotations | No | No | Yes | -| ... with context bounds | Yes | Yes | No | -| Can be overloaded | Yes | Yes | No | -| Can be inherited | Yes | Yes | Yes | -| Can override and be overridden | Yes | Yes | Yes | - -In Scala programs type macros can appear in one of five possible roles: type role, applied type role, parent type role, new role and annotation role. Depending on the role in which a macro is used, which can be inspected with the NEW `c.macroRole` API ([Scaladoc](https://scala-webapps.epfl.ch/jenkins/view/misc/job/macro-paradise-nightly-main/ws/dists/latest/doc/scala-devel-docs/api/index.html#scala.reflect.macros.Enclosures)), its list of allowed expansions is different. - -| Role | Example | Class | Non-class? | Apply? | Template? | -|--------------|-------------------------------------------------|-------|------------|--------|-----------| -| Type | `def x: TM(2)(3) = ???` | Yes | Yes | No | No | -| Applied type | `class C[T: TM(2)(3)]` | Yes | Yes | No | No | -| Parent type | `class C extends TM(2)(3)`
        `new TM(2)(3){}` | Yes | No | Yes | Yes | -| New | `new TM(2)(3)` | Yes | No | Yes | No | -| Annotation | `@TM(2)(3) class C` | Yes | No | Yes | No | - -To put it in a nutshell, expansion of a type macro replace the usage of a type macro with a tree it returns. To find out whether an expansion makes sense, mentally replace some usage of a macro with its expansion and check whether the resulting program is correct. - -For example, a type macro used as `TM(2)(3)` in `class C extends TM(2)(3)` can expand into `Apply(Ident(TypeName("B")), List(Literal(Constant(2))))`, because that would result in `class C extends B(2)`. However the same expansion wouldn't make sense if `TM(2)(3)` was used as a type in `def x: TM(2)(3) = ???`, because `def x: B(2) = ???` (given that `B` itself is not a type macro; if it is, it will be recursively expanded and the result of the expansion will determine validity of the program). - -## Tips and tricks - -### Generating classes and objects - -With type macros you might increasingly find yourself in a zone where `reify` is not applicable, as explained [at StackOverflow](http://stackoverflow.com/questions/13795490/how-to-use-type-calculated-in-scala-macro-in-a-reify-clause). In that case consider using [quasiquotes](/overviews/quasiquotes/intro.html), another experimental feature from macro paradise, as an alternative to manual tree construction. diff --git a/overviews/macros/typeproviders.md b/overviews/macros/typeproviders.md deleted file mode 100644 index d8ad9101ad..0000000000 --- a/overviews/macros/typeproviders.md +++ /dev/null @@ -1,141 +0,0 @@ ---- -layout: overview-large -title: Type Providers - -discourse: true - -partof: macros -num: 8 -outof: 13 -languages: [ja] ---- -EXPERIMENTAL - -**Eugene Burmako** - -Type providers aren't implemented as a dedicated macro flavor, but are rather built on top of the functionality -that Scala macros already provide. - -There are two strategies of emulating type providers: one based on structural types (referred to as "anonymous type providers") -and one based on macro annotations (referred to as "public type providers"). The former builds on functionality available -in 2.10.x, 2.11.x and 2.12.x, while the latter requires macro paradise. Both strategies can be used to implement erased type providers -as described below. - -Note that macro paradise is needed both to compile and to expand macro annotations, -which means that both authors and users of public type providers will have to add macro paradise to their builds. -However, after macro annotations expand, the resulting code will no longer have any references to macro paradise -and won't require its presence at compile-time or at runtime. - -Recently we've given a talk about macro-based type providers in Scala, summarizing the state of the art and providing -concrete examples. Slides and accompanying code can be found at [https://github.com/travisbrown/type-provider-examples](https://github.com/travisbrown/type-provider-examples). - -## Introduction - -Type providers are a strongly-typed type-bridging mechanism, which enables information-rich programming in F# 3.0. -A type provider is a compile-time facility, which is capable of generating definitions and their implementations -based on static parameters describing datasources. Type providers can operate in two modes: non-erased and erased. -The former is similar to textual code generation in the sense that every generated type becomes bytecode, while -in the latter case generated types only manifest themselves during type checking, but before bytecode generation -get erased to programmer-provided upper bounds. - -In Scala, macro expansions can generate whatever code the programmer likes, including `ClassDef`, `ModuleDef`, `DefDef`, -and other definition nodes, so the code generation part of type providers is covered. Keeping that in mind, in order -to emulate type providers we need to solve two more challenges: - -1. Make generated definitions publicly visible (def macros, the only available macro flavor in Scala 2.10, 2.11 and 2.12, -are local in the sense that the scope of their expansions is limited: [https://groups.google.com/d/msg/scala-user/97ARwwoaq2U/kIGWeiqSGzcJ](https://groups.google.com/d/msg/scala-user/97ARwwoaq2U/kIGWeiqSGzcJ)). -1. Make generated definitions optionally erasable (Scala supports erasure for a number of language constructs, -e.g. for abstract type members and value classes, but the mechanism is not extensible, which means that macro writers can't customize it). - -## Anonymous type providers - -Even though the scope of definitions introduced by expansions of def macros is limited to those expansions, -these definitions can escape their scopes by turning into structural types. For instance, consider the `h2db` macro that -takes a connection string and generates a module that encapsulates the given database, expanding as follows. - - def h2db(connString: String): Any = macro ... - - // an invocation of the `h2db` macro - val db = h2db("jdbc:h2:coffees.h2.db") - - // expands into the following code - val db = { - trait Db { - case class Coffee(...) - val Coffees: Table[Coffee] = ... - } - new Db {} - } - -It is true that noone outside the macro expansion block would be able to refer to the `Coffee` class directly, -however if we inspect the type of `db`, we will find something fascinating. - - scala> val db = h2db("jdbc:h2:coffees.h2.db") - db: AnyRef { - type Coffee { val name: String; val price: Int; ... } - val Coffees: Table[this.Coffee] - } = $anon$1... - -As we can see, when the typechecker tried to infer a type for `db`, it took all the references to locally declared classes -and replaced them with structural types that contain all publicly visible members of those classes. The resulting type -captures the essence of the generated classes, providing a statically typed interface to their members. - - scala> db.Coffees.all - res1: List[Db$1.this.Coffee] = List(Coffee(Brazilian,99,0)) - -This approach to type providers is quite neat, because it can be used with production versions of Scala, however -it has performance problems caused by the fact that Scala emits reflective calls when compiling accesses to members -of structural types. There are several strategies of dealing with that, but this margin is too narrow to contain them -so I refer you to an amazing blog series by Travis Brown for details: [post 1](http://meta.plasm.us/posts/2013/06/19/macro-supported-dsls-for-schema-bindings/), [post 2](http://meta.plasm.us/posts/2013/07/11/fake-type-providers-part-2/), [post 3](http://meta.plasm.us/posts/2013/07/12/vampire-methods-for-structural-types/). - -## Public type providers - -With the help of [macro paradise](/overviews/macros/paradise.html) and its [macro annotations](/overviews/macros/annotations.html), it becomes -possible to easily generate publicly visible classes, without having to apply workarounds based on structural types. The annotation-based -solution is very straightforward, so I won't be writing much about it here. - - class H2Db(connString: String) extends StaticAnnotation { - def macroTransform(annottees: Any*) = macro ... - } - - @H2Db("jdbc:h2:coffees.h2.db") object Db - println(Db.Coffees.all) - Db.Coffees.insert("Brazilian", 99, 0) - -### Addressing the erasure problem - -We haven't looked into this in much detail, but there's a hypothesis that a combination of type members -and singleton types can provide an equivalent of erased type providers in F#. Concretely, classes that we don't want to erase -should be declared as usual, whereas classes that should be erased to a given upper bound should be declared as type aliases -to that upper bound parameterized by a singleton type that carries unique identifiers. With that approach, every new generated type -would still incur the overhead of additional bytecode to the metadata of type aliases, but that bytecode would be significantly smaller -than bytecode of a full-fledged class. This technique applies to both anonymous and public type providers. - - object Netflix { - type Title = XmlEntity["http://.../Title".type] - def Titles: List[Title] = ... - type Director = XmlEntity["http://.../Director".type] - def Directors: List[Director] = ... - ... - } - - class XmlEntity[Url] extends Dynamic { - def selectDynamic(field: String) = macro XmlEntity.impl - } - - object XmlEntity { - def impl(c: Context)(field: c.Tree) = { - import c.universe._ - val TypeRef(_, _, tUrl) = c.prefix.tpe - val ConstantType(Constant(sUrl: String)) = tUrl - val schema = loadSchema(sUrl) - val Literal(Constant(sField: String)) = field - if (schema.contains(sField)) q"${c.prefix}($sField)" - else c.abort(s"value $sField is not a member of $sUrl") - } - } - -## Blackbox vs whitebox - -Both anonymous and public type providers must be [whitebox](/overviews/macros/blackbox-whitebox.html). -If you declare a type provider macro as [blackbox](/overviews/macros/blackbox-whitebox.html), it will not work. diff --git a/overviews/macros/untypedmacros.md b/overviews/macros/untypedmacros.md deleted file mode 100644 index e827e5d3bc..0000000000 --- a/overviews/macros/untypedmacros.md +++ /dev/null @@ -1,73 +0,0 @@ ---- -layout: overview-large -title: Untyped Macros - -discourse: true -languages: [ja] ---- -OBSOLETE - -**Eugene Burmako** - -Untyped macros used to be available in previous versions of ["Macro Paradise"](/overviews/macros/paradise.html), -but are not supported anymore in macro paradise 2.0. -Visit [the paradise 2.0 announcement](http://scalamacros.org/news/2013/08/05/macro-paradise-2.0.0-snapshot.html) -for an explanation and suggested migration strategy. - -## Intuition - -Being statically typed is great, but sometimes that is too much of a burden. Take for example, the latest experiment of Alois Cochard with -implementing enums using type macros - the so called [Enum Paradise](https://github.com/aloiscochard/enum-paradise). Here's how Alois has -to write his type macro, which synthesizes an enumeration module from a lightweight spec: - - object Days extends Enum('Monday, 'Tuesday, 'Wednesday...) - -Instead of using clean identifier names, e.g. `Monday` or `Friday`, we have to quote those names, so that the typechecker doesn't complain -about non-existent identifiers. What a bummer - in the `Enum` macro we want to introduce new bindings, not to look up for existing ones, -but the compiler won't let us, because it thinks it needs to typecheck everything that goes into the macro. - -Let's take a look at how the `Enum` macro is implemented by inspecting the signatures of its macro definition and implementation. There we can -see that the macro definition signature says `symbol: Symbol*`, forcing the compiler to typecheck the corresponding argument: - - type Enum(symbol: Symbol*) = macro Macros.enum - object Macros { - def enum(c: Context)(symbol: c.Expr[Symbol]*): c.Tree = ... - } - -Untyped macros provide a notation and a mechanism to tell the compiler that you know better how to typecheck given arguments of your macro. -To do that, simply replace macro definition parameter types with underscores and macro implementation parameter types with `c.Tree`: - - type Enum(symbol: _*) = macro Macros.enum - object Macros { - def enum(c: Context)(symbol: c.Tree*): c.Tree = ... - } - -## Details - -The cease-typechecking underscore can be used in exactly three places in Scala programs: 1) as a parameter type in a macro, -2) as a vararg parameter type in a macro, 3) as a return type of a macro. Usages outside macros or as parts of complex types won't work. -The former will lead to a compile error, the latter, as in e.g. `List[_]`, will produce existential types as usual. - -Note that untyped macros enable extractor macros: [SI-5903](https://issues.scala-lang.org/browse/SI-5903). In 2.10.x, it is possible -to declare `unapply` or `unapplySeq` as macros, but usability of such macros is extremely limited as described in the discussion -of the linked JIRA issue. Untyped macros make the full power of textual abstraction available in pattern matching. The -[test/files/run/macro-expand-unapply-c](https://github.com/scalamacros/kepler/tree/paradise/macros211/test/files/run/macro-expand-unapply-c) -unit test provides details on this matter. - -If a macro has one or more untyped parameters, then when typing its expansions, the typechecker will do nothing to its arguments -and will pass them to the macro untyped. Even if some of the parameters do have type annotations, they will currently be ignored. This -is something we plan on improving: [SI-6971](https://issues.scala-lang.org/browse/SI-6971). Since arguments aren't typechecked, you -also won't having implicits resolved and type arguments inferred (however, you can do both with `c.typeCheck` and `c.inferImplicitValue`). - -Explicitly provided type arguments will be passed to the macro as is. If type arguments aren't provided, they will be inferred as much as -possible without typechecking the value arguments and passed to the macro in that state. Note that type arguments still get typechecked, but -this restriction might also be lifted in the future: [SI-6972](https://issues.scala-lang.org/browse/SI-6972). - -If a def macro has untyped return type, then the first of the two typechecks employed after its expansion will be omitted. A refresher: -the first typecheck of a def macro expansion is performed against the return type of its definitions, the second typecheck is performed -against the expected type of the expandee. More information can be found at Stack Overflow: [Static return type of Scala macros](http://stackoverflow.com/questions/13669974/static-return-type-of-scala-macros). Type macros never underwent the first typecheck, so -nothing changes for them (and you won't be able to specify any return type for a type macro to begin with). - -Finally the untyped macros patch enables using `c.Tree` instead of `c.Expr[T]` everywhere in signatures of macro implementations. -Both for parameters and return types, all four combinations of untyped/typed in macro def and tree/expr in macro impl are supported. -Check our unit tests for more information: [test/files/run/macro-untyped-conformance](https://github.com/scalamacros/kepler/blob/b55bda4860a205c88e9ae27015cf2d6563cc241d/test/files/run/macro-untyped-conformance/Impls_Macros_1.scala). diff --git a/overviews/macros/usecases.md b/overviews/macros/usecases.md deleted file mode 100644 index b67d8eb055..0000000000 --- a/overviews/macros/usecases.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -layout: overview-large -title: Use Cases - -discourse: true - -partof: macros -num: 1 -outof: 13 -languages: [ja] ---- - -EXPERIMENTAL - -**Eugene Burmako** - -Since their release as an experimental feature of Scala 2.10, macros have brought previously impossible or prohibitively complex things -to the realm of possible. Both commercial and research users of Scala use macros to bring their ideas to life. -At EPFL we are leveraging macros to power our research. Lightbend also employs macros in a number of projects. -Macros are also popular in the community and have already given rise to a number of interesting applications. - -The recent talk ["What Are Macros Good For?"](http://scalamacros.org/paperstalks/2013-07-17-WhatAreMacrosGoodFor.pdf) -describes and systemizes uses that macros found among Scala 2.10 users. The thesis of the talk is that macros are good for -code generation, static checking and DSLs, illustrated with a number of examples from research and industry. - -We have also published a paper in the Scala'13 workshop, -["Scala Macros: Let Our Powers Combine!"](http://scalamacros.org/paperstalks/2013-04-22-LetOurPowersCombine.pdf), -covering the state of the art of macrology in Scala 2.10 from a more academic point of view. -In the paper we show how the rich syntax and static types of Scala synergize with macros and -explore how macros enable new and unique ways to use pre-existing language features. diff --git a/overviews/parallel-collections/architecture.md b/overviews/parallel-collections/architecture.md deleted file mode 100644 index 0232d3401a..0000000000 --- a/overviews/parallel-collections/architecture.md +++ /dev/null @@ -1,116 +0,0 @@ ---- -layout: overview-large -title: Architecture of the Parallel Collections Library - -discourse: true - -partof: parallel-collections -languages: [ja, zh-cn, es, ru] -num: 5 ---- - -Like the normal, sequential collections library, Scala's parallel collections -library contains a large number of collection operations which exist uniformly -on many different parallel collection implementations. And like the sequential -collections library, Scala's parallel collections library seeks to prevent -code duplication by likewise implementing most operations in terms of parallel -collection "templates" which need only be defined once and can be flexibly -inherited by many different parallel collection implementations. - -The benefits of this approach are greatly eased **maintenance** and -**extensibility**. In the case of maintenance-- by having a single -implementation of a parallel collections operation inherited by all parallel -collections, maintenance becomes easier and more robust; bug fixes propagate -down the class hierarchy, rather than needing implementations to be -duplicated. For the same reasons, the entire library becomes easier to -extend-- new collection classes can simply inherit most of their operations. - -## Core Abstractions - -The aforementioned "template" traits implement most parallel operations in -terms of two core abstractions-- `Splitter`s and `Combiner`s. - -### Splitters - -The job of a `Splitter`, as its name suggests, is to split a parallel -collection into a non-trival partition of its elements. The basic idea is to -split the collection into smaller parts until they are small enough to be -operated on sequentially. - - trait Splitter[T] extends Iterator[T] { - def split: Seq[Splitter[T]] - } - -Interestingly, `Splitter`s are implemented as `Iterator`s, meaning that apart -from splitting, they are also used by the framework to traverse a parallel -collection (that is, they inherit standard methods on `Iterator`s such as -`next` and `hasNext`.) What's unique about this "splitting iterator" is that, -its `split` method splits `this` (again, a `Splitter`, a type of `Iterator`) -further into additional `Splitter`s which each traverse over **disjoint** -subsets of elements of the whole parallel collection. And similar to normal -`Iterator`s, a `Splitter` is invalidated after its `split` method is invoked. - -In general, collections are partitioned using `Splitter`s into subsets of -roughly the same size. In cases where more arbitrarily-sized partions are -required, in particular on parallel sequences, a `PreciseSplitter` is used, -which inherits `Splitter` and additionally implements a precise split method, -`psplit`. - -### Combiners - -`Combiner`s can be thought of as a generalized `Builder`, from Scala's sequential -collections library. Each parallel collection provides a separate `Combiner`, -in the same way that each sequential collection provides a `Builder`. - -While in the case of sequential collections, elements can be added to a -`Builder`, and a collection can be produced by invoking the `result` method, -in the case of parallel collections, a `Combiner` has a method called -`combine` which takes another `Combiner` and produces a new `Combiner` that -contains the union of both's elements. After `combine` has been invoked, both -`Combiner`s become invalidated. - - trait Combiner[Elem, To] extends Builder[Elem, To] { - def combine(other: Combiner[Elem, To]): Combiner[Elem, To] - } - -The two type parameters `Elem` and `To` above simply denote the element type -and the type of the resulting collection, respectively. - -_Note:_ Given two `Combiner`s, `c1` and `c2` where `c1 eq c2` is `true` -(meaning they're the same `Combiner`), invoking `c1.combine(c2)` always does -nothing and simpy returns the receiving `Combiner`, `c1`. - -## Hierarchy - -Scala's parallel collection's draws much inspiration from the design of -Scala's (sequential) collections library-- as a matter of fact, it mirrors the -regular collections framework's corresponding traits, as shown below. - -[]({{ site.baseurl }}/resources/images/parallel-collections-hierarchy.png) - -
        Hierarchy of Scala's Collections and Parallel Collections Libraries
        -
        - -The goal is of course to integrate parallel collections as tightly as possible -with sequential collections, so as to allow for straightforward substitution -of sequential and parallel collections. - -In order to be able to have a reference to a collection which may be either -sequential or parallel (such that it's possible to "toggle" between a parallel -collection and a sequential collection by invoking `par` and `seq`, -respectively), there has to exist a common supertype of both collection types. -This is the origin of the "general" traits shown above, `GenTraversable`, -`GenIterable`, `GenSeq`, `GenMap` and `GenSet`, which don't guarantee in-order -or one-at-a-time traversal. Corresponding sequential or parallel traits -inherit from these. For example, a `ParSeq` and `Seq` are both subtypes of a -general sequence `GenSeq`, but they are in no inheritance relationship with -respect to each other. - -For a more detailed discussion of hierarchy shared between sequential and -parallel collections, see the technical report. \[[1][1]\] - -## References - -1. [On a Generic Parallel Collection Framework, Aleksandar Prokopec, Phil Bawgell, Tiark Rompf, Martin Odersky, June 2011][1] - -[1]: http://infoscience.epfl.ch/record/165523/files/techrep.pdf "flawed-benchmark" diff --git a/overviews/parallel-collections/concrete-parallel-collections.md b/overviews/parallel-collections/concrete-parallel-collections.md deleted file mode 100644 index 9d1917a8e6..0000000000 --- a/overviews/parallel-collections/concrete-parallel-collections.md +++ /dev/null @@ -1,270 +0,0 @@ ---- -layout: overview-large -title: Concrete Parallel Collection Classes - -discourse: true - -partof: parallel-collections -languages: [ja, zh-cn, es, ru] -num: 2 ---- - -## Parallel Array - -A [ParArray](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/parallel/mutable/ParArray.html) -sequence holds a linear, -contiguous array of elements. This means that the elements can be accessed and -updated efficiently by modifying the underlying array. Traversing the -elements is also very efficient for this reason. Parallel arrays are like -arrays in the sense that their size is constant. - - scala> val pa = scala.collection.parallel.mutable.ParArray.tabulate(1000)(x => 2 * x + 1) - pa: scala.collection.parallel.mutable.ParArray[Int] = ParArray(1, 3, 5, 7, 9, 11, 13,... - - scala> pa reduce (_ + _) - res0: Int = 1000000 - - scala> pa map (x => (x - 1) / 2) - res1: scala.collection.parallel.mutable.ParArray[Int] = ParArray(0, 1, 2, 3, 4, 5, 6, 7,... - -Internally, splitting a parallel array -[splitter]({{ site.baseurl }}/overviews/parallel-collections/architecture.html#core_abstractions) -amounts to creating two new splitters with their iteration indices updated. -[Combiners]({{ site.baseurl }}/overviews/parallel-collections/architecture.html#core_abstractions) -are slightly more involved.Since for most transformer methods (e.g. `flatMap`, `filter`, `takeWhile`, -etc.) we don't know the number of elements (and hence, the array size) in -advance, each combiner is essentially a variant of an array buffer with an -amortized constant time `+=` operation. Different processors add elements to -separate parallel array combiners, which are then combined by chaining their -internal arrays. The underlying array is only allocated and filled in parallel -after the total number of elements becomes known. For this reason, transformer -methods are slightly more expensive than accessor methods. Also, note that the -final array allocation proceeds sequentially on the JVM, so this can prove to -be a sequential bottleneck if the mapping operation itself is very cheap. - -By calling the `seq` method, parallel arrays are converted to `ArraySeq` -collections, which are their sequential counterparts. This conversion is -efficient, and the `ArraySeq` is backed by the same underlying array as the -parallel array it was obtained from. - - -## Parallel Vector - -A [ParVector](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/parallel/immutable/ParVector.html) -is an immutable sequence with a low-constant factor logarithmic access and -update time. - - scala> val pv = scala.collection.parallel.immutable.ParVector.tabulate(1000)(x => x) - pv: scala.collection.parallel.immutable.ParVector[Int] = ParVector(0, 1, 2, 3, 4, 5, 6, 7, 8, 9,... - - scala> pv filter (_ % 2 == 0) - res0: scala.collection.parallel.immutable.ParVector[Int] = ParVector(0, 2, 4, 6, 8, 10, 12, 14, 16, 18,... - -Immutable vectors are represented by 32-way trees, so -[splitter]({{ site.baseurl }}/overviews/parallel-collections/architecture.html#core_abstractions)s -are split by assigning subtrees to each splitter. -[Combiners]({{ site.baseurl }}/overviews/parallel-collections/architecture.html#core_abstractions) -currently keep a vector of -elements and are combined by lazily copying the elements. For this reason, -transformer methods are less scalable than those of a parallel array. Once the -vector concatenation operation becomes available in a future Scala release, -combiners will be combined using concatenation and transformer methods will -become much more efficient. - -Parallel vector is a parallel counterpart of the sequential -[Vector](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Vector.html), -so conversion between the two takes constant time. - - -## Parallel Range - -A [ParRange](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/parallel/immutable/ParRange.html) -is an ordered sequence of elements equally spaced apart. A parallel range is -created in a similar way as the sequential -[Range](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Range.html): - - scala> 1 to 3 par - res0: scala.collection.parallel.immutable.ParRange = ParRange(1, 2, 3) - - scala> 15 to 5 by -2 par - res1: scala.collection.parallel.immutable.ParRange = ParRange(15, 13, 11, 9, 7, 5) - -Just as sequential ranges have no builders, parallel ranges have no -[combiner]({{ site.baseurl }}/overviews/parallel-collections/architecture.html#core_abstractions)s. -Mapping the elements of a parallel range produces a parallel vector. -Sequential ranges and parallel ranges can be converted efficiently one from -another using the `seq` and `par` methods. - - -## Parallel Hash Tables - -Parallel hash tables store their elements in an underlying array and place -them in the position determined by the hash code of the respective element. -Parallel mutable hash sets -([mutable.ParHashSet](http://www.scala-lang.org/api/{{ site.scala-version}}/scala/collection/parallel/mutable/ParHashSet.html)) -and parallel mutable hash maps -([mutable.ParHashMap](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/parallel/mutable/ParHashMap.html)) -are based on hash tables. - - scala> val phs = scala.collection.parallel.mutable.ParHashSet(1 until 2000: _*) - phs: scala.collection.parallel.mutable.ParHashSet[Int] = ParHashSet(18, 327, 736, 1045, 773, 1082,... - - scala> phs map (x => x * x) - res0: scala.collection.parallel.mutable.ParHashSet[Int] = ParHashSet(2181529, 2446096, 99225, 2585664,... - -Parallel hash table combiners sort elements into buckets according to their -hashcode prefix. They are combined by simply concatenating these buckets -together. Once the final hash table is to be constructed (i.e. combiner -`result` method is called), the underlying array is allocated and the elements -from different buckets are copied in parallel to different contiguous segments -of the hash table array. - -Sequential hash maps and hash sets can be converted to their parallel variants -using the `par` method. Parallel hash tables internally require a size map -which tracks the number of elements in different chunks of the hash table. -What this means is that the first time that a sequential hash table is -converted into a parallel hash table, the table is traversed and the size map -is created - for this reason, the first call to `par` takes time linear in the -size of the hash table. Further modifications to the hash table maintain the -state of the size map, so subsequent conversions using `par` and `seq` have -constant complexity. Maintenance of the size map can be turned on and off -using the `useSizeMap` method of the hash table. Importantly, modifications in -the sequential hash table are visible in the parallel hash table, and vice -versa. - - -## Parallel Hash Tries - -Parallel hash tries are a parallel counterpart of the immutable hash tries, -which are used to represent immutable sets and maps efficiently. They are -supported by classes -[immutable.ParHashSet](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/parallel/immutable/ParHashSet.html) -and -[immutable.ParHashMap](http://www.scala-lang.org/api/{{ site.scala-version}}/scala/collection/parallel/immutable/ParHashMap.html). - - scala> val phs = scala.collection.parallel.immutable.ParHashSet(1 until 1000: _*) - phs: scala.collection.parallel.immutable.ParHashSet[Int] = ParSet(645, 892, 69, 809, 629, 365, 138, 760, 101, 479,... - - scala> phs map { x => x * x } sum - res0: Int = 332833500 - -Similar to parallel hash tables, parallel hash trie -[combiners]({{ site.baseurl }}/overviews/parallel-collections/architecture.html#core_abstractions) -pre-sort the -elements into buckets and construct the resulting hash trie in parallel by -assigning different buckets to different processors, which construct the -subtries independently. - -Parallel hash tries can be converted back and forth to sequential hash tries -by using the `seq` and `par` method in constant time. - - -## Parallel Concurrent Tries - -A [concurrent.TrieMap](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/concurrent/TrieMap.html) -is a concurrent thread-safe map, whereas a -[mutable.ParTrieMap](http://www.scala-lang.org/api/{{ site.scala-version}}/scala/collection/parallel/mutable/ParTrieMap.html) -is its parallel counterpart. While most concurrent data structures do not guarantee -consistent traversal if the data structure is modified during traversal, -Ctries guarantee that updates are only visible in the next iteration. This -means that you can mutate the concurrent trie while traversing it, like in the -following example which outputs square roots of number from 1 to 99: - - scala> val numbers = scala.collection.parallel.mutable.ParTrieMap((1 until 100) zip (1 until 100): _*) map { case (k, v) => (k.toDouble, v.toDouble) } - numbers: scala.collection.parallel.mutable.ParTrieMap[Double,Double] = ParTrieMap(0.0 -> 0.0, 42.0 -> 42.0, 70.0 -> 70.0, 2.0 -> 2.0,... - - scala> while (numbers.nonEmpty) { - | numbers foreach { case (num, sqrt) => - | val nsqrt = 0.5 * (sqrt + num / sqrt) - | numbers(num) = nsqrt - | if (math.abs(nsqrt - sqrt) < 0.01) { - | println(num, nsqrt) - | numbers.remove(num) - | } - | } - | } - (1.0,1.0) - (2.0,1.4142156862745097) - (7.0,2.64576704419029) - (4.0,2.0000000929222947) - ... - - -[Combiners]({{ site.baseurl }}/overviews/parallel-collections/architecture.html#core_abstractions) -are implemented as `TrieMap`s under the hood-- since this is a -concurrent data structure, only one combiner is constructed for the entire -transformer method invocation and shared by all the processors. - -As with all parallel mutable collections, `TrieMap`s and parallel `ParTrieMap`s obtained -by calling `seq` or `par` methods are backed by the same store, so -modifications in one are visible in the other. Conversions happen in constant -time. - - -## Performance characteristics - -Performance characteristics of sequence types: - -| | head | tail | apply | update| prepend | append | insert | -| -------- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | -| `ParArray` | C | L | C | C | L | L | L | -| `ParVector` | eC | eC | eC | eC | eC | eC | - | -| `ParRange` | C | C | C | - | - | - | - | - -Performance characteristics of set and map types: - -| | lookup | add | remove | -| -------- | ---- | ---- | ---- | -| **immutable** | | | | -| `ParHashSet`/`ParHashMap`| eC | eC | eC | -| **mutable** | | | | -| `ParHashSet`/`ParHashMap`| C | C | C | -| `ParTrieMap` | eC | eC | eC | - - -### Key - -The entries in the above two tables are explained as follows: - -| | | -| --- | ---- | -| **C** | The operation takes (fast) constant time. | -| **eC** | The operation takes effectively constant time, but this might depend on some assumptions such as maximum length of a vector or distribution of hash keys.| -| **aC** | The operation takes amortized constant time. Some invocations of the operation might take longer, but if many operations are performed on average only constant time per operation is taken. | -| **Log** | The operation takes time proportional to the logarithm of the collection size. | -| **L** | The operation is linear, that is it takes time proportional to the collection size. | -| **-** | The operation is not supported. | - -The first table treats sequence types--both immutable and mutable--with the following operations: - -| | | -| --- | ---- | -| **head** | Selecting the first element of the sequence. | -| **tail** | Producing a new sequence that consists of all elements except the first one. | -| **apply** | Indexing. | -| **update** | Functional update (with `updated`) for immutable sequences, side-effecting update (with `update` for mutable sequences. | -| **prepend**| Adding an element to the front of the sequence. For immutable sequences, this produces a new sequence. For mutable sequences it modified the existing sequence. | -| **append** | Adding an element and the end of the sequence. For immutable sequences, this produces a new sequence. For mutable sequences it modified the existing sequence. | -| **insert** | Inserting an element at an arbitrary position in the sequence. This is only supported directly for mutable sequences. | - -The second table treats mutable and immutable sets and maps with the following operations: - -| | | -| --- | ---- | -| **lookup** | Testing whether an element is contained in set, or selecting a value associated with a key. | -| **add** | Adding a new element to a set or key/value pair to a map. | -| **remove** | Removing an element from a set or a key from a map. | -| **min** | The smallest element of the set, or the smallest key of a map. | - - - - - - - - - - - - - diff --git a/overviews/parallel-collections/configuration.md b/overviews/parallel-collections/configuration.md deleted file mode 100644 index 858179ab3c..0000000000 --- a/overviews/parallel-collections/configuration.md +++ /dev/null @@ -1,82 +0,0 @@ ---- -layout: overview-large -title: Configuring Parallel Collections - -discourse: true - -partof: parallel-collections -languages: [ja, zh-cn, es, ru] -num: 7 ---- - -## Task support - -Parallel collections are modular in the way operations are scheduled. Each -parallel collection is parametrized with a task support object which is -responsible for scheduling and load-balancing tasks to processors. - -The task support object internally keeps a reference to a thread pool -implementation and decides how and when tasks are split into smaller tasks. To -learn more about the internals of how exactly this is done, see the tech -report \[[1][1]\]. - -There are currently a few task support implementations available for parallel -collections. The `ForkJoinTaskSupport` uses a fork-join pool internally and is -used by default on JVM 1.6 or greater. The less efficient -`ThreadPoolTaskSupport` is a fallback for JVM 1.5 and JVMs that do not support -the fork join pools. The `ExecutionContextTaskSupport` uses the default -execution context implementation found in `scala.concurrent`, and it reuses -the thread pool used in `scala.concurrent` (this is either a fork join pool or -a thread pool executor, depending on the JVM version). The execution context -task support is set to each parallel collection by default, so parallel -collections reuse the same fork-join pool as the future API. - -Here is a way to change the task support of a parallel collection: - - scala> import scala.collection.parallel._ - import scala.collection.parallel._ - - scala> val pc = mutable.ParArray(1, 2, 3) - pc: scala.collection.parallel.mutable.ParArray[Int] = ParArray(1, 2, 3) - - scala> pc.tasksupport = new ForkJoinTaskSupport(new scala.concurrent.forkjoin.ForkJoinPool(2)) - pc.tasksupport: scala.collection.parallel.TaskSupport = scala.collection.parallel.ForkJoinTaskSupport@4a5d484a - - scala> pc map { _ + 1 } - res0: scala.collection.parallel.mutable.ParArray[Int] = ParArray(2, 3, 4) - -The above sets the parallel collection to use a fork-join pool with -parallelism level 2. To set the parallel collection to use a thread pool -executor: - - scala> pc.tasksupport = new ThreadPoolTaskSupport() - pc.tasksupport: scala.collection.parallel.TaskSupport = scala.collection.parallel.ThreadPoolTaskSupport@1d914a39 - - scala> pc map { _ + 1 } - res1: scala.collection.parallel.mutable.ParArray[Int] = ParArray(2, 3, 4) - -When a parallel collection is serialized, the task support field is omitted -from serialization. When deserializing a parallel collection, the task support -field is set to the default value-- the execution context task support. - -To implement a custom task support, extend the `TaskSupport` trait and -implement the following methods: - - def execute[R, Tp](task: Task[R, Tp]): () => R - - def executeAndWaitResult[R, Tp](task: Task[R, Tp]): R - - def parallelismLevel: Int - -The `execute` method schedules a task asynchronously and returns a future to -wait on the result of the computation. The `executeAndWait` method does the -same, but only returns when the task is completed. The `parallelismLevel` -simply returns the targeted number of cores that the task support uses to -schedule tasks. - - -## References - -1. [On a Generic Parallel Collection Framework, June 2011][1] - - [1]: http://infoscience.epfl.ch/record/165523/files/techrep.pdf "parallel-collections" diff --git a/overviews/parallel-collections/conversions.md b/overviews/parallel-collections/conversions.md deleted file mode 100644 index e211ece23e..0000000000 --- a/overviews/parallel-collections/conversions.md +++ /dev/null @@ -1,80 +0,0 @@ ---- -layout: overview-large -title: Parallel Collection Conversions - -discourse: true - -partof: parallel-collections -languages: [ja, zh-cn, es, ru] -num: 3 ---- - -## Converting between sequential and parallel collections - -Every sequential collection can be converted to its parallel variant -using the `par` method. Certain sequential collections have a -direct parallel counterpart. For these collections the conversion is -efficient-- it occurs in constant time, since both the sequential and -the parallel collection have the same data-structural representation -(one exception is mutable hash maps and hash sets which are slightly -more expensive to convert the first time `par` is called, but -subsequent invocations of `par` take constant time). It should be -noted that for mutable collections, changes in the sequential collection are -visible in its parallel counterpart if they share the underlying data-structure. - -| Sequential | Parallel | -| ------------- | -------------- | -| **mutable** | | -| `Array` | `ParArray` | -| `HashMap` | `ParHashMap` | -| `HashSet` | `ParHashSet` | -| `TrieMap` | `ParTrieMap` | -| **immutable** | | -| `Vector` | `ParVector` | -| `Range` | `ParRange` | -| `HashMap` | `ParHashMap` | -| `HashSet` | `ParHashSet` | - -Other collections, such as lists, queues or streams, are inherently sequential -in the sense that the elements must be accessed one after the other. These -collections are converted to their parallel variants by copying the elements -into a similar parallel collection. For example, a functional list is -converted into a standard immutable parallel sequence, which is a parallel -vector. - -Every parallel collection can be converted to its sequential variant -using the `seq` method. Converting a parallel collection to a -sequential collection is always efficient-- it takes constant -time. Calling `seq` on a mutable parallel collection yields a -sequential collection which is backed by the same store-- updates to -one collection will be visible in the other one. - - -## Converting between different collection types - -Orthogonal to converting between sequential and parallel collections, -collections can be converted between different collection types. For -example, while calling `toSeq` converts a sequential set to a -sequential sequence, calling `toSeq` on a parallel set converts it to -a parallel sequence. The general rule is that if there is a -parallel version of `X`, then the `toX` method converts the collection -into a `ParX` collection. - -Here is a summary of all conversion methods: - -| Method | Return Type | -| -------------- | -------------- | -| `toArray` | `Array` | -| `toList` | `List` | -| `toIndexedSeq` | `IndexedSeq` | -| `toStream` | `Stream` | -| `toIterator` | `Iterator` | -| `toBuffer` | `Buffer` | -| `toTraversable`| `GenTraverable`| -| `toIterable` | `ParIterable` | -| `toSeq` | `ParSeq` | -| `toSet` | `ParSet` | -| `toMap` | `ParMap` | - - - diff --git a/overviews/parallel-collections/ctries.md b/overviews/parallel-collections/ctries.md deleted file mode 100644 index 8407293800..0000000000 --- a/overviews/parallel-collections/ctries.md +++ /dev/null @@ -1,174 +0,0 @@ ---- -layout: overview-large -title: Concurrent Tries - -discourse: true - -partof: parallel-collections -languages: [ja, zh-cn, es, ru] -num: 4 ---- - -Most concurrent data structures do not guarantee consistent -traversal if the data structure is modified during traversal. -This is, in fact, the case with most mutable collections, too. -Concurrent tries are special in the sense that they allow you to modify -the trie being traversed itself. The modifications are only visible in the -subsequent traversal. This holds both for sequential concurrent tries and their -parallel counterparts. The only difference between the two is that the -former traverses its elements sequentially, whereas the latter does so in -parallel. - -This is a nice property that allows you to write some algorithms more -easily. Typically, these are algorithms that process a dataset of elements -iteratively, in which different elements need a different number of -iterations to be processed. - -The following example computes the square roots of a set of numbers. Each iteration -iteratively updates the square root value. Numbers whose square roots converged -are removed from the map. - - case class Entry(num: Double) { - var sqrt = num - } - - val length = 50000 - - // prepare the list - val entries = (1 until length) map { num => Entry(num.toDouble) } - val results = ParTrieMap() - for (e <- entries) results += ((e.num, e)) - - // compute square roots - while (results.nonEmpty) { - for ((num, e) <- results) { - val nsqrt = 0.5 * (e.sqrt + e.num / e.sqrt) - if (math.abs(nsqrt - e.sqrt) < 0.01) { - results.remove(num) - } else e.sqrt = nsqrt - } - } - -Note that in the above Babylonian method square root computation -(\[[3][3]\]) some numbers may converge much faster than the others. For -this reason, we want to remove them from `results` so that only those -elements that need to be worked on are traversed. - -Another example is the breadth-first search algorithm, which iteratively expands the nodefront -until either it finds some path to the target or there are no more -nodes to expand. We define a node on a 2D map as a tuple of -`Int`s. We define the `map` as a 2D array of booleans which denote is -the respective slot occupied or not. We then declare 2 concurrent trie -maps-- `open` which contains all the nodes which have to be -expanded (the nodefront), and `closed` which contains all the nodes which have already -been expanded. We want to start the search from the corners of the map and -find a path to the center of the map-- we initialize the `open` map -with appropriate nodes. Then we iteratively expand all the nodes in -the `open` map in parallel until there are no more nodes to expand. -Each time a node is expanded, it is removed from the `open` map and -placed in the `closed` map. -Once done, we output the path from the target to the initial node. - - val length = 1000 - - // define the Node type - type Node = (Int, Int); - type Parent = (Int, Int); - - // operations on the Node type - def up(n: Node) = (n._1, n._2 - 1); - def down(n: Node) = (n._1, n._2 + 1); - def left(n: Node) = (n._1 - 1, n._2); - def right(n: Node) = (n._1 + 1, n._2); - - // create a map and a target - val target = (length / 2, length / 2); - val map = Array.tabulate(length, length)((x, y) => (x % 3) != 0 || (y % 3) != 0 || (x, y) == target) - def onMap(n: Node) = n._1 >= 0 && n._1 < length && n._2 >= 0 && n._2 < length - - // open list - the nodefront - // closed list - nodes already processed - val open = ParTrieMap[Node, Parent]() - val closed = ParTrieMap[Node, Parent]() - - // add a couple of starting positions - open((0, 0)) = null - open((length - 1, length - 1)) = null - open((0, length - 1)) = null - open((length - 1, 0)) = null - - // greedy bfs path search - while (open.nonEmpty && !open.contains(target)) { - for ((node, parent) <- open) { - def expand(next: Node) { - if (onMap(next) && map(next._1)(next._2) && !closed.contains(next) && !open.contains(next)) { - open(next) = node - } - } - expand(up(node)) - expand(down(node)) - expand(left(node)) - expand(right(node)) - closed(node) = parent - open.remove(node) - } - } - - // print path - var pathnode = open(target) - while (closed.contains(pathnode)) { - print(pathnode + "->") - pathnode = closed(pathnode) - } - println() - -There is a Game of Life example on GitHub which uses Ctries to -selectively simulate only those parts of the Game of Life automaton which -are currently active \[[4][4]\]. -It also includes a Swing-based visualization of the Game of Life simulation, -in which you can observe how tweaking the parameters affects performance. - -The concurrent tries also support a linearizable, lock-free, constant -time `snapshot` operation. This operation creates a new concurrent -trie with all the elements at a specific point in time, thus in effect -capturing the state of the trie at a specific point. -The `snapshot` operation merely creates -a new root for the concurrent trie. Subsequent updates lazily rebuild the part of -the concurrent trie relevant to the update and leave the rest of the concurrent trie -intact. First of all, this means that the snapshot operation by itself is not expensive -since it does not copy the elements. Second, since the copy-on-write optimization copies -only parts of the concurrent trie, subsequent modifications scale horizontally. -The `readOnlySnapshot` method is slightly more efficient than the -`snapshot` method, but returns a read-only map which cannot be -modified. Concurrent tries also support a linearizable, constant-time -`clear` operation based on the snapshot mechanism. -To learn more about how concurrent tries and snapshots work, see \[[1][1]\] and \[[2][2]\]. - -The iterators for concurrent tries are based on snapshots. Before the iterator -object gets created, a snapshot of the concurrent trie is taken, so the iterator -only traverses the elements in the trie at the time at which the snapshot was created. -Naturally, the iterators use the read-only snapshot. - -The `size` operation is also based on the snapshot. A straightforward implementation, the `size` -call would just create an iterator (i.e. a snapshot) and traverse the elements to count them. -Every call to `size` would thus require time linear in the number of elements. However, concurrent -tries have been optimized to cache sizes of their different parts, thus reducing the complexity -of the `size` method to amortized logarithmic time. In effect, this means that after calling -`size` once, subsequent calls to `size` will require a minimum amount of work, typically recomputing -the size only for those branches of the trie which have been modified since the last `size` call. -Additionally, size computation for parallel concurrent tries is performed in parallel. - - - - -## References - -1. [Cache-Aware Lock-Free Concurrent Hash Tries][1] -2. [Concurrent Tries with Efficient Non-Blocking Snapshots][2] -3. [Methods of computing square roots][3] -4. [Game of Life simulation][4] - - [1]: http://infoscience.epfl.ch/record/166908/files/ctries-techreport.pdf "Ctries-techreport" - [2]: http://lampwww.epfl.ch/~prokopec/ctries-snapshot.pdf "Ctries-snapshot" - [3]: http://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method "babylonian-method" - [4]: https://github.com/axel22/ScalaDays2012-TrieMap "game-of-life-ctries" diff --git a/overviews/parallel-collections/custom-parallel-collections.md b/overviews/parallel-collections/custom-parallel-collections.md deleted file mode 100644 index 4dc67cdcc4..0000000000 --- a/overviews/parallel-collections/custom-parallel-collections.md +++ /dev/null @@ -1,332 +0,0 @@ ---- -layout: overview-large -title: Creating Custom Parallel Collections - -discourse: true - -partof: parallel-collections -languages: [ja, zh-cn, es, ru] -num: 6 ---- - -## Parallel collections without combiners - -Just as it is possible to define custom sequential collections without -defining their builders, it is possible to define parallel collections without -defining their combiners. The consequence of not having a combiner is that -transformer methods (e.g. `map`, `flatMap`, `collect`, `filter`, ...) will by -default return a standard collection type which is nearest in the hierarchy. -For example, ranges do not have builders, so mapping elements of a range -creates a vector. - -In the following example we define a parallel string collection. Since strings -are logically immutable sequences, we have parallel strings inherit -`immutable.ParSeq[Char]`: - - class ParString(val str: String) - extends immutable.ParSeq[Char] { - -Next, we define methods found in every immutable sequence: - - def apply(i: Int) = str.charAt(i) - - def length = str.length - -We have to also define the sequential counterpart of this parallel collection. -In this case, we return the `WrappedString` class: - - def seq = new collection.immutable.WrappedString(str) - -Finally, we have to define a splitter for our parallel string collection. We -name the splitter `ParStringSplitter` and have it inherit a sequence splitter, -that is, `SeqSplitter[Char]`: - - def splitter = new ParStringSplitter(str, 0, str.length) - - class ParStringSplitter(private var s: String, private var i: Int, private val ntl: Int) - extends SeqSplitter[Char] { - - final def hasNext = i < ntl - - final def next = { - val r = s.charAt(i) - i += 1 - r - } - -Above, `ntl` represents the total length of the string, `i` is the current -position and `s` is the string itself. - -Parallel collection iterators or splitters require a few more methods in -addition to `next` and `hasNext` found in sequential collection iterators. -First of all, they have a method called `remaining` which returns the number -of elements this splitter has yet to traverse. Next, they have a method called -`dup` which duplicates the current splitter. - - def remaining = ntl - i - - def dup = new ParStringSplitter(s, i, ntl) - -Finally, methods `split` and `psplit` are used to create splitters which -traverse subsets of the elements of the current splitter. Method `split` has -the contract that it returns a sequence of splitters which traverse disjoint, -non-overlapping subsets of elements that the current splitter traverses, none -of which is empty. If the current splitter has 1 or less elements, then -`split` just returns a sequence of this splitter. Method `psplit` has to -return a sequence of splitters which traverse exactly as many elements as -specified by the `sizes` parameter. If the `sizes` parameter specifies less -elements than the current splitter, then an additional splitter with the rest -of the elements is appended at the end. If the `sizes` parameter requires more -elements than there are remaining in the current splitter, it will append an -empty splitter for each size. Finally, calling either `split` or `psplit` -invalidates the current splitter. - - def split = { - val rem = remaining - if (rem >= 2) psplit(rem / 2, rem - rem / 2) - else Seq(this) - } - - def psplit(sizes: Int*): Seq[ParStringSplitter] = { - val splitted = new ArrayBuffer[ParStringSplitter] - for (sz <- sizes) { - val next = (i + sz) min ntl - splitted += new ParStringSplitter(s, i, next) - i = next - } - if (remaining > 0) splitted += new ParStringSplitter(s, i, ntl) - splitted - } - } - } - -Above, `split` is implemented in terms of `psplit`, which is often the case -with parallel sequences. Implementing a splitter for parallel maps, sets or -iterables is often easier, since it does not require `psplit`. - -Thus, we obtain a parallel string class. The only downside is that calling transformer methods -such as `filter` will not produce a parallel string, but a parallel vector instead, which -may be suboptimal - producing a string again from the vector after filtering may be costly. - - -## Parallel collections with combiners - -Lets say we want to `filter` the characters of the parallel string, to get rid -of commas for example. As noted above, calling `filter` produces a parallel -vector and we want to obtain a parallel string (since some interface in the -API might require a sequential string). - -To avoid this, we have to write a combiner for the parallel string collection. -We will also inherit the `ParSeqLike` trait this time to ensure that return -type of `filter` is more specific - a `ParString` instead of a `ParSeq[Char]`. -The `ParSeqLike` has a third type parameter which specifies the type of the -sequential counterpart of the parallel collection (unlike sequential `*Like` -traits which have only two type parameters). - - class ParString(val str: String) - extends immutable.ParSeq[Char] - with ParSeqLike[Char, ParString, collection.immutable.WrappedString] - -All the methods remain the same as before, but we add an additional protected method `newCombiner` which -is internally used by `filter`. - - protected[this] override def newCombiner: Combiner[Char, ParString] = new ParStringCombiner - -Next we define the `ParStringCombiner` class. Combiners are subtypes of -builders and they introduce an additional method called `combine`, which takes -another combiner as an argument and returns a new combiner which contains the -elements of both the current and the argument combiner. The current and the -argument combiner are invalidated after calling `combine`. If the argument is -the same object as the current combiner, then `combine` just returns the -current combiner. This method is expected to be efficient, having logarithmic -running time with respect to the number of elements in the worst case, since -it is called multiple times during a parallel computation. - -Our `ParStringCombiner` will internally maintain a sequence of string -builders. It will implement `+=` by adding an element to the last string -builder in the sequence, and `combine` by concatenating the lists of string -builders of the current and the argument combiner. The `result` method, which -is called at the end of the parallel computation, will produce a parallel -string by appending all the string builders together. This way, elements are -copied only once at the end instead of being copied every time `combine` is -called. Ideally, we would like to parallelize this process and copy them in -parallel (this is being done for parallel arrays), but without tapping into -the internal representation of strings this is the best we can do-- we have to -live with this sequential bottleneck. - - private class ParStringCombiner extends Combiner[Char, ParString] { - var sz = 0 - val chunks = new ArrayBuffer[StringBuilder] += new StringBuilder - var lastc = chunks.last - - def size: Int = sz - - def +=(elem: Char): this.type = { - lastc += elem - sz += 1 - this - } - - def clear = { - chunks.clear - chunks += new StringBuilder - lastc = chunks.last - sz = 0 - } - - def result: ParString = { - val rsb = new StringBuilder - for (sb <- chunks) rsb.append(sb) - new ParString(rsb.toString) - } - - def combine[U <: Char, NewTo >: ParString](other: Combiner[U, NewTo]) = if (other eq this) this else { - val that = other.asInstanceOf[ParStringCombiner] - sz += that.sz - chunks ++= that.chunks - lastc = chunks.last - this - } - } - - -## How do I implement my combiner in general? - -There are no predefined recipes-- it depends on the data-structure at -hand, and usually requires a bit of ingenuity on the implementer's -part. However there are a few approaches usually taken: - -1. Concatenation and merge. Some data-structures have efficient -implementations (usually logarithmic) of these operations. -If the collection at hand is backed by such a data-structure, -its combiner can be the collection itself. Finger trees, -ropes and various heaps are particularly suitable for such an approach. - -2. Two-phase evaluation. An approach taken in parallel arrays and -parallel hash tables, it assumes the elements can be efficiently -partially sorted into concatenable buckets from which the final -data-structure can be constructed in parallel. In the first phase -different processors populate these buckets independently and -concatenate the buckets together. In the second phase, the data -structure is allocated and different processors populate different -parts of the data structure in parallel using elements from disjoint -buckets. -Care must be taken that different processors never modify the same -part of the data structure, otherwise subtle concurrency errors may occur. -This approach is easily applicable to random access sequences, as we -have shown in the previous section. - -3. A concurrent data-structure. While the last two approaches actually -do not require any synchronization primitives in the data-structure -itself, they assume that it can be constructed concurrently in a way -such that two different processors never modify the same memory -location. There exists a large number of concurrent data-structures -that can be modified safely by multiple processors-- concurrent skip lists, -concurrent hash tables, split-ordered lists, concurrent avl trees, to -name a few. -An important consideration in this case is that the concurrent -data-structure has a horizontally scalable insertion method. -For concurrent parallel collections the combiner can be the collection -itself, and a single combiner instance is shared between all the -processors performing a parallel operation. - - -## Integration with the collections framework - -Our `ParString` class is not complete yet. Although we have implemented a -custom combiner which will be used by methods such as `filter`, `partition`, -`takeWhile` or `span`, most transformer methods require an implicit -`CanBuildFrom` evidence (see Scala collections guide for a full explanation). -To make it available and completely integrate `ParString` with the collections -framework, we have to mix an additional trait called `GenericParTemplate` and -define the companion object of `ParString`. - - class ParString(val str: String) - extends immutable.ParSeq[Char] - with GenericParTemplate[Char, ParString] - with ParSeqLike[Char, ParString, collection.immutable.WrappedString] { - - def companion = ParString - -Inside the companion object we provide an implicit evidence for the `CanBuildFrom` parameter. - - object ParString { - implicit def canBuildFrom: CanCombineFrom[ParString, Char, ParString] = - new CanCombinerFrom[ParString, Char, ParString] { - def apply(from: ParString) = newCombiner - def apply() = newCombiner - } - - def newBuilder: Combiner[Char, ParString] = newCombiner - - def newCombiner: Combiner[Char, ParString] = new ParStringCombiner - - def apply(elems: Char*): ParString = { - val cb = newCombiner - cb ++= elems - cb.result - } - } - - - -## Further customizations-- concurrent and other collections - -Implementing a concurrent collection (unlike parallel collections, concurrent -collections are ones that can be concurrently modified, like -`collection.concurrent.TrieMap`) is not always straightforward. Combiners in -particular often require a lot of thought. In most _parallel_ collections -described so far, combiners use a two-step evaluation. In the first step the -elements are added to the combiners by different processors and the combiners -are merged together. In the second step, after all the elements are available, -the resulting collection is constructed. - -Another approach to combiners is to construct the resulting collection as the -elements. This requires the collection to be thread-safe-- a combiner must -allow _concurrent_ element insertion. In this case one combiner is shared by -all the processors. - -To parallelize a concurrent collection, its combiners must override the method -`canBeShared` to return `true`. This will ensure that only one combiner is -created when a parallel operation is invoked. Next, the `+=` method must be -thread-safe. Finally, method `combine` still returns the current combiner if -the current combiner and the argument combiner are the same, and is free to -throw an exception otherwise. - -Splitters are divided into smaller splitters to achieve better load balancing. -By default, information returned by the `remaining` method is used to decide -when to stop dividing the splitter. For some collections, calling the -`remaining` method may be costly and some other means should be used to decide -when to divide the splitter. In this case, one should override the -`shouldSplitFurther` method in the splitter. - -The default implementation divides the splitter if the number of remaining -elements is greater than the collection size divided by eight times the -parallelism level. - - def shouldSplitFurther[S](coll: ParIterable[S], parallelismLevel: Int) = - remaining > thresholdFromSize(coll.size, parallelismLevel) - -Equivalently, a splitter can hold a counter on how many times it was split and -implement `shouldSplitFurther` by returning `true` if the split count is -greater than `3 + log(parallelismLevel)`. This avoids having to call -`remaining`. - -Furthermore, if calling `remaining` is not a cheap operation for a particular -collection (i.e. it requires evaluating the number of elements in the -collection), then the method `isRemainingCheap` in splitters should be -overridden to return `false`. - -Finally, if the `remaining` method in splitters is extremely cumbersome to -implement, you can override the method `isStrictSplitterCollection` in its -collection to return `false`. Such collections will fail to execute some -methods which rely on splitters being strict, i.e. returning a correct value -in the `remaining` method. Importantly, this does not effect methods used in -for-comprehensions. - - - - - - - diff --git a/overviews/parallel-collections/overview.md b/overviews/parallel-collections/overview.md deleted file mode 100644 index 100f9fd929..0000000000 --- a/overviews/parallel-collections/overview.md +++ /dev/null @@ -1,274 +0,0 @@ ---- -layout: overview-large -title: Overview - -discourse: true - -partof: parallel-collections -languages: [ja, zh-cn, es, ru] -num: 1 ---- - -**Aleksandar Prokopec, Heather Miller** - -## Motivation - -Amidst the shift in recent years by processor manufacturers from single to -multi-core architectures, academia and industry alike have conceded that -_Popular Parallel Programming_ remains a formidable challenge. - -Parallel collections were included in the Scala standard library in an effort -to facilitate parallel programming by sparing users from low-level -parallelization details, meanwhile providing them with a familiar and simple -high-level abstraction. The hope was, and still is, that implicit parallelism -behind a collections abstraction will bring reliable parallel execution one -step closer to the workflow of mainstream developers. - -The idea is simple-- collections are a well-understood and frequently-used -programming abstraction. And given their regularity, they're able to be -efficiently parallelized, transparently. By allowing a user to "swap out" -sequential collections for ones that are operated on in parallel, Scala's -parallel collections take a large step forward in enabling parallelism to be -easily brought into more code. - -Take the following, sequential example, where we perform a monadic operation -on some large collection: - - val list = (1 to 10000).toList - list.map(_ + 42) - -To perform the same operation in parallel, one must simply invoke the `par` -method on the sequential collection, `list`. After that, one can use a -parallel collection in the same way one would normally use a sequential -collection. The above example can be parallelized by simply doing the -following: - - list.par.map(_ + 42) - -The design of Scala's parallel collections library is inspired by and deeply -integrated with Scala's (sequential) collections library (introduced in 2.8). -It provides a parallel counterpart to a number of important data structures -from Scala's (sequential) collection library, including: - -* `ParArray` -* `ParVector` -* `mutable.ParHashMap` -* `mutable.ParHashSet` -* `immutable.ParHashMap` -* `immutable.ParHashSet` -* `ParRange` -* `ParTrieMap` (`collection.concurrent.TrieMap`s are new in 2.10) - -In addition to a common architecture, Scala's parallel collections library -additionally shares _extensibility_ with the sequential collections library. -That is, like normal sequential collections, users can integrate their own -collection types and automatically inherit all of the predefined (parallel) -operations available on the other parallel collections in the standard -library. - -## Some Examples - -To attempt to illustrate the generality and utility of parallel collections, -we provide a handful of simple example usages, all of which are transparently -executed in parallel. - -_Note:_ Some of the following examples operate on small collections, which -isn't recommended. They're provided as examples for illustrative purposes -only. As a general heuristic, speed-ups tend to be noticeable when the size of -the collection is large, typically several thousand elements. (For more -information on the relationship between the size of a parallel collection and -performance, please see the -[appropriate subsection]({{ site.baseurl}}/overviews/parallel-collections/performance.html#how_big_should_a_collection_be_to_go_parallel) of the [performance]({{ site.baseurl }}/overviews/parallel-collections/performance.html) -section of this guide.) - -#### map - -Using a parallel `map` to transform a collection of `String` to all-uppercase: - - scala> val lastNames = List("Smith","Jones","Frankenstein","Bach","Jackson","Rodin").par - lastNames: scala.collection.parallel.immutable.ParSeq[String] = ParVector(Smith, Jones, Frankenstein, Bach, Jackson, Rodin) - - scala> lastNames.map(_.toUpperCase) - res0: scala.collection.parallel.immutable.ParSeq[String] = ParVector(SMITH, JONES, FRANKENSTEIN, BACH, JACKSON, RODIN) - -#### fold - -Summing via `fold` on a `ParArray`: - - scala> val parArray = (1 to 10000).toArray.par - parArray: scala.collection.parallel.mutable.ParArray[Int] = ParArray(1, 2, 3, ... - - scala> parArray.fold(0)(_ + _) - res0: Int = 50005000 - -#### filter - -Using a parallel `filter` to select the last names that come alphabetically -after the letter "I". - - scala> val lastNames = List("Smith","Jones","Frankenstein","Bach","Jackson","Rodin").par - lastNames: scala.collection.parallel.immutable.ParSeq[String] = ParVector(Smith, Jones, Frankenstein, Bach, Jackson, Rodin) - - scala> lastNames.filter(_.head >= 'J') - res0: scala.collection.parallel.immutable.ParSeq[String] = ParVector(Smith, Jones, Jackson, Rodin) - -## Creating a Parallel Collection - -Parallel collections are meant to be used in exactly the same way as -sequential collections-- the only noteworthy difference is how to _obtain_ a -parallel collection. - -Generally, one has two choices for creating a parallel collection: - -First, by using the `new` keyword and a proper import statement: - - import scala.collection.parallel.immutable.ParVector - val pv = new ParVector[Int] - -Second, by _converting_ from a sequential collection: - - val pv = Vector(1,2,3,4,5,6,7,8,9).par - -What's important to expand upon here are these conversion methods-- sequential -collections can be converted to parallel collections by invoking the -sequential collection's `par` method, and likewise, parallel collections can -be converted to sequential collections by invoking the parallel collection's -`seq` method. - -_Of Note:_ Collections that are inherently sequential (in the sense that the -elements must be accessed one after the other), like lists, queues, and -streams, are converted to their parallel counterparts by copying the elements -into a similar parallel collection. An example is `List`-- it's converted into -a standard immutable parallel sequence, which is a `ParVector`. Of course, the -copying required for these collection types introduces an overhead not -incurred by any other collection types, like `Array`, `Vector`, `HashMap`, etc. - -For more information on conversions on parallel collections, see the -[conversions]({{ site.baseurl }}/overviews/parallel-collections/conversions.html) -and [concrete parallel collection classes]({{ site.baseurl }}/overviews/parallel-collections/concrete-parallel-collections.html) -sections of this guide. - -## Semantics - -While the parallel collections abstraction feels very much the same as normal -sequential collections, it's important to note that its semantics differs, -especially with regards to side-effects and non-associative operations. - -In order to see how this is the case, first, we visualize _how_ operations are -performed in parallel. Conceptually, Scala's parallel collections framework -parallelizes an operation on a parallel collection by recursively "splitting" -a given collection, applying an operation on each partition of the collection -in parallel, and re-"combining" all of the results that were completed in -parallel. - -These concurrent, and "out-of-order" semantics of parallel collections lead to -the following two implications: - -1. **Side-effecting operations can lead to non-determinism** -2. **Non-associative operations lead to non-determinism** - -### Side-Effecting Operations - -Given the _concurrent_ execution semantics of the parallel collections -framework, operations performed on a collection which cause side-effects -should generally be avoided, in order to maintain determinism. A simple -example is by using an accessor method, like `foreach` to increment a `var` -declared outside of the closure which is passed to `foreach`. - - scala> var sum = 0 - sum: Int = 0 - - scala> val list = (1 to 1000).toList.par - list: scala.collection.parallel.immutable.ParSeq[Int] = ParVector(1, 2, 3,… - - scala> list.foreach(sum += _); sum - res01: Int = 467766 - - scala> var sum = 0 - sum: Int = 0 - - scala> list.foreach(sum += _); sum - res02: Int = 457073 - - scala> var sum = 0 - sum: Int = 0 - - scala> list.foreach(sum += _); sum - res03: Int = 468520 - -Here, we can see that each time `sum` is reinitialized to 0, and `foreach` is -called again on `list`, `sum` holds a different value. The source of this -non-determinism is a _data race_-- concurrent reads/writes to the same mutable -variable. - -In the above example, it's possible for two threads to read the _same_ value -in `sum`, to spend some time doing some operation on that value of `sum`, and -then to attempt to write a new value to `sum`, potentially resulting in an -overwrite (and thus, loss) of a valuable result, as illustrated below: - - ThreadA: read value in sum, sum = 0 value in sum: 0 - ThreadB: read value in sum, sum = 0 value in sum: 0 - ThreadA: increment sum by 760, write sum = 760 value in sum: 760 - ThreadB: increment sum by 12, write sum = 12 value in sum: 12 - -The above example illustrates a scenario where two threads read the same -value, `0`, before one or the other can sum `0` with an element from their -partition of the parallel collection. In this case, `ThreadA` reads `0` and -sums it with its element, `0+760`, and in the case of `ThreadB`, sums `0` with -its element, `0+12`. After computing their respective sums, they each write -their computed value in `sum`. Since `ThreadA` beats `ThreadB`, it writes -first, only for the value in `sum` to be overwritten shortly after by -`ThreadB`, in effect completely overwriting (and thus losing) the value `760`. - -### Non-Associative Operations - -Given this _"out-of-order"_ semantics, also must be careful to perform only -associative operations in order to avoid non-determinism. That is, given a -parallel collection, `pcoll`, one should be sure that when invoking a -higher-order function on `pcoll`, such as `pcoll.reduce(func)`, the order in -which `func` is applied to the elements of `pcoll` can be arbitrary. A simple, -but obvious example is a non-associative operation such as subtraction: - - scala> val list = (1 to 1000).toList.par - list: scala.collection.parallel.immutable.ParSeq[Int] = ParVector(1, 2, 3,… - - scala> list.reduce(_-_) - res01: Int = -228888 - - scala> list.reduce(_-_) - res02: Int = -61000 - - scala> list.reduce(_-_) - res03: Int = -331818 - -In the above example, we take a `ParVector[Int]`, invoke `reduce`, and pass to -it `_-_`, which simply takes two unnamed elements, and subtracts the first -from the second. Due to the fact that the parallel collections framework spawns -threads which, in effect, independently perform `reduce(_-_)` on different -sections of the collection, the result of two runs of `reduce(_-_)` on the -same collection will not be the same. - -_Note:_ Often, it is thought that, like non-associative operations, non-commutative -operations passed to a higher-order function on a parallel -collection likewise result in non-deterministic behavior. This is not the -case, a simple example is string concatenation-- an associative, but non- -commutative operation: - - scala> val strings = List("abc","def","ghi","jk","lmnop","qrs","tuv","wx","yz").par - strings: scala.collection.parallel.immutable.ParSeq[java.lang.String] = ParVector(abc, def, ghi, jk, lmnop, qrs, tuv, wx, yz) - - scala> val alphabet = strings.reduce(_++_) - alphabet: java.lang.String = abcdefghijklmnopqrstuvwxyz - -The _"out of order"_ semantics of parallel collections only means that -the operation will be executed out of order (in a _temporal_ sense. That is, -non-sequentially), it does not mean that the result will be -re-"*combined*" out of order (in a _spatial_ sense). On the contrary, results -will generally always be reassembled _in order_-- that is, a parallel collection -broken into partitions A, B, C, in that order, will be reassembled once again -in the order A, B, C. Not some other arbitrary order like B, C, A. - -For more on how parallel collections split and combine operations on different -parallel collection types, see the [Architecture]({{ site.baseurl }}/overviews -/parallel-collections/architecture.html) section of this guide. - diff --git a/overviews/parallel-collections/performance.md b/overviews/parallel-collections/performance.md deleted file mode 100644 index 42228ba3f4..0000000000 --- a/overviews/parallel-collections/performance.md +++ /dev/null @@ -1,284 +0,0 @@ ---- -layout: overview-large -title: Measuring Performance - -discourse: true - -partof: parallel-collections -num: 8 -outof: 8 -languages: [ja, zh-cn, es, ru] ---- - -## Performance on the JVM - -The performance model on the JVM is sometimes convoluted in commentaries about -it, and as a result is not well understood. For various reasons, some code may -not be as performant or as scalable as expected. Here, we provide a few -examples. - -One of the reasons is that the compilation process for a JVM application is -not the same as that of a statically compiled language (see \[[2][2]\]). The -Java and Scala compilers convert source code into JVM bytecode and do very -little optimization. On most modern JVMs, once the program bytecode is run, it -is converted into machine code for the computer architecture on which it is -being run. This is called the just-in-time compilation. The level of code -optimization is, however, low with just-in-time compilation, since it has to -be fast. To avoid recompiling, the so called HotSpot compiler only optimizes -parts of the code which are executed frequently. What this means for the -benchmark writer is that a program might have different performance each time -it is run. Executing the same piece of code (e.g. a method) multiple times in -the same JVM instance might give very different performance results depending -on whether the particular code was optimized in between the runs. -Additionally, measuring the execution time of some piece of code may include -the time during which the JIT compiler itself was performing the optimization, -thus giving inconsistent results. - -Another hidden execution that takes part on the JVM is the automatic memory -management. Every once in a while, the execution of the program is stopped and -a garbage collector is run. If the program being benchmarked allocates any -heap memory at all (and most JVM programs do), the garbage collector will have -to run, thus possibly distorting the measurement. To amortize the garbage -collection effects, the measured program should run many times to trigger many -garbage collections. - -One common cause of a performance deterioration is also boxing and unboxing -that happens implicitly when passing a primitive type as an argument to a -generic method. At runtime, primitive types are converted to objects which -represent them, so that they could be passed to a method with a generic type -parameter. This induces extra allocations and is slower, also producing -additional garbage on the heap. - -Where parallel performance is concerned, one common issue is memory -contention, as the programmer does not have explicit control about where the -objects are allocated. -In fact, due to GC effects, contention can occur at a later stage in -the application lifetime after objects get moved around in memory. -Such effects need to be taken into consideration when writing a benchmark. - - -## Microbenchmarking example - -There are several approaches to avoid the above effects during measurement. -First of all, the target microbenchmark must be executed enough times to make -sure that the just-in-time compiler compiled it to machine code and that it -was optimized. This is known as the warm-up phase. - -The microbenchmark itself should be run in a separate JVM instance to reduce -noise coming from garbage collection of the objects allocated by different -parts of the program or unrelated just-in-time compilation. - -It should be run using the server version of the HotSpot JVM, which does more -aggressive optimizations. - -Finally, to reduce the chance of a garbage collection occurring in the middle -of the benchmark, ideally a garbage collection cycle should occur prior to the -run of the benchmark, postponing the next cycle as far as possible. - -The `scala.testing.Benchmark` trait is predefined in the Scala standard -library and is designed with above in mind. Here is an example of benchmarking -a map operation on a concurrent trie: - - import collection.parallel.mutable.ParTrieMap - import collection.parallel.ForkJoinTaskSupport - - object Map extends testing.Benchmark { - val length = sys.props("length").toInt - val par = sys.props("par").toInt - val partrie = ParTrieMap((0 until length) zip (0 until length): _*) - - partrie.tasksupport = new ForkJoinTaskSupport(new scala.concurrent.forkjoin.ForkJoinPool(par)) - - def run = { - partrie map { - kv => kv - } - } - } - -The `run` method embodies the microbenchmark code which will be run -repetitively and whose running time will be measured. The object `Map` above -extends the `scala.testing.Benchmark` trait and parses system specified -parameters `par` for the parallelism level and `length` for the number of -elements in the trie. - -After compiling the program above, run it like this: - - java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=1 -Dlength=300000 Map 10 - -The `server` flag specifies that the server VM should be used. The `cp` -specifies the classpath and includes classfiles in the current directory and -the scala library jar. Arguments `-Dpar` and `-Dlength` are the parallelism -level and the number of elements. Finally, `10` means that the benchmark -should be run that many times within the same JVM. - -Running times obtained by setting the `par` to `1`, `2`, `4` and `8` on a -quad-core i7 with hyperthreading: - - Map$ 126 57 56 57 54 54 54 53 53 53 - Map$ 90 99 28 28 26 26 26 26 26 26 - Map$ 201 17 17 16 15 15 16 14 18 15 - Map$ 182 12 13 17 16 14 14 12 12 12 - -We can see above that the running time is higher during the initial runs, but -is reduced after the code gets optimized. Further, we can see that the benefit -of hyperthreading is not high in this example, as going from `4` to `8` -threads results only in a minor performance improvement. - - -## How big should a collection be to go parallel? - -This is a question commonly asked. The answer is somewhat involved. - -The size of the collection at which the parallelization pays of really -depends on many factors. Some of them, but not all, include: - -- Machine architecture. Different CPU types have different - performance and scalability characteristics. Orthogonal to that, - whether the machine is multicore or has multiple processors - communicating via motherboard. -- JVM vendor and version. Different VMs apply different - optimizations to the code at runtime. They implement different memory - management and synchronization techniques. Some do not support - `ForkJoinPool`, reverting to `ThreadPoolExecutor`s, resulting in - more overhead. -- Per-element workload. A function or a predicate for a parallel - operation determines how big is the per-element workload. The - smaller the workload, the higher the number of elements needed to - gain speedups when running in parallel. -- Specific collection. For example, `ParArray` and - `ParTrieMap` have splitters that traverse the collection at - different speeds, meaning there is more per-element work in just the - traversal itself. -- Specific operation. For example, `ParVector` is a lot slower for - transformer methods (like `filter`) than it is for accessor methods (like `foreach`) -- Side-effects. When modifying memory areas concurrently or using - synchronization within the body of `foreach`, `map`, etc., - contention can occur. -- Memory management. When allocating a lot of objects a garbage - collection cycle can be triggered. Depending on how the references - to new objects are passed around, the GC cycle can take more or less time. - -Even in separation, it is not easy to reason about things above and -give a precise answer to what the collection size should be. To -roughly illustrate what the size should be, we give an example of -a cheap side-effect-free parallel vector reduce (in this case, sum) -operation performance on an i7 quad-core processor (not using -hyperthreading) on JDK7: - - import collection.parallel.immutable.ParVector - - object Reduce extends testing.Benchmark { - val length = sys.props("length").toInt - val par = sys.props("par").toInt - val parvector = ParVector((0 until length): _*) - - parvector.tasksupport = new collection.parallel.ForkJoinTaskSupport(new scala.concurrent.forkjoin.ForkJoinPool(par)) - - def run = { - parvector reduce { - (a, b) => a + b - } - } - } - - object ReduceSeq extends testing.Benchmark { - val length = sys.props("length").toInt - val vector = collection.immutable.Vector((0 until length): _*) - - def run = { - vector reduce { - (a, b) => a + b - } - } - } - -We first run the benchmark with `250000` elements and obtain the -following results, for `1`, `2` and `4` threads: - - java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=1 -Dlength=250000 Reduce 10 10 - Reduce$ 54 24 18 18 18 19 19 18 19 19 - java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=2 -Dlength=250000 Reduce 10 10 - Reduce$ 60 19 17 13 13 13 13 14 12 13 - java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=4 -Dlength=250000 Reduce 10 10 - Reduce$ 62 17 15 14 13 11 11 11 11 9 - -We then decrease the number of elements down to `120000` and use `4` -threads to compare the time to that of a sequential vector reduce: - - java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=4 -Dlength=120000 Reduce 10 10 - Reduce$ 54 10 8 8 8 7 8 7 6 5 - java -server -cp .:../../build/pack/lib/scala-library.jar -Dlength=120000 ReduceSeq 10 10 - ReduceSeq$ 31 7 8 8 7 7 7 8 7 8 - -`120000` elements seems to be the around the threshold in this case. - -As another example, we take the `mutable.ParHashMap` and the `map` -method (a transformer method) and run the following benchmark in the same environment: - - import collection.parallel.mutable.ParHashMap - - object Map extends testing.Benchmark { - val length = sys.props("length").toInt - val par = sys.props("par").toInt - val phm = ParHashMap((0 until length) zip (0 until length): _*) - - phm.tasksupport = new collection.parallel.ForkJoinTaskSupport(new scala.concurrent.forkjoin.ForkJoinPool(par)) - - def run = { - phm map { - kv => kv - } - } - } - - object MapSeq extends testing.Benchmark { - val length = sys.props("length").toInt - val hm = collection.mutable.HashMap((0 until length) zip (0 until length): _*) - - def run = { - hm map { - kv => kv - } - } - } - -For `120000` elements we get the following times when ranging the -number of threads from `1` to `4`: - - java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=1 -Dlength=120000 Map 10 10 - Map$ 187 108 97 96 96 95 95 95 96 95 - java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=2 -Dlength=120000 Map 10 10 - Map$ 138 68 57 56 57 56 56 55 54 55 - java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=4 -Dlength=120000 Map 10 10 - Map$ 124 54 42 40 38 41 40 40 39 39 - -Now, if we reduce the number of elements to `15000` and compare that -to the sequential hashmap: - - java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=1 -Dlength=15000 Map 10 10 - Map$ 41 13 10 10 10 9 9 9 10 9 - java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=2 -Dlength=15000 Map 10 10 - Map$ 48 15 9 8 7 7 6 7 8 6 - java -server -cp .:../../build/pack/lib/scala-library.jar -Dlength=15000 MapSeq 10 10 - MapSeq$ 39 9 9 9 8 9 9 9 9 9 - -For this collection and this operation it makes sense -to go parallel when there are above `15000` elements (in general, -it is feasible to parallelize hashmaps and hashsets with fewer -elements than would be required for arrays or vectors). - - - - - -## References - -1. [Anatomy of a flawed microbenchmark, Brian Goetz][1] -2. [Dynamic compilation and performance measurement, Brian Goetz][2] - - [1]: http://www.ibm.com/developerworks/java/library/j-jtp02225/index.html "flawed-benchmark" - [2]: http://www.ibm.com/developerworks/library/j-jtp12214/ "dynamic-compilation" - - - diff --git a/overviews/quasiquotes/future.md b/overviews/quasiquotes/future.md deleted file mode 100644 index 34013b26b6..0000000000 --- a/overviews/quasiquotes/future.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -layout: overview-large -title: Future prospects - -discourse: true - -partof: quasiquotes -num: 13 -outof: 13 ---- -**Denys Shabalin** EXPERIMENTAL - -In the [scala.meta](http://scalameta.org) project we are working on the following features that target future versions of Scala: - -* Hygiene: [SI-7823](https://issues.scala-lang.org/browse/SI-7823) -* Alternative to Scheme's ellipsis: [SI-8164](https://issues.scala-lang.org/browse/SI-8164) -* Safety by construction diff --git a/overviews/quasiquotes/usecases.md b/overviews/quasiquotes/usecases.md deleted file mode 100644 index ec78a3d06c..0000000000 --- a/overviews/quasiquotes/usecases.md +++ /dev/null @@ -1,91 +0,0 @@ ---- -layout: overview-large -title: Use cases - -discourse: true - -partof: quasiquotes -num: 6 -outof: 13 ---- -**Denys Shabalin** EXPERIMENTAL - -## AST manipulation in macros and compiler plugins - -Quasiquotes were designed primary as tool for ast manipulation in macros. A common workflow is to deconstruct arguments with quasiquote patterns and then construct a rewritten result with another quasiquote: - - // macro that prints the expression code before executing it - object debug { - def apply[T](x: => T): T = macro impl - def impl(c: Context)(x: c.Tree) = { import c.universe._ - val q"..$stats" = x - val loggedStats = stats.flatMap { stat => - val msg = "executing " + showCode(stat) - List(q"println($msg)", stat) - } - q"..$loggedStats" - } - } - - // usage - object Test extends App { - def faulty: Int = throw new Exception - debug { - val x = 1 - val y = x + faulty - x + y - } - } - - // output - executing val x: Int = 1 - executing val y: Int = x.+(Test.this.faulty) - java.lang.Exception - ... - -To simplify integration with macros we've also made it easier to simply use trees in macro implementations instead of the reify-centric `Expr` api that might be used previously: - - // 2.10 - object Macro { - def apply(x: Int): Int = macro impl - def impl(c: Context)(x: c.Expr[Int]): c.Expr[Int] = { import c.universe._ - c.Expr(q"$x + 1") - } - } - - // in 2.11 you can also do it like that - object Macro { - def apply(x: Int): Int = macro impl - def impl(c: Context)(x: c.Tree) = { import c.universe._ - q"$x + 1" - } - } - -You no longer need to wrap the return value of a macro with `c.Expr`, or to specify the argument types twice, and the return type in `impl` is now optional. - -Quasiquotes can also be used "as is" in compiler plugins as the reflection API is strict subset of the compiler's `Global` API. - -## Just in time compilation - -Thanks to the `ToolBox` API, one can generate, compile and run Scala code at runtime: - - scala> val code = q"""println("compiled and run at runtime!")""" - scala> val compiledCode = toolbox.compile(code) - scala> val result = compiledCode() - compiled and run at runtime! - result: Any = () - -## Offline code generation - -Thanks to the new `showCode` "pretty printer" one can implement an offline code generator that does AST manipulation with the help of quasiquotes, and then serializes that into actual source code right before writing it to disk: - - object OfflineCodeGen extends App { - def generateCode() = - q"package mypackage { class MyClass }" - def saveToFile(path: String, code: Tree) = { - val writer = new java.io.PrintWriter(path) - try writer.write(showCode(code)) - finally writer.close() - } - saveToFile("myfile.scala", generateCode()) - } diff --git a/overviews/reflection/annotations-names-scopes.md b/overviews/reflection/annotations-names-scopes.md deleted file mode 100644 index da668607a0..0000000000 --- a/overviews/reflection/annotations-names-scopes.md +++ /dev/null @@ -1,448 +0,0 @@ ---- -layout: overview-large -title: Annotations, Names, Scopes, and More - -discourse: true - -partof: reflection -num: 4 -outof: 7 -languages: [ja] ---- - -EXPERIMENTAL - -## Annotations - -In Scala, declarations can be annotated using subtypes of -`scala.annotation.Annotation`. Furthermore, since Scala integrates with -[Java's annotation system](http://docs.oracle.com/javase/7/docs/technotes/guides/language/annotations.html#_top), -it's possible to work with annotations produced by a -standard Java compiler. - -Annotations can be inspected reflectively if the corresponding annotations -have been persisted, so that they can be read from the classfile containing -the annotated declarations. A custom annotation type can be made persistent by -inheriting from `scala.annotation.StaticAnnotation` or -`scala.annotation.ClassfileAnnotation`. As a result, instances of the -annotation type are stored as special attributes in the corresponding -classfile. Note that subclassing just -`scala.annotation.Annotation` is not enough to have the corresponding metadata -persisted for runtime reflection. Moreover, subclassing -`scala.annotation.ClassfileAnnotation` does not make your annotation visible -as a Java annotation at runtime; that requires writing the annotation class -in Java. - -The API distinguishes between two kinds of annotations: - -- *Java annotations:* annotations on definitions produced by the Java compiler, _i.e.,_ subtypes of `java.lang.annotation.Annotation` attached to program definitions. When read by Scala reflection, the `scala.annotation.ClassfileAnnotation` trait is automatically added as a subclass to every Java annotation. -- *Scala annotations:* annotations on definitions or types produced by the Scala compiler. - -The distinction between Java and Scala annotations is manifested in the -contract of `scala.reflect.api.Annotations#Annotation`, which exposes both -`scalaArgs` and `javaArgs`. For Scala or Java annotations extending -`scala.annotation.ClassfileAnnotation` `scalaArgs` is empty and the arguments -(if any) are stored in `javaArgs`. For all other Scala annotations, the -arguments are stored in `scalaArgs` and `javaArgs` is empty. - -Arguments in `scalaArgs` are represented as typed trees. Note that these trees -are not transformed by any phases following the type-checker. Arguments in -`javaArgs` are represented as a map from `scala.reflect.api.Names#Name` to -`scala.reflect.api.Annotations#JavaArgument`. Instances of `JavaArgument` -represent different kinds of Java annotation arguments: - -- literals (primitive and string constants), -- arrays, and -- nested annotations. - -## Names - -Names are simple wrappers for strings. -[Name](http://www.scala-lang.org/api/current/scala-reflect/index.html#scala.reflect.api.Names$NameApi) -has two subtypes `TermName` and `TypeName` which distinguish names of terms (like -objects or members) and types (like classes, traits, and type members). A term -and a type of the same name can co-exist in the same object. In other words, -types and terms have separate name spaces. - -Names are associated with a universe. Example: - - scala> import scala.reflect.runtime.universe._ - import scala.reflect.runtime.universe._ - - scala> val mapName = TermName("map") - mapName: scala.reflect.runtime.universe.TermName = map - -Above, we're creating a `Name` associated with the runtime reflection universe -(this is also visible in its path-dependent type -`reflect.runtime.universe.TermName`). - -Names are often used to look up members of types. For example, to search for -the `map` method (which is a term) declared in the `List` class, one can do: - - scala> val listTpe = typeOf[List[Int]] - listTpe: scala.reflect.runtime.universe.Type = scala.List[Int] - - scala> listTpe.member(mapName) - res1: scala.reflect.runtime.universe.Symbol = method map - -To search for a type member, one can follow the same procedure, using -`TypeName` instead. It is also possible to rely on implicit conversions to -convert between strings and term or type names: - - scala> listTpe.member("map": TermName) - res2: scala.reflect.runtime.universe.Symbol = method map - -### Standard Names - -Certain names, such as "`_root_`", have special meanings in Scala programs. As -such they are essential for reflectively accessing certain Scala constructs. -For example, reflectively invoking a constructor requires using the -*standard name* `universe.nme.CONSTRUCTOR`, the term name `` which represents the -constructor name on the JVM. - -There are both - -- *standard term names,* _e.g.,_ "``", "`package`", and "`_root_`", and -- *standard type names,* _e.g.,_ "``", "`_`", and "`_*`". - -Some names, such as "package", exist both as a type name and a term name. -Standard names are made available through the `nme` and `tpnme` members of -class `Universe`. For a complete specification of all standard names, see the -[API documentation](http://www.scala-lang.org/api/current/scala-reflect/index.html#scala.reflect.api.StandardNames). - -## Scopes - -A scope object generally maps names to symbols available in a corresponding -lexical scope. Scopes can be nested. The base type exposed in the reflection -API, however, only exposes a minimal interface, representing a scope as an -iterable of [Symbol](http://www.scala-lang.org/api/current/scala-reflect/index.html#scala.reflect.api.Symbols$Symbol)s. - -Additional functionality is exposed in *member scopes* that are returned by -`members` and `declarations` defined in -[scala.reflect.api.Types#TypeApi](http://www.scala-lang.org/api/current/scala-reflect/index.html#scala.reflect.api.Types$TypeApi). -[scala.reflect.api.Scopes#MemberScope](http://www.scala-lang.org/api/current/scala-reflect/index.html#scala.reflect.api.Scopes$MemberScope) -supports the `sorted` method, which sorts members *in declaration order*. - -The following example returns a list of the symbols of all overridden members -of the `List` class, in declaration order: - - scala> val overridden = listTpe.declarations.sorted.filter(_.isOverride) - overridden: List[scala.reflect.runtime.universe.Symbol] = List(method companion, method ++, method +:, method toList, method take, method drop, method slice, method takeRight, method splitAt, method takeWhile, method dropWhile, method span, method reverse, method stringPrefix, method toStream, method foreach) - -## Exprs - -In addition to type `scala.reflect.api.Trees#Tree`, the base type of abstract -syntax trees, typed trees can also be represented as instances of type -[`scala.reflect.api.Exprs#Expr`](http://www.scala-lang.org/api/current/scala-reflect/index.html#scala.reflect.api.Exprs$Expr). -An `Expr` wraps -an abstract syntax tree and an internal type tag to provide access to the type -of the tree. `Expr`s are mainly used to simply and conveniently create typed -abstract syntax trees for use in a macro. In most cases, this involves methods -`reify` and `splice` (see the -[macros guide](http://docs.scala-lang.org/overviews/macros/overview.html) for details). - -## Flags and flag sets - -Flags are used to provide modifiers for abstract syntax trees that represent -definitions via the `flags` field of `scala.reflect.api.Trees#Modifiers`. -Trees that accept modifiers are: - -- `scala.reflect.api.Trees#ClassDef`. Classes and traits. -- `scala.reflect.api.Trees#ModuleDef`. Objects. -- `scala.reflect.api.Trees#ValDef`. Vals, vars, parameters, and self type annotations. -- `scala.reflect.api.Trees#DefDef`. Methods and constructors. -- `scala.reflect.api.Trees#TypeDef`. Type aliases, abstract type members and type parameters. - -For example, to create a class named `C` one would write something like: - - ClassDef(Modifiers(NoFlags), TypeName("C"), Nil, ...) - -Here, the flag set is empty. To make `C` private, one would write something -like: - - ClassDef(Modifiers(PRIVATE), TypeName("C"), Nil, ...) - -Flags can also be combined with the vertical bar operator (`|`). For example, -a private final class is written something like: - - ClassDef(Modifiers(PRIVATE | FINAL), TypeName("C"), Nil, ...) - -The list of all available flags is defined in -`scala.reflect.api.FlagSets#FlagValues`, available via -`scala.reflect.api.FlagSets#Flag`. (Typically, one uses a wildcard import for -this, _e.g.,_ `import scala.reflect.runtime.universe.Flag._`.) - -Definition trees are compiled down to symbols, so that flags on modifiers of -these trees are transformed into flags on the resulting symbols. Unlike trees, -symbols don't expose flags, but rather provide test methods following the -`isXXX` pattern (_e.g.,_ `isFinal` can be used to test finality). In some -cases, these test methods require a conversion using `asTerm`, `asType`, or -`asClass`, as some flags only make sense for certain kinds of symbols. - -*Of note:* This part of the reflection API is being considered a candidate -for redesign. It is quite possible that in future releases of the reflection -API, flag sets could be replaced with something else. - -## Constants - -Certain expressions that the Scala specification calls *constant expressions* -can be evaluated by the Scala compiler at compile time. The following kinds of -expressions are compile-time constants (see [section 6.24 of the Scala language specification](http://scala-lang.org/files/archive/spec/2.11/06-expressions.html#constant-expressions)): - -1. Literals of primitive value classes ([Byte](http://www.scala-lang.org/api/current/index.html#scala.Byte), [Short](http://www.scala-lang.org/api/current/index.html#scala.Short), [Int](http://www.scala-lang.org/api/current/index.html#scala.Int), [Long](http://www.scala-lang.org/api/current/index.html#scala.Long), [Float](http://www.scala-lang.org/api/current/index.html#scala.Float), [Double](http://www.scala-lang.org/api/current/index.html#scala.Double), [Char](http://www.scala-lang.org/api/current/index.html#scala.Char), [Boolean](http://www.scala-lang.org/api/current/index.html#scala.Boolean) and [Unit](http://www.scala-lang.org/api/current/index.html#scala.Unit)) - represented directly as the corresponding type. - -2. String literals - represented as instances of the string. - -3. References to classes, typically constructed with [scala.Predef#classOf](http://www.scala-lang.org/api/current/index.html#scala.Predef$@classOf[T]:Class[T]) - represented as [types](http://www.scala-lang.org/api/current/scala-reflect/index.html#scala.reflect.api.Types$Type). - -4. References to Java enumeration values - represented as [symbols](http://www.scala-lang.org/api/current/scala-reflect/index.html#scala.reflect.api.Symbols$Symbol). - -Constant expressions are used to represent - -- literals in abstract syntax trees (see `scala.reflect.api.Trees#Literal`), and -- literal arguments for Java class file annotations (see `scala.reflect.api.Annotations#LiteralArgument`). - -Example: - - Literal(Constant(5)) - -The above expression creates an AST representing the integer literal `5` in -Scala source code. - -`Constant` is an example of a "virtual case class", _i.e.,_ a class whose -instances can be constructed and matched against as if it were a case class. -Both types `Literal` and `LiteralArgument` have a `value` method returning the -compile-time constant underlying the literal. - -Examples: - - Constant(true) match { - case Constant(s: String) => println("A string: " + s) - case Constant(b: Boolean) => println("A Boolean value: " + b) - case Constant(x) => println("Something else: " + x) - } - assert(Constant(true).value == true) - -Class references are represented as instances of -`scala.reflect.api.Types#Type`. Such a reference can be converted to a runtime -class using the `runtimeClass` method of a `RuntimeMirror` such as -`scala.reflect.runtime.currentMirror`. (This conversion from a type to a -runtime class is necessary, because when the Scala compiler processes a class -reference, the underlying runtime class might not yet have been compiled.) - -Java enumeration value references are represented as symbols (instances of -`scala.reflect.api.Symbols#Symbol`), which on the JVM point to methods that -return the underlying enumeration values. A `RuntimeMirror` can be used to -inspect an underlying enumeration or to get the runtime value of a reference -to an enumeration. - -Example: - - // Java source: - enum JavaSimpleEnumeration { FOO, BAR } - - import java.lang.annotation.*; - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.TYPE}) - public @interface JavaSimpleAnnotation { - Class classRef(); - JavaSimpleEnumeration enumRef(); - } - - @JavaSimpleAnnotation( - classRef = JavaAnnottee.class, - enumRef = JavaSimpleEnumeration.BAR - ) - public class JavaAnnottee {} - - // Scala source: - import scala.reflect.runtime.universe._ - import scala.reflect.runtime.{currentMirror => cm} - - object Test extends App { - val jann = typeOf[JavaAnnottee].typeSymbol.annotations(0).javaArgs - - def jarg(name: String) = jann(TermName(name)) match { - // Constant is always wrapped in a Literal or LiteralArgument tree node - case LiteralArgument(ct: Constant) => value - case _ => sys.error("Not a constant") - } - - val classRef = jarg("classRef").value.asInstanceOf[Type] - println(showRaw(classRef)) // TypeRef(ThisType(), JavaAnnottee, List()) - println(cm.runtimeClass(classRef)) // class JavaAnnottee - - val enumRef = jarg("enumRef").value.asInstanceOf[Symbol] - println(enumRef) // value BAR - - val siblings = enumRef.owner.typeSignature.declarations - val enumValues = siblings.filter(sym => sym.isVal && sym.isPublic) - println(enumValues) // Scope { - // final val FOO: JavaSimpleEnumeration; - // final val BAR: JavaSimpleEnumeration - // } - - val enumClass = cm.runtimeClass(enumRef.owner.asClass) - val enumValue = enumClass.getDeclaredField(enumRef.name.toString).get(null) - println(enumValue) // BAR - } - -## Printers - -Utilities for nicely printing -[`Trees`](http://www.scala-lang.org/api/current/scala-reflect/index.html#scala.reflect.api.Trees) and -[`Types`](http://www.scala-lang.org/api/current/scala-reflect/index.html#scala.reflect.api.Types). - -### Printing Trees - -The method `show` displays the "prettified" representation of reflection -artifacts. This representation provides one with the desugared Java -representation of Scala code. For example: - - scala> import scala.reflect.runtime.universe._ - import scala.reflect.runtime.universe._ - - scala> def tree = reify { final class C { def x = 2 } }.tree - tree: scala.reflect.runtime.universe.Tree - - scala> show(tree) - res0: String = - { - final class C extends AnyRef { - def () = { - super.(); - () - }; - def x = 2 - }; - () - } - -The method `showRaw` displays the internal structure of a given reflection -object as a Scala abstract syntax tree (AST), the representation that the -Scala typechecker operates on. - -Note that while this representation appears to generate correct trees that one -might think would be possible to use in a macro implementation, this is not -usually the case. Symbols aren't fully represented (only their names are). -Thus, this method is best-suited for use simply inspecting ASTs given some -valid Scala code. - - scala> showRaw(tree) - res1: String = Block(List( - ClassDef(Modifiers(FINAL), TypeName("C"), List(), Template( - List(Ident(TypeName("AnyRef"))), - emptyValDef, - List( - DefDef(Modifiers(), nme.CONSTRUCTOR, List(), List(List()), TypeTree(), - Block(List( - Apply(Select(Super(This(tpnme.EMPTY), tpnme.EMPTY), nme.CONSTRUCTOR), List())), - Literal(Constant(())))), - DefDef(Modifiers(), TermName("x"), List(), List(), TypeTree(), - Literal(Constant(2))))))), - Literal(Constant(()))) - -The method `showRaw` can also print `scala.reflect.api.Types` next to the artifacts being inspected. - - scala> import scala.tools.reflect.ToolBox // requires scala-compiler.jar - import scala.tools.reflect.ToolBox - - scala> import scala.reflect.runtime.{currentMirror => cm} - import scala.reflect.runtime.{currentMirror=>cm} - - scala> showRaw(cm.mkToolBox().typeCheck(tree), printTypes = true) - res2: String = Block[1](List( - ClassDef[2](Modifiers(FINAL), TypeName("C"), List(), Template[3]( - List(Ident[4](TypeName("AnyRef"))), - emptyValDef, - List( - DefDef[2](Modifiers(), nme.CONSTRUCTOR, List(), List(List()), TypeTree[3](), - Block[1](List( - Apply[4](Select[5](Super[6](This[3](TypeName("C")), tpnme.EMPTY), ...))), - Literal[1](Constant(())))), - DefDef[2](Modifiers(), TermName("x"), List(), List(), TypeTree[7](), - Literal[8](Constant(2))))))), - Literal[1](Constant(()))) - [1] TypeRef(ThisType(scala), scala.Unit, List()) - [2] NoType - [3] TypeRef(NoPrefix, TypeName("C"), List()) - [4] TypeRef(ThisType(java.lang), java.lang.Object, List()) - [5] MethodType(List(), TypeRef(ThisType(java.lang), java.lang.Object, List())) - [6] SuperType(ThisType(TypeName("C")), TypeRef(... java.lang.Object ...)) - [7] TypeRef(ThisType(scala), scala.Int, List()) - [8] ConstantType(Constant(2)) - -### Printing Types - -The method `show` can be used to produce a *readable* string representation of a type: - - scala> import scala.reflect.runtime.universe._ - import scala.reflect.runtime.universe._ - - scala> def tpe = typeOf[{ def x: Int; val y: List[Int] }] - tpe: scala.reflect.runtime.universe.Type - - scala> show(tpe) - res0: String = scala.AnyRef{def x: Int; val y: scala.List[Int]} - -Like the method `showRaw` for `scala.reflect.api.Trees`, `showRaw` for -`scala.reflect.api.Types` provides a visualization of the Scala AST operated -on by the Scala typechecker. - - scala> showRaw(tpe) - res1: String = RefinedType( - List(TypeRef(ThisType(scala), TypeName("AnyRef"), List())), - Scope( - TermName("x"), - TermName("y"))) - -The `showRaw` method also has named parameters `printIds` and `printKinds`, -both with default argument `false`. When passing `true` to these, `showRaw` -additionally shows the unique identifiers of symbols, as well as their kind -(package, type, method, getter, etc.). - - scala> showRaw(tpe, printIds = true, printKinds = true) - res2: String = RefinedType( - List(TypeRef(ThisType(scala#2043#PK), TypeName("AnyRef")#691#TPE, List())), - Scope( - TermName("x")#2540#METH, - TermName("y")#2541#GET)) - -## Positions - -Positions (instances of the -[Position](http://www.scala-lang.org/api/current/scala-reflect/index.html#scala.reflect.api.Position) trait) -are used to track the origin of symbols and tree nodes. They are commonly used when -displaying warnings and errors, to indicate the incorrect point in the -program. Positions indicate a column and line in a source file (the offset -from the beginning of the source file is called its "point", which is -sometimes less convenient to use). They also carry the content of the line -they refer to. Not all trees or symbols have a position; a missing position is -indicated using the `NoPosition` object. - -Positions can refer either to only a single character in a source file, or to -a *range*. In the latter case, a *range position* is used (positions that are -not range positions are also called *offset positions*). Range positions have -in addition `start` and `end` offsets. The `start` and `end` offsets can be -"focussed" on using the `focusStart` and `focusEnd` methods which return -positions (when called on a position which is not a range position, they just -return `this`). - -Positions can be compared using methods such as `precedes`, which holds if -both positions are defined (_i.e.,_ the position is not `NoPosition`) and the -end point of `this` position is not larger than the start point of the given -position. In addition, range positions can be tested for inclusion (using -method `includes`) and overlapping (using method `overlaps`). - -Range positions are either *transparent* or *opaque* (not transparent). The -fact whether a range position is opaque or not has an impact on its permitted -use, because trees containing range positions must satisfy the following -invariants: - -- A tree with an offset position never contains a child with a range position -- If the child of a tree with a range position also has a range position, then the child's range is contained in the parent's range. -- Opaque range positions of children of the same node are non-overlapping (this means their overlap is at most a single point). - -Using the `makeTransparent` method, an opaque range position can be converted -to a transparent one; all other positions are returned unchanged. - diff --git a/overviews/reflection/changelog211.md b/overviews/reflection/changelog211.md deleted file mode 100644 index 831acb168f..0000000000 --- a/overviews/reflection/changelog211.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -layout: overview-large -title: Changes in Scala 2.11 - -partof: reflection -num: 7 -outof: 7 ---- - -EXPERIMENTAL - -**Eugene Burmako** - -The page lives at [/overviews/macros/changelog211.html](/overviews/macros/changelog211.html). \ No newline at end of file diff --git a/overviews/reflection/environment-universes-mirrors.md b/overviews/reflection/environment-universes-mirrors.md deleted file mode 100644 index e1add4127c..0000000000 --- a/overviews/reflection/environment-universes-mirrors.md +++ /dev/null @@ -1,200 +0,0 @@ ---- -layout: overview-large -title: Environment, Universes, and Mirrors - -discourse: true - -partof: reflection -num: 2 -outof: 7 -languages: [ja] ---- - -EXPERIMENTAL - -## Environment - -The reflection environment differs based on whether the reflective task is to -be done at run time or at compile time. The distinction between an environment to be used at -run time or compile time is encapsulated in a so-called *universe*. Another -important aspect of the reflective environment is the set of entities that we -have reflective access to. This set of entities is determined by a so-called -*mirror*. - -For example, the entities accessible through runtime -reflection are made available by a `ClassloaderMirror`. This mirror provides -only access to entities (packages, types, and members) loaded by a specific -classloader. - -Mirrors not only determine the set of entities that can be accessed -reflectively. They also provide reflective operations to be performed on those -entities. For example, in runtime reflection an *invoker mirror* can be used -to invoke a method or constructor of a class. - -## Universes - -There are two principal -types of universes-- since there exists both runtime and compile-time -reflection capabilities, one must use the universe that corresponds to -whatever the task is at hand. Either: - -- `scala.reflect.runtime.universe` for **runtime reflection**, or -- `scala.reflect.macros.Universe` for **compile-time reflection**. - -A universe provides an interface to all the principal concepts used in -reflection, such as `Types`, `Trees`, and `Annotations`. - -## Mirrors - -All information provided by -reflection is made accessible through *mirrors*. Depending on -the type of information to be obtained, or the reflective action to be taken, -different flavors of mirrors must be used. *Classloader mirrors* can be used to obtain representations of types and -members. From a classloader mirror, it's possible to obtain more specialized *invoker mirrors* (the most commonly-used mirrors), which implement reflective -invocations, such as method or constructor calls and field accesses. - -Summary: - -- **"Classloader" mirrors**. -These mirrors translate names to symbols (via methods `staticClass`/`staticModule`/`staticPackage`). - -- **"Invoker" mirrors**. -These mirrors implement reflective invocations (via methods `MethodMirror.apply`, `FieldMirror.get`, etc.). These "invoker" mirrors are the types of mirrors that are most commonly used. - -### Runtime Mirrors - -The entry point to mirrors for use at runtime is via `ru.runtimeMirror()`, where `ru` is `scala.reflect.runtime.universe`. - -The result of a `scala.reflect.api.JavaMirrors#runtimeMirror` call is a classloader mirror, of type `scala.reflect.api.Mirrors#ReflectiveMirror`, which can load symbols by name. - -A classloader mirror can create invoker mirrors (including `scala.reflect.api.Mirrors#InstanceMirror`, `scala.reflect.api.Mirrors#MethodMirror`, `scala.reflect.api.Mirrors#FieldMirror`, `scala.reflect.api.Mirrors#ClassMirror`, and `scala.reflect.api.Mirrors#ModuleMirror`). - -Examples of how these two types of mirrors interact are available below. - -### Types of Mirrors, Their Use Cases & Examples - -A `ReflectiveMirror` is used for loading symbols by name, and as an entry point into invoker mirrors. Entry point: `val m = ru.runtimeMirror()`. Example: - - scala> val ru = scala.reflect.runtime.universe - ru: scala.reflect.api.JavaUniverse = ... - - scala> val m = ru.runtimeMirror(getClass.getClassLoader) - m: scala.reflect.runtime.universe.Mirror = JavaMirror ... - -An `InstanceMirror` is used for creating invoker mirrors for methods and fields and for inner classes and inner objects (modules). Entry point: `val im = m.reflect()`. Example: - - scala> class C { def x = 2 } - defined class C - - scala> val im = m.reflect(new C) - im: scala.reflect.runtime.universe.InstanceMirror = instance mirror for C@3442299e - -A `MethodMirror` is used for invoking instance methods (Scala only has instance methods-- methods of objects are instance methods of object instances, obtainable via `ModuleMirror.instance`). Entry point: `val mm = im.reflectMethod()`. Example: - - scala> val methodX = ru.typeOf[C].declaration(ru.TermName("x")).asMethod - methodX: scala.reflect.runtime.universe.MethodSymbol = method x - - scala> val mm = im.reflectMethod(methodX) - mm: scala.reflect.runtime.universe.MethodMirror = method mirror for C.x: scala.Int (bound to C@3442299e) - - scala> mm() - res0: Any = 2 - -A `FieldMirror` is used for getting/setting instance fields (like methods, Scala only has instance fields, see above). Entry point: `val fm = im.reflectField()`. Example: - - scala> class C { val x = 2; var y = 3 } - defined class C - - scala> val m = ru.runtimeMirror(getClass.getClassLoader) - m: scala.reflect.runtime.universe.Mirror = JavaMirror ... - - scala> val im = m.reflect(new C) - im: scala.reflect.runtime.universe.InstanceMirror = instance mirror for C@5f0c8ac1 - - scala> val fieldX = ru.typeOf[C].declaration(ru.TermName("x")).asTerm.accessed.asTerm - fieldX: scala.reflect.runtime.universe.TermSymbol = value x - - scala> val fmX = im.reflectField(fieldX) - fmX: scala.reflect.runtime.universe.FieldMirror = field mirror for C.x (bound to C@5f0c8ac1) - - scala> fmX.get - res0: Any = 2 - - scala> fmX.set(3) - - scala> val fieldY = ru.typeOf[C].declaration(ru.TermName("y")).asTerm.accessed.asTerm - fieldY: scala.reflect.runtime.universe.TermSymbol = variable y - - scala> val fmY = im.reflectField(fieldY) - fmY: scala.reflect.runtime.universe.FieldMirror = field mirror for C.y (bound to C@5f0c8ac1) - - scala> fmY.get - res1: Any = 3 - - scala> fmY.set(4) - - scala> fmY.get - res2: Any = 4 - -A `ClassMirror` is used for creating invoker mirrors for constructors. Entry points: for static classes `val cm1 = m.reflectClass()`, for inner classes `val mm2 = im.reflectClass()`. Example: - - scala> case class C(x: Int) - defined class C - - scala> val m = ru.runtimeMirror(getClass.getClassLoader) - m: scala.reflect.runtime.universe.Mirror = JavaMirror ... - - scala> val classC = ru.typeOf[C].typeSymbol.asClass - classC: scala.reflect.runtime.universe.Symbol = class C - - scala> val cm = m.reflectClass(classC) - cm: scala.reflect.runtime.universe.ClassMirror = class mirror for C (bound to null) - - scala> val ctorC = ru.typeOf[C].declaration(ru.nme.CONSTRUCTOR).asMethod - ctorC: scala.reflect.runtime.universe.MethodSymbol = constructor C - - scala> val ctorm = cm.reflectConstructor(ctorC) - ctorm: scala.reflect.runtime.universe.MethodMirror = constructor mirror for C.(x: scala.Int): C (bound to null) - - scala> ctorm(2) - res0: Any = C(2) - -A `ModuleMirror` is used for accessing instances of singleton objects. Entry points: for static objects `val mm1 = m.reflectModule()`, for inner objects `val mm2 = im.reflectModule()`. Example: - - scala> object C { def x = 2 } - defined module C - - scala> val m = ru.runtimeMirror(getClass.getClassLoader) - m: scala.reflect.runtime.universe.Mirror = JavaMirror ... - - scala> val objectC = ru.typeOf[C.type].termSymbol.asModule - objectC: scala.reflect.runtime.universe.ModuleSymbol = object C - - scala> val mm = m.reflectModule(objectC) - mm: scala.reflect.runtime.universe.ModuleMirror = module mirror for C (bound to null) - - scala> val obj = mm.instance - obj: Any = C$@1005ec04 - -### Compile-Time Mirrors - -Compile-time mirrors make use of only classloader mirrors to load symbols by name. - -The entry point to classloader mirrors is via `scala.reflect.macros.Context#mirror`. Typical methods which use classloader mirrors include `scala.reflect.api.Mirror#staticClass`, `scala.reflect.api.Mirror#staticModule`, and `scala.reflect.api.Mirror#staticPackage`. For example: - - import scala.reflect.macros.Context - - case class Location(filename: String, line: Int, column: Int) - - object Macros { - def currentLocation: Location = macro impl - - def impl(c: Context): c.Expr[Location] = { - import c.universe._ - val pos = c.macroApplication.pos - val clsLocation = c.mirror.staticModule("Location") // get symbol of "Location" object - c.Expr(Apply(Ident(clsLocation), List(Literal(Constant(pos.source.path)), Literal(Constant(pos.line)), Literal(Constant(pos.column))))) - } - } - -*Of note:* There are several high-level alternatives that one can use to avoid having to manually lookup symbols. For example, `typeOf[Location.type].termSymbol` (or `typeOf[Location].typeSymbol` if we needed a `ClassSymbol`), which are typesafe since we don’t have to use strings to lookup the symbol. diff --git a/overviews/reflection/overview.md b/overviews/reflection/overview.md deleted file mode 100644 index f0ace068aa..0000000000 --- a/overviews/reflection/overview.md +++ /dev/null @@ -1,346 +0,0 @@ ---- -layout: overview-large -title: Overview - -partof: reflection -num: 1 -outof: 7 -languages: [ja] ---- - -EXPERIMENTAL - -**Heather Miller, Eugene Burmako, Philipp Haller** - -*Reflection* is the ability of a program to inspect, and possibly even modify -itself. It has a long history across object-oriented, functional, -and logic programming paradigms. -While some languages are built around reflection as a guiding principle, many -languages progressively evolve their reflection abilities over time. - -Reflection involves the ability to **reify** (ie. make explicit) otherwise-implicit -elements of a program. These elements can be either static program elements -like classes, methods, or expressions, or dynamic elements like the current -continuation or execution events such as method invocations and field accesses. -One usually distinguishes between compile-time and runtime reflection depending -on when the reflection process is performed. **Compile-time reflection** -is a powerful way to develop program transformers and generators, while -**runtime reflection** is typically used to adapt the language semantics -or to support very late binding between software components. - -Until 2.10, Scala has not had any reflection capabilities of its own. Instead, -one could use part of the Java reflection API, namely that dealing with providing -the ability to dynamically inspect classes and objects and access their members. -However, many Scala-specific elements are unrecoverable under standalone Java reflection, -which only exposes Java elements (no functions, no traits) -and types (no existential, higher-kinded, path-dependent and abstract types). -In addition, Java reflection is also unable to recover runtime type info of Java types -that are generic at compile-time; a restriction that carried through to runtime -reflection on generic types in Scala. - -In Scala 2.10, a new reflection library was introduced not only to address the shortcomings -of Java’s runtime reflection on Scala-specific and generic types, but to also -add a more powerful toolkit of general reflective capabilities to Scala. Along -with full-featured runtime reflection for Scala types and generics, Scala 2.10 also -ships with compile-time reflection capabilities, in the form of -[macros]({{site.baseurl }}/overviews/macros/overview.html), as well as the -ability to *reify* Scala expressions into abstract syntax trees. - -## Runtime Reflection - -What is runtime reflection? Given a type or instance of some object at **runtime**, -reflection is the ability to: - -- inspect the type of that object, including generic types, -- to instantiate new objects, -- or to access or invoke members of that object. - -Let's jump in and see how to do each of the above with a few examples. - -### Examples - -#### Inspecting a Runtime Type (Including Generic Types at Runtime) - -As with other JVM languages, Scala's types are _erased_ at compile time. This -means that if you were to inspect the runtime type of some instance, that you -might not have access to all type information that the Scala compiler has -available at compile time. - -`TypeTags` can be thought of as objects which carry along all type information -available at compile time, to runtime. Though, it's important to note that -`TypeTag`s are always generated by the compiler. This generation is triggered -whenever an implicit parameter or context bound requiring a `TypeTag` is used. -This means that, typically, one can only obtain a `TypeTag` using implicit -parameters or context bounds. - -For example, using context bounds: - - scala> import scala.reflect.runtime.{universe => ru} - import scala.reflect.runtime.{universe=>ru} - - scala> val l = List(1,2,3) - l: List[Int] = List(1, 2, 3) - - scala> def getTypeTag[T: ru.TypeTag](obj: T) = ru.typeTag[T] - getTypeTag: [T](obj: T)(implicit evidence$1: ru.TypeTag[T])ru.TypeTag[T] - - scala> val theType = getTypeTag(l).tpe - theType: ru.Type = List[Int] - -In the above, we first import `scala.reflect.runtime.universe` (it must always -be imported in order to use `TypeTag`s), and we create a `List[Int]` called -`l`. Then, we define a method `getTypeTag` which has a type parameter `T` that -has a context bound (as the REPL shows, this is equivalent to defining an -implicit "evidence" parameter, which causes the compiler to generate a -`TypeTag` for `T`). Finally, we invoke our method with `l` as its parameter, -and call `tpe` which returns the type contained in the `TypeTag`. As we can -see, we get the correct, complete type (including `List`'s concrete type -argument), `List[Int]`. - -Once we have obtained the desired `Type` instance, we can inspect it, e.g.: - - scala> val decls = theType.declarations.take(10) - decls: Iterable[ru.Symbol] = List(constructor List, method companion, method isEmpty, method head, method tail, method ::, method :::, method reverse_:::, method mapConserve, method ++) - -#### Instantiating a Type at Runtime - -Types obtained through reflection can be instantiated by invoking their -constructor using an appropriate "invoker" mirror (mirrors are expanded upon -[below]({{ site.baseurl }}/overviews/reflection/overview.html#mirrors)). Let's -walk through an example using the REPL: - - scala> case class Person(name: String) - defined class Person - - scala> val m = ru.runtimeMirror(getClass.getClassLoader) - m: scala.reflect.runtime.universe.Mirror = JavaMirror with ... - -In the first step we obtain a mirror `m` which makes all classes and types -available that are loaded by the current classloader, including class -`Person`. - - scala> val classPerson = ru.typeOf[Person].typeSymbol.asClass - classPerson: scala.reflect.runtime.universe.ClassSymbol = class Person - - scala> val cm = m.reflectClass(classPerson) - cm: scala.reflect.runtime.universe.ClassMirror = class mirror for Person (bound to null) - -The second step involves obtaining a `ClassMirror` for class `Person` using -the `reflectClass` method. The `ClassMirror` provides access to the -constructor of class `Person`. - - scala> val ctor = ru.typeOf[Person].declaration(ru.nme.CONSTRUCTOR).asMethod - ctor: scala.reflect.runtime.universe.MethodSymbol = constructor Person - -The symbol for `Person`s constructor can be obtained using only the runtime -universe `ru` by looking it up in the declarations of type `Person`. - - scala> val ctorm = cm.reflectConstructor(ctor) - ctorm: scala.reflect.runtime.universe.MethodMirror = constructor mirror for Person.(name: String): Person (bound to null) - - scala> val p = ctorm("Mike") - p: Any = Person(Mike) - -#### Accessing and Invoking Members of Runtime Types - -In general, members of runtime types are accessed using an appropriate -"invoker" mirror (mirrors are expanded upon -[below]({{ site.baseurl}}/overviews/reflection/overview.html#mirrors)). -Let's walk through an example using the REPL: - - scala> case class Purchase(name: String, orderNumber: Int, var shipped: Boolean) - defined class Purchase - - scala> val p = Purchase("Jeff Lebowski", 23819, false) - p: Purchase = Purchase(Jeff Lebowski,23819,false) - -In this example, we will attempt to get and set the `shipped` field of -`Purchase` `p`, reflectively. - - scala> import scala.reflect.runtime.{universe => ru} - import scala.reflect.runtime.{universe=>ru} - - scala> val m = ru.runtimeMirror(p.getClass.getClassLoader) - m: scala.reflect.runtime.universe.Mirror = JavaMirror with ... - -As we did in the previous example, we'll begin by obtaining a mirror `m`, -which makes all classes and types available that are loaded by the classloader -that also loaded the class of `p` (`Purchase`), which we need in order to -access member `shipped`. - - scala> val shippingTermSymb = ru.typeOf[Purchase].declaration(ru.TermName("shipped")).asTerm - shippingTermSymb: scala.reflect.runtime.universe.TermSymbol = method shipped - -We now look up the declaration of the `shipped` field, which gives us a -`TermSymbol` (a type of `Symbol`). We'll need to use this `Symbol` later to -obtain a mirror that gives us access to the value of this field (for some -instance). - - scala> val im = m.reflect(p) - im: scala.reflect.runtime.universe.InstanceMirror = instance mirror for Purchase(Jeff Lebowski,23819,false) - - scala> val shippingFieldMirror = im.reflectField(shippingTermSymb) - shippingFieldMirror: scala.reflect.runtime.universe.FieldMirror = field mirror for Purchase.shipped (bound to Purchase(Jeff Lebowski,23819,false)) - -In order to access a specific instance's `shipped` member, we need a mirror -for our specific instance, `p`'s instance mirror, `im`. Given our instance -mirror, we can obtain a `FieldMirror` for any `TermSymbol` representing a -field of `p`'s type. - -Now that we have a `FieldMirror` for our specific field, we can use methods -`get` and `set` to get/set our specific instance's `shipped` member. Let's -change the status of `shipped` to `true`. - - scala> shippingFieldMirror.get - res7: Any = false - - scala> shippingFieldMirror.set(true) - - scala> shippingFieldMirror.get - res9: Any = true - -### Runtime Classes in Java vs. Runtime Types in Scala - -Those who are comfortable using Java reflection to obtain Java _Class_ -instances at runtime might have noticed that, in Scala, we instead obtain -runtime _types_. - -The REPL-run below shows a very simple scenario where using Java reflection on -Scala classes might return surprising or incorrect results. - -First, we define a base class `E` with an abstract type member `T`, and from -it, we derive two subclasses, `C` and `D`. - - scala> class E { - | type T - | val x: Option[T] = None - | } - defined class E - - scala> class C extends E - defined class C - - scala> class D extends C - defined class D - -Then, we create an instance of both `C` and `D`, meanwhile making type member -`T` concrete (in both cases, `String`) - - scala> val c = new C { type T = String } - c: C{type T = String} = $anon$1@7113bc51 - - scala> val d = new D { type T = String } - d: D{type T = String} = $anon$1@46364879 - -Now, we use methods `getClass` and `isAssignableFrom` from Java Reflection to -obtain an instance of `java.lang.Class` representing the runtime classes of -`c` and `d`, and then we test to see that `d`'s runtime class is a subclass of -`c`'s runtime representation. - - scala> c.getClass.isAssignableFrom(d.getClass) - res6: Boolean = false - -Since above, we saw that `D` extends `C`, this result is a bit surprising. In -performing this simple runtime type check, one would expect the result of the -question "is the class of `d` a subclass of the class of `c`?" to be `true`. -However, as you might've noticed above, when `c` and `d` are instantiated, the -Scala compiler actually creates anonymous subclasses of `C` and `D`, -respectively. This is due to the fact that the Scala compiler must translate -Scala-specific (_i.e.,_ non-Java) language features into some equivalent in -Java bytecode in order to be able to run on the JVM. Thus, the Scala compiler -often creates synthetic classes (i.e. automatically-generated classes) that -are used at runtime in place of user-defined classes. This is quite -commonplace in Scala and can be observed when using Java reflection with a -number of Scala features, _e.g._ closures, type members, type refinements, -local classes, _etc_. - -In situations like these, we can instead use Scala reflection to obtain -precise runtime _types_ of these Scala objects. Scala runtime types carry -along all type info from compile-time, avoiding these types mismatches between -compile-time and run-time. - -Below, we use define a method which uses Scala reflection to get the runtime -types of its arguments, and then checks the subtyping relationship between the -two. If its first argument's type is a subtype of its second argument's type, -it returns `true`. - - scala> import scala.reflect.runtime.{universe => ru} - import scala.reflect.runtime.{universe=>ru} - - scala> def m[T: ru.TypeTag, S: ru.TypeTag](x: T, y: S): Boolean = { - | val leftTag = ru.typeTag[T] - | val rightTag = ru.typeTag[S] - | leftTag.tpe <:< rightTag.tpe - | } - m: [T, S](x: T, y: S)(implicit evidence$1: scala.reflect.runtime.universe.TypeTag[T], implicit evidence$2: scala.reflect.runtime.universe.TypeTag[S])Boolean - - scala> m(d, c) - res9: Boolean = true - -As we can see, we now get the expected result-- `d`'s runtime type is indeed a -subtype of `c`'s runtime type. - -## Compile-time Reflection - -Scala reflection enables a form of *metaprogramming* which makes it possible -for programs to modify *themselves* at compile time. This compile-time -reflection is realized in the form of macros, which provide the ability to -execute methods that manipulate abstract syntax trees at compile-time. - -A particularly interesting aspect of macros is that they are based on the same -API used also for Scala's runtime reflection, provided in package -`scala.reflect.api`. This enables the sharing of generic code between macros -and implementations that utilize runtime reflection. - -Note that -[the macros guide]({{ site.baseurl }}/overviews/macros/overview.html) -focuses on macro specifics, whereas this guide focuses on the general aspects -of the reflection API. Many concepts directly apply to macros, though, such -as abstract syntax trees which are discussed in greater detail in the section on -[Symbols, Trees, and Types]({{site.baseurl }}/overviews/reflection/symbols-trees-types.html). - -## Environment - -All reflection tasks require a proper environment to be set up. This -environment differs based on whether the reflective task is to be done at run -time or at compile time. The distinction between an environment to be used at -run time or compile time is encapsulated in a so-called *universe*. Another -important aspect of the reflective environment is the set of entities that we -have reflective access to. This set of entities is determined by a so-called -*mirror*. - -Mirrors not only determine the set of entities that can be accessed -reflectively. They also provide reflective operations to be performed on those -entities. For example, in runtime reflection an *invoker mirror* can be used -to invoke a method or constructor of a class. - -### Universes - -`Universe` is the entry point to Scala reflection. -A universe provides an interface to all the principal concepts used in -reflection, such as `Types`, `Trees`, and `Annotations`. For more details, see -the section of this guide on -[Universes]({{ site.baseurl}}/overviews/reflection/environment-universes-mirrors.html), -or the -[Universes API docs](http://www.scala-lang.org/api/current/scala-reflect/index.html#scala.reflect.api.Universe) -in package `scala.reflect.api`. - -To use most aspects of Scala reflection, including most code examples provided -in this guide, you need to make sure you import a `Universe` or the members -of a `Universe`. Typically, to use runtime reflection, one can import all -members of `scala.reflect.runtime.universe`, using a wildcard import: - - import scala.reflect.runtime.universe._ - -### Mirrors - -`Mirror`s are a central part of Scala Reflection. All information provided by -reflection is made accessible through these so-called mirrors. Depending on -the type of information to be obtained, or the reflective action to be taken, -different flavors of mirrors must be used. - -For more details, see the section of this guide on -[Mirrors]({{ site.baseurl}}/overviews/reflection/environment-universes-mirrors.html), -or the -[Mirrors API docs](http://www.scala-lang.org/api/current/scala-reflect/index.html#scala.reflect.api.Mirrors) -in package `scala.reflect.api`. diff --git a/overviews/reflection/symbols-trees-types.md b/overviews/reflection/symbols-trees-types.md deleted file mode 100644 index 838e962908..0000000000 --- a/overviews/reflection/symbols-trees-types.md +++ /dev/null @@ -1,776 +0,0 @@ ---- -layout: overview-large -title: Symbols, Trees, and Types - -discourse: true - -partof: reflection -num: 3 -outof: 7 -languages: [ja] ---- - -EXPERIMENTAL - -## Symbols - -Symbols are used to establish bindings between a name and the entity it refers -to, such as a class or a method. Anything you define and can give a name to in -Scala has an associated symbol. - -Symbols contain all available information about the declaration of an entity -(`class`/`object`/`trait` etc.) or a member (`val`s/`var`s/`def`s etc.), and -as such are an integral abstraction central to both runtime reflection and -compile-time reflection (macros). - -A symbol can provide a wealth of information ranging from the basic `name` -method available on all symbols to other, more involved, concepts such as -getting the `baseClasses` from `ClassSymbol`. Other common use cases of -symbols include inspecting members' signatures, getting type parameters of a -class, getting the parameter type of a method or finding out the type of a -field. - -### The Symbol Owner Hierarchy - -Symbols are organized in a hierarchy. For example, a symbol that represents a -parameter of a method is *owned* by the corresponding method symbol, a method -symbol is *owned* by its enclosing class, trait, or object, a class is *owned* -by a containing package and so on. - -If a symbol does not have an owner, for example, because it refers to a top-level -entity, such as a top-level package, then its owner is the special -`NoSymbol` singleton object. Representing a missing symbol, `NoSymbol` is -commonly used in the API to denote an empty or default value. Accessing the -`owner` of `NoSymbol` throws an exception. See the API docs for the general -interface provided by type -[`Symbol`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/reflect/api/Symbols$SymbolApi.html) - -### `TypeSymbol`s - -A `TypeSymbol` represents type, class, and trait declarations, as well as type -parameters. Interesting members that do not apply to the more specific -`ClassSymbol`s, include `isAbstractType`, `isContravariant`, and -`isCovariant`. - -- `ClassSymbol`: Provides access to all information contained in a class or trait declaration, e.g., `name`, modifiers (`isFinal`, `isPrivate`, `isProtected`, `isAbstractClass`, etc.), `baseClasses`, and `typeParams`. - -### `TermSymbol`s - -The type of term symbols representing val, var, def, and object declarations -as well as packages and value parameters. - -- `MethodSymbol`: The type of method symbols representing def declarations (subclass of `TermSymbol`). It supports queries like checking whether a method is a (primary) constructor, or whether a method supports variable-length argument lists. -- `ModuleSymbol`: The type of module symbols representing object declarations. It allows looking up the class implicitly associated with the object definition via member `moduleClass`. The opposite look up is also possible. One can go back from a module class to the associated module symbol by inspecting its `selfType.termSymbol`. - -### Symbol Conversions - -There can be situations where one uses a method that returns an instance of -the general `Symbol` type. In cases like these, it's possible to convert the -more general `Symbol` type obtained to the specific, more specialized symbol -type needed. - -Symbol conversions, such as `asClass` or `asMethod`, are used to convert to a -more specific subtype of `Symbol` as appropriate (if you want to use the -`MethodSymbol` interface, for example). - -For example, - - scala> import scala.reflect.runtime.universe._ - import scala.reflect.runtime.universe._ - - scala> class C[T] { def test[U](x: T)(y: U): Int = ??? } - defined class C - - scala> val testMember = typeOf[C[Int]].member(TermName("test")) - testMember: scala.reflect.runtime.universe.Symbol = method test - -In this case, `member` returns an instance of `Symbol`, not `MethodSymbol` as -one might expect. Thus, we must use `asMethod` to ensure that we obtain a -`MethodSymbol` - - scala> testMember.asMethod - res0: scala.reflect.runtime.universe.MethodSymbol = method test - -### Free symbols - -The two symbol types `FreeTermSymbol` and `FreeTypeSymbol` have a special -status, in the sense that they refer to symbols whose available information is -not complete. These symbols are generated in some cases during reification -(see the corresponding section about reifying trees for more background). -Whenever reification cannot locate a symbol (meaning that the symbol is not -available in the corresponding class file, for example, because the symbol -refers to a local class), it reifies it as a so-called "free type", a -synthetic dummy symbol that remembers the original name and owner and has a -surrogate type signature that closely follows the original. You can check -whether a symbol is a free type by calling `sym.isFreeType`. You can also get -a list of all free types referenced by a tree and its children by calling -`tree.freeTypes`. Finally, you can get warnings when reification produces free -types by using `-Xlog-free-types`. - -## Types - -As its name suggests, instances of `Type` represent information about the type -of a corresponding symbol. This includes its members (methods, fields, type -aliases, abstract types, nested classes, traits, etc.) either declared -directly or inherited, its base types, its erasure and so on. Types also -provide operations to test for type conformance or equivalence. - -### Instantiating Types - -In general, there are three ways to instantiate a `Type`. - -1. via method `typeOf` on `scala.reflect.api.TypeTags`, which is mixed into `Universe` (simplest and most common). -2. Standard Types, such as `Int`, `Boolean`, `Any`, or `Unit` are accessible through the available universe. -3. Manual instantiation using factory methods such as `typeRef` or `polyType` on `scala.reflect.api.Types`, (not recommended). - -#### Instantiating Types With `typeOf` - -To instantiate a type, most of the time, the -`scala.reflect.api.TypeTags#typeOf` method can be used. It takes a type -argument and produces a `Type` instance which represents that argument. For -example: - - scala> import scala.reflect.runtime.universe._ - import scala.reflect.runtime.universe._ - - scala> typeOf[List[Int]] - res0: scala.reflect.runtime.universe.Type = scala.List[Int] - -In this example, a -[`scala.reflect.api.Types$TypeRef`](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/reflect/api/Types$TypeRef.html) -is returned, which corresponds to the type constructor `List`, applied to -the type argument `Int`. - -Note, however, that this approach requires one to specify by hand the type -we're trying to instantiate. What if we're interested in obtaining an instance -of `Type` that corresponds to some arbitrary instance? One can simply define a -method with a context bound on the type parameter-- this generates a -specialized `TypeTag` for us, which we can use to obtain the type of our -arbitrary instance: - - scala> def getType[T: TypeTag](obj: T) = typeOf[T] - getType: [T](obj: T)(implicit evidence$1: scala.reflect.runtime.universe.TypeTag[T])scala.reflect.runtime.universe.Type - - scala> getType(List(1,2,3)) - res1: scala.reflect.runtime.universe.Type = List[Int] - - scala> class Animal; class Cat extends Animal - defined class Animal - defined class Cat - - scala> val a = new Animal - a: Animal = Animal@21c17f5a - - scala> getType(a) - res2: scala.reflect.runtime.universe.Type = Animal - - scala> val c = new Cat - c: Cat = Cat@2302d72d - - scala> getType(c) - res3: scala.reflect.runtime.universe.Type = Cat - -_Note:_ Method `typeOf` does not work for types with type parameters, such as -`typeOf[List[A]]` where `A` is a type parameter. In this case, one can use -`scala.reflect.api.TypeTags#weakTypeOf` instead. For more details, see the -[TypeTags]({{ site.baseurl }}/overviews/reflection/typetags-manifests.html) -section of this guide. - -#### Standard Types - -Standard types, such as `Int`, `Boolean`, `Any`, or `Unit`, are accessible through a universe's `definitions` member. For example: - - scala> import scala.reflect.runtime.universe - import scala.reflect.runtime.universe - - scala> val intTpe = universe.definitions.IntTpe - intTpe: scala.reflect.runtime.universe.Type = Int - -The list of standard types is specified in trait `StandardTypes` in -[`scala.reflect.api.StandardDefinitions`](http://www.scala-lang.org/api/current/index.html#scala.reflect.api.StandardDefinitions$StandardTypes). - -### Common Operations on Types - -Types are typically used for type conformance tests or are queried for members. -The three main classes of operations performed on types are: - -1. Checking the subtyping relationship between two types. -2. Checking for equality between two types. -3. Querying a given type for certain members or inner types. - -#### Subtyping Relationships - -Given two `Type` instances, one can easily test whether one is a subtype of -the other using `<:<` (and in exceptional cases, `weak_<:<`, explained below) - - scala> import scala.reflect.runtime.universe._ - import scala.reflect.runtime.universe._ - - scala> class A; class B extends A - defined class A - defined class B - - scala> typeOf[A] <:< typeOf[B] - res0: Boolean = false - - scala> typeOf[B] <:< typeOf[A] - res1: Boolean = true - -Note that method `weak_<:<` exists to check for _weak conformance_ between two -types. This is typically important when dealing with numeric types. - -Scala's numeric types abide by the following ordering (section 3.5.3 of the Scala language specification): - -> In some situations Scala uses a more general conformance relation. A type S weakly conforms to a type T, written S <:w T, if S<:T or both S and T are primitive number types and S precedes T in the following ordering: - -| Weak Conformance Relations | - --------------------------- -| `Byte` `<:w` `Short` | -| `Short` `<:w` `Int` | -| `Char` `<:w` `Int` | -| `Int` `<:w` `Long` | -| `Long` `<:w` `Float` | -| `Float` `<:w` `Double` | - -For example, weak conformance is used to determine the type of the following if-expression: - - scala> if (true) 1 else 1d - res2: Double = 1.0 - -In the if-expression shown above, the result type is defined to be the -_weak least upper bound_ of the two types (i.e., the least upper bound with -respect to weak conformance). - -Thus, since `Double` is defined to be the least upper bound with respect to -weak conformance between `Int` and `Double` (according to the spec, shown -above), `Double` is inferred as the type of our example if-expression. - -Note that method `weak_<:<` checks for _weak conformance_ (as opposed to `<:<` -which checks for conformance without taking into consideration weak -conformance relations in section 3.5.3 of the spec) and thus returns the -correct result when inspecting conformance relations between numeric types -`Int` and `Double`: - - scala> typeOf[Int] weak_<:< typeOf[Double] - res3: Boolean = true - - scala> typeOf[Double] weak_<:< typeOf[Int] - res4: Boolean = false - -Whereas using `<:<` would incorrectly report that `Int` and `Double` do not -conform to each other in any way: - - scala> typeOf[Int] <:< typeOf[Double] - res5: Boolean = false - - scala> typeOf[Double] <:< typeOf[Int] - res6: Boolean = false - -#### Type Equality - -Similar to type conformance, one can easily check the _equality_ of two types. -That is, given two arbitrary types, one can use method `=:=` to see if both -denote the exact same compile-time type. - - scala> import scala.reflect.runtime.universe._ - import scala.reflect.runtime.universe._ - - scala> def getType[T: TypeTag](obj: T) = typeOf[T] - getType: [T](obj: T)(implicit evidence$1: scala.reflect.runtime.universe.TypeTag[T])scala.reflect.runtime.universe.Type - - scala> class A - defined class A - - scala> val a1 = new A; val a2 = new A - a1: A = A@cddb2e7 - a2: A = A@2f0c624a - - scala> getType(a1) =:= getType(a2) - res0: Boolean = true - -Note that the _precise type info_ must be the same for both instances. In the -following code snippet, for example, we have two instances of `List` -with different type arguments. - - scala> getType(List(1,2,3)) =:= getType(List(1.0, 2.0, 3.0)) - res1: Boolean = false - - scala> getType(List(1,2,3)) =:= getType(List(9,8,7)) - res2: Boolean = true - -Also important to note is that `=:=` should _always_ be used to compare types -for equality. That is, never use `==`, as it can't check for type equality in -the presence of type aliases, whereas `=:=` can: - - scala> type Histogram = List[Int] - defined type alias Histogram - - scala> typeOf[Histogram] =:= getType(List(4,5,6)) - res3: Boolean = true - - scala> typeOf[Histogram] == getType(List(4,5,6)) - res4: Boolean = false - -As we can see, `==` incorrectly reports that `Histogram` and `List[Int]` have -different types. - -#### Querying Types for Members and Declarations - -Given a `Type`, one can also _query_ it for specific members or declarations. -A `Type`'s _members_ include all fields, methods, type aliases, abstract -types, nested classes/objects/traits, etc. A `Type`'s _declarations_ are only -those members that were declared (not inherited) in the class/trait/object -definition which the given `Type` represents. - -To obtain a `Symbol` for some specific member or declaration, one need only to use methods `members` or `declarations` which provide the list of definitions associated with that type. There also exists singular counterparts for each, methods `member` and `declaration` as well. The signatures of all four are shown below: - - /** The member with given name, either directly declared or inherited, an - * OverloadedSymbol if several exist, NoSymbol if none exist. */ - def member(name: Universe.Name): Universe.Symbol - - /** The defined or declared members with name name in this type; an - * OverloadedSymbol if several exist, NoSymbol if none exist. */ - def declaration(name: Universe.Name): Universe.Symbol - - /** A Scope containing all members of this type - * (directly declared or inherited). */ - def members: Universe.MemberScope // MemberScope is a type of - // Traversable, use higher-order - // functions such as map, - // filter, foreach to query! - - /** A Scope containing the members declared directly on this type. */ - def declarations: Universe.MemberScope // MemberScope is a type of - // Traversable, use higher-order - // functions such as map, - // filter, foreach to query! - -For example, to look up the `map` method of `List`, one can do: - - scala> import scala.reflect.runtime.universe._ - import scala.reflect.runtime.universe._ - - scala> typeOf[List[_]].member("map": TermName) - res0: scala.reflect.runtime.universe.Symbol = method map - -Note that we pass method `member` a `TermName`, since we're looking up a -method. If we were to look up a type member, such as `List`'s self type, `Self`, we -would pass a `TypeName`: - - scala> typeOf[List[_]].member("Self": TypeName) - res1: scala.reflect.runtime.universe.Symbol = type Self - -We can also query all members or declarations on a type in interesting ways. -We can use method `members` to obtain a `Traversable` (`MemberScopeApi` -extends `Traversable`) of `Symbol`s representing all inherited or declared -members on a given type, which means that we can use popular higher-order -functions on collections like `foreach`, `filter`, `map`, etc., to explore our -type's members. For example, to print the members of `List` which are private, -one must simply do: - - scala> typeOf[List[Int]].members.filter(_.isPrivate).foreach(println _) - method super$sameElements - method occCounts - class CombinationsItr - class PermutationsItr - method sequential - method iterateUntilEmpty - -## Trees - -Trees are the basis of Scala's abstract syntax which is used to represent -programs. They are also called abstract syntax trees and commonly abbreviated -as ASTs. - -In Scala reflection, APIs that produce or use trees are the following: - -1. Scala annotations, which use trees to represent their arguments, exposed in `Annotation.scalaArgs` (for more, see the [Annotations]({{ site.baseurl }}/overviews/reflection/annotations-names-scopes.html) section of this guide). -2. `reify`, a special method that takes an expression and returns an AST that represents this expression. -3. Compile-time reflection with macros (outlined in the [Macros guide]({{ site.baseurl }}/overviews/macros/overview.html)) and runtime compilation with toolboxes both use trees as their program representation medium. - -It's important to note that trees are immutable except for three fields-- -`pos` (`Position`), `symbol` (`Symbol`), and `tpe` (`Type`), which are -assigned when a tree is typechecked. - -### Kinds of `Tree`s - -There are three main categories of trees: - -1. **Subclasses of `TermTree`** which represent terms, _e.g.,_ method invocations are represented by `Apply` nodes, object instantiation is achieved using `New` nodes, etc. -2. **Subclasses of `TypTree`** which represent types that are explicitly specified in program source code, _e.g.,_ `List[Int]` is parsed as `AppliedTypeTree`. _Note_: `TypTree` is not misspelled, nor is it conceptually the same as `TypeTree`-- `TypeTree` is something different. That is, in situations where `Type`s are constructed by the compiler (_e.g.,_ during type inference), they can be wrapped in `TypeTree` trees and integrated into the AST of the program. -3. **Subclasses of `SymTree`** which introduce or reference definitions. Examples of the introduction of new definitions include `ClassDef`s which represent class and trait definitions, or `ValDef` which represent field and parameter definitions. Examples of the reference of existing definitions include `Ident`s which refer to an existing definition in the current scope such as a local variable or a method. - -Any other type of tree that one might encounter are typically syntactic or -short-lived constructs. For example, `CaseDef`, which wraps individual match -cases; such nodes are neither terms nor types, nor do they carry a symbol. - -### Inspecting Trees - -Scala Reflection provides a handful of ways to visualize trees, all available -through a universe. Given a tree, one can: - -- use methods `show` or `toString` which print pseudo-Scala code represented by the tree. -- use method `showRaw` to see the raw internal tree that the typechecker operates upon. - -For example, given the following tree: - - scala> import scala.reflect.runtime.universe._ - import scala.reflect.runtime.universe._ - - scala> val tree = Apply(Select(Ident(TermName("x")), TermName("$plus")), List(Literal(Constant(2)))) - tree: scala.reflect.runtime.universe.Apply = x.$plus(2) - -We can use method `show` (or `toString`, which is equivalent) to see what that -tree represents. - - scala> show(tree) - res0: String = x.$plus(2) - -As we can see, `tree` simply adds `2` to term `x`. - -We can also go in the other direction. Given some Scala expression, we can -first obtain a tree, and then use method `showRaw` to see the raw internal -tree that the compiler and typechecker operate on. For example, given the -expression: - - scala> import scala.reflect.runtime.universe._ - import scala.reflect.runtime.universe._ - - scala> val expr = reify { class Flower { def name = "Rose" } } - expr: scala.reflect.runtime.universe.Expr[Unit] = ... - -Here, `reify` simply takes the Scala expression it was passed, and returns a -Scala `Expr`, which is simply wraps a `Tree` and a `TypeTag` (see the -[Expr]({{ site.baseurl }}/overviews/reflection/annotations-names-scopes.html) -section of this guide for more information about `Expr`s). We can obtain -the tree that `expr` contains by: - - scala> val tree = expr.tree - tree: scala.reflect.runtime.universe.Tree = - { - class Flower extends AnyRef { - def () = { - super.(); - () - }; - def name = "Rose" - }; - () - } - -And we can inspect the raw tree by simply doing: - - scala> showRaw(tree) - res1: String = Block(List(ClassDef(Modifiers(), TypeName("Flower"), List(), Template(List(Ident(TypeName("AnyRef"))), emptyValDef, List(DefDef(Modifiers(), nme.CONSTRUCTOR, List(), List(List()), TypeTree(), Block(List(Apply(Select(Super(This(tpnme.EMPTY), tpnme.EMPTY), nme.CONSTRUCTOR), List())), Literal(Constant(())))), DefDef(Modifiers(), TermName("name"), List(), List(), TypeTree(), Literal(Constant("Rose"))))))), Literal(Constant(()))) - -### Traversing Trees - -After one understands the structure of a given tree, typically the next step -is to extract info from it. This is accomplished by _traversing_ the tree, and -it can be done in one of two ways: - -- Traversal via pattern matching. -- Using a subclass of `Traverser` - -#### Traversal via Pattern Matching - -Traversal via pattern matching is the simplest and most common way to traverse -a tree. Typically, one traverses a tree via pattern matching when they are -interested in the state of a given tree at a single node. For example, say we -simply want to obtain the function and the argument of the only `Apply` node -in the following tree: - - scala> import scala.reflect.runtime.universe._ - import scala.reflect.runtime.universe._ - - scala> val tree = Apply(Select(Ident(TermName("x")), TermName("$plus")), List(Literal(Constant(2)))) - tree: scala.reflect.runtime.universe.Apply = x.$plus(2) - -We can simply match on our `tree`, and in the case that we have an `Apply` -node, just return `Apply`'s function and argument: - - scala> val (fun, arg) = tree match { - | case Apply(fn, a :: Nil) => (fn, a) - | } - fun: scala.reflect.runtime.universe.Tree = x.$plus - arg: scala.reflect.runtime.universe.Tree = 2 - -We can achieve exactly the same thing a bit more concisely, by putting the -pattern match on the left-hand side: - - scala> val Apply(fun, arg :: Nil) = tree - fun: scala.reflect.runtime.universe.Tree = x.$plus - arg: scala.reflect.runtime.universe.Tree = 2 - -Note that `Tree`s can typically be quite complex, with nodes nested -arbitrarily deep within other nodes. A simple illustration would be if we were -to add a second `Apply` node to the above tree which serves to add `3` to our -sum: - - scala> val tree = Apply(Select(Apply(Select(Ident(TermName("x")), TermName("$plus")), List(Literal(Constant(2)))), TermName("$plus")), List(Literal(Constant(3)))) - tree: scala.reflect.runtime.universe.Apply = x.$plus(2).$plus(3) - -If we apply the same pattern match as above, we obtain the outer `Apply` node -which contains as its function the entire tree representing `x.$plus(2)` that -we saw above: - - scala> val Apply(fun, arg :: Nil) = tree - fun: scala.reflect.runtime.universe.Tree = x.$plus(2).$plus - arg: scala.reflect.runtime.universe.Tree = 3 - - scala> showRaw(fun) - res3: String = Select(Apply(Select(Ident(TermName("x")), TermName("$plus")), List(Literal(Constant(2)))), TermName("$plus")) - -In cases where one must do some richer task, such as traversing an entire -tree without stopping at a specific node, or collecting and inspecting all -nodes of a specific type, using `Traverser` for traversal might be more -advantageous. - -#### Traversal via `Traverser` - -In situations where it's necessary to traverse an entire tree from top to -bottom, using traversal via pattern matching would be infeasible-- to do it -this way, one must individually handle every type of node that we might come -across in the pattern match. Thus, in these situations, typically class -`Traverser` is used. - -`Traverser` makes sure to visit every node in a given tree, in a depth-first search. - -To use a `Traverser`, simply subclass `Traverser` and override method -`traverse`. In doing so, you can simply provide custom logic to handle only -the cases you're interested in. For example, if, given our -`x.$plus(2).$plus(3)` tree from the previous section, we would like to collect -all `Apply` nodes, we could do: - - scala> import scala.reflect.runtime.universe._ - import scala.reflect.runtime.universe._ - - scala> val tree = Apply(Select(Apply(Select(Ident(TermName("x")), TermName("$plus")), List(Literal(Constant(2)))), TermName("$plus")), List(Literal(Constant(3)))) - tree: scala.reflect.runtime.universe.Apply = x.$plus(2).$plus(3) - - scala> object traverser extends Traverser { - | var applies = List[Apply]() - | override def traverse(tree: Tree): Unit = tree match { - | case app @ Apply(fun, args) => - | applies = app :: applies - | super.traverse(fun) - | super.traverseTrees(args) - | case _ => super.traverse(tree) - | } - | } - defined module traverser - -In the above, we intend to construct a list of `Apply` nodes that we find in -our given tree. - -We achieve this by in effect _adding_ a special case to the already depth-first -`traverse` method defined in superclass `Traverser`, via subclass -`traverser`'s overridden `traverse` method. Our special case affects only -nodes that match the pattern `Apply(fun, args)`, where `fun` is some function -(represented by a `Tree`) and `args` is a list of arguments (represented by a -list of `Tree`s). - -When a the tree matches the pattern (_i.e.,_ when we have an `Apply` node), we -simply add it to our `List[Apply]`, `applies`, and continue our traversal. - -Note that, in our match, we call `super.traverse` on the function `fun` -wrapped in our `Apply`, and we call `super.traverseTrees` on our argument list -`args` (essentially the same as `super.traverse`, but for `List[Tree]` rather -than a single `Tree`). In both of these calls, our objective is simple-- we -want to make sure that we use the default `traverse` method in `Traverser` -because we don't know whether the `Tree` that represents fun contains our -`Apply` pattern-- that is, we want to traverse the entire sub-tree. Since the -`Traverser` superclass calls `this.traverse`, passing in every nested sub- -tree, eventually our custom `traverse` method is guaranteed to be called for -each sub-tree that matches our `Apply` pattern. - -To trigger the `traverse` and to see the resulting `List` of matching `Apply` -nodes, simply do: - - scala> traverser.traverse(tree) - - scala> traverser.applies - res0: List[scala.reflect.runtime.universe.Apply] = List(x.$plus(2), x.$plus(2).$plus(3)) - -### Creating Trees - -When working with runtime reflection, one need not construct trees manually. -However, runtime compilation with toolboxes and compile-time reflection with -macros both use trees as their program representation medium. In these cases, -there are three recommended ways to create trees: - -1. Via method `reify` (should be preferred wherever possible). -2. Via method `parse` on `ToolBox`es. -3. Manual construction (not recommended). - -#### Tree Creation via `reify` - -Method `reify` simply takes a Scala expression as an argument, and produces -that argument's typed `Tree` representation as a result. - -Tree creation via method `reify` is the recommended way of creating trees in -Scala Reflection. To see why, let's start with a small example: - - scala> import scala.reflect.runtime.universe._ - import scala.reflect.runtime.universe._ - - scala> { val tree = reify(println(2)).tree; showRaw(tree) } - res0: String = Apply(Select(Select(This(TypeName("scala")), TermName("Predef")), TermName("println")), List(Literal(Constant(2)))) - -Here, we simply `reify` the call to `println(2)`-- that is, we convert the -expression `println(2)` to its corresponding tree representation. Then we -output the raw tree. Note that the `println` method was transformed to -`scala.Predef.println`. Such transformations ensure that regardless of where -the result of `reify` is used, it will not unexpectedly change its meaning. -For example, even if this `println(2)` snippet is later inserted into a block -of code that defines its own `println`, it wouldn't affect the behavior of the -snippet. - -This way of creating trees is thus _hygenic_, in the sense that it preserves -bindings of identifiers. - -##### Splicing Trees - -Using `reify` also allows one to compose trees from smaller trees. This is -done using `Expr.splice`. - -_Note:_ `Expr` is `reify`'s return type. It can be thought of as a simple -wrapper which contains a _typed_ `Tree`, a `TypeTag` and a handful of -reification-relevant methods, such as `splice`. For more information about -`Expr`s, see -[the relevant section of this guide]({{ site.baseurl}}/overviews/reflection/annotations-names-scopes.html). - -For example, let's try to construct a tree representing `println(2)` using -`splice`: - - scala> val x = reify(2) - x: scala.reflect.runtime.universe.Expr[Int(2)] = Expr[Int(2)](2) - - scala> reify(println(x.splice)) - res1: scala.reflect.runtime.universe.Expr[Unit] = Expr[Unit](scala.this.Predef.println(2)) - -Here, we `reify` `2` and `println` separately, and simply `splice` one into -the other. - -Note, however, that there is a requirement for the argument of `reify` to be -valid and typeable Scala code. If instead of the argument to `println` we -wanted to abstract over the `println` itself, it wouldn't be possible: - - scala> val fn = reify(println) - fn: scala.reflect.runtime.universe.Expr[Unit] = Expr[Unit](scala.this.Predef.println()) - - scala> reify(fn.splice(2)) - :12: error: Unit does not take parameters - reify(fn.splice(2)) - ^ - -As we can see, the compiler assumes that we wanted to reify a call to -`println` with no arguments, when what we really wanted was to capture the -name of the function to be called. - -These types of use-cases are currently inexpressible when using `reify`. - -#### Tree Creation via `parse` on `ToolBox`es - -`Toolbox`es can be used to typecheck, compile, and execute abstract syntax -trees. A toolbox can also be used to parse a string into an AST. - -_Note:_ Using toolboxes requires `scala-compiler.jar` to be on the classpath. - -Let's see how `parse` deals with the `println` example from the previous -section: - - scala> import scala.reflect.runtime.universe._ - import scala.reflect.runtime.universe._ - - scala> import scala.tools.reflect.ToolBox - import scala.tools.reflect.ToolBox - - scala> val tb = runtimeMirror(getClass.getClassLoader).mkToolBox() - tb: scala.tools.reflect.ToolBox[scala.reflect.runtime.universe.type] = scala.tools.reflect.ToolBoxFactory$ToolBoxImpl@7bc979dd - - scala> showRaw(tb.parse("println(2)")) - res2: String = Apply(Ident(TermName("println")), List(Literal(Constant(2)))) - -It's important to note that, unlike `reify`, toolboxes aren't limited by the -typeability requirement-- although this flexibility is achieved by sacrificing -robustness. That is, here we can see that `parse`, unlike `reify`, doesn’t -reflect the fact that `println` should be bound to the standard `println` -method. - -_Note:_ when using macros, one shouldn’t use `ToolBox.parse`. This is because -there’s already a `parse` method built into the macro context. For example: - - scala> import scala.language.experimental.macros - import scala.language.experimental.macros - - scala> def impl(c: scala.reflect.macros.Context) = c.Expr[Unit](c.parse("println(2)")) - impl: (c: scala.reflect.macros.Context)c.Expr[Unit] - - scala> def test = macro impl - test: Unit - - scala> test - 2 - -##### Typechecking with ToolBoxes - -As earlier alluded to, `ToolBox`es enable one to do more than just -constructing trees from strings. They can also be used to typecheck, compile, -and execute trees. - -In addition to outlining the structure of the program, trees also hold -important information about the semantics of the program encoded in `symbol` -(a symbol assigned to trees that introduce or reference definitions), and -`tpe` (the type of the tree). By default these fields are empty, but -typechecking fills them in. - -When using the runtime reflection framework, typechecking is implemented by -`ToolBox.typeCheck`. When using macros, at compile time one can use the -`Context.typeCheck` method. - - scala> import scala.reflect.runtime.universe._ - import scala.reflect.runtime.universe._ - - scala> val tree = reify { "test".length }.tree - tree: scala.reflect.runtime.universe.Tree = "test".length() - - scala> import scala.tools.reflect.ToolBox - import scala.tools.reflect.ToolBox - - scala> val tb = runtimeMirror(getClass.getClassLoader).mkToolBox() - tb: scala.tools.reflect.ToolBox[scala.reflect.runtime.universe.type] = ... - - scala> val ttree = tb.typeCheck(tree) - ttree: tb.u.Tree = "test".length() - - scala> ttree.tpe - res5: tb.u.Type = Int - - scala> ttree.symbol - res6: tb.u.Symbol = method length - -Here, we simply create a tree that represents a call to `"test".length`, and -use `ToolBox` `tb`'s `typeCheck` method to typecheck the tree. As we can see, -`ttree` gets the correct type, `Int`, and its `Symbol` is correctly set. - -#### Tree Creation via Manual Construction - -If all else fails, one can manually construct trees. This is the most low-level -way to create trees, and it should only be attempted if no other -approach works. It sometimes offers greater flexibility when compared with -`parse`, though this flexibility is achieved at a cost of excessive verbosity -and fragility. - -Our earlier example involving `println(2)` can be manually constructed as -follows: - - scala> Apply(Ident(TermName("println")), List(Literal(Constant(2)))) - res0: scala.reflect.runtime.universe.Apply = println(2) - -The canonical use case for this technique is when the target tree needs to be -assembled from dynamically created parts, which don’t make sense in isolation -from one another. In that case, `reify` will most likely be inapplicable, -because it requires its argument to be typeable. `parse` might not work -either, since quite often, trees are assembled on sub-expression level, with -individual parts being inexpressible as Scala sources. diff --git a/overviews/reflection/thread-safety.md b/overviews/reflection/thread-safety.md deleted file mode 100644 index dc4f0d95f1..0000000000 --- a/overviews/reflection/thread-safety.md +++ /dev/null @@ -1,56 +0,0 @@ ---- -layout: overview-large -title: Thread Safety - -discourse: true - -partof: reflection -num: 6 -outof: 7 -languages: [ja] ---- - -EXPERIMENTAL - -**Eugene Burmako** - -Unfortunately, in its current state released in Scala 2.10.0, reflection is not thread safe. -There's a JIRA issue [SI-6240](https://issues.scala-lang.org/browse/SI-6240), which can be used to track our progress -and to look up technical details, and here's a concise summary of the state of the art. - -

        NEW Thread safety issues have been fixed in Scala 2.11.0-RC1, but we are going to keep this document available for now, since the problem still remains in the Scala 2.10.x series, and we currently don't have concrete plans on when the fix is going to be backported.

        - -Currently we know about two kinds of races associated with reflection. First of all, reflection initialization (the code that is called -when `scala.reflect.runtime.universe` is accessed for the first time) cannot be safely called from multiple threads. Secondly, symbol -initialization (the code that is called when symbol's flags or type signature are accessed for the first time) isn't safe as well. -Here's a typical manifestation: - - java.lang.NullPointerException: - at s.r.i.Types$TypeRef.computeHashCode(Types.scala:2332) - at s.r.i.Types$UniqueType.(Types.scala:1274) - at s.r.i.Types$TypeRef.(Types.scala:2315) - at s.r.i.Types$NoArgsTypeRef.(Types.scala:2107) - at s.r.i.Types$ModuleTypeRef.(Types.scala:2078) - at s.r.i.Types$PackageTypeRef.(Types.scala:2095) - at s.r.i.Types$TypeRef$.apply(Types.scala:2516) - at s.r.i.Types$class.typeRef(Types.scala:3577) - at s.r.i.SymbolTable.typeRef(SymbolTable.scala:13) - at s.r.i.Symbols$TypeSymbol.newTypeRef(Symbols.scala:2754) - -Good news is that compile-time reflection (the one exposed to macros via `scala.reflect.macros.Context`) is much less susceptible to -threading problems than runtime reflection (the one exposed via `scala.reflect.runtime.universe`). The first reason is that by the time -macros get chance to run, compile-time reflective universe are already initialized, which rules our the race condition #1. The second reason -is that the compiler has never been thread-safe, so there are no tools, which expect is to run in parallel. Nevertheless, if your macro -spawns multiple threads you should still be careful. - -It's much worse for runtime reflection though. Reflection init is called the first time when `scala.reflect.runtime.universe` is initialized, -and this initialization can happen in an indirect fashion. The most prominent example here is that calling methods with `TypeTag` context bounds -is potentially problematic, because to call such a method Scala typically needs to construct an autogenerated type tag, which needs to create -a type, which needs to initialize the reflective universe. A corollary is that if you don't take special measures, you can't call reliably -use `TypeTag`-based methods in tests, because a lot of tools, e.g. sbt, run tests in parallel. - -Bottom line: -* If you're writing a macro, which doesn't explicitly create threads, you're perfectly fine. -* Runtime reflection mixed with threads or actors might be dangerous. -* Multiple threads calling methods with `TypeTag` context bounds might lead to non-deterministic results. -* Check out [SI-6240](https://issues.scala-lang.org/browse/SI-6240) to see our progress with this issue. diff --git a/overviews/reflection/typetags-manifests.md b/overviews/reflection/typetags-manifests.md deleted file mode 100644 index c82ce0dec8..0000000000 --- a/overviews/reflection/typetags-manifests.md +++ /dev/null @@ -1,162 +0,0 @@ ---- -layout: overview-large -title: TypeTags and Manifests - -discourse: true - -partof: reflection -num: 5 -outof: 7 -languages: [ja] ---- - -As with other JVM languages, Scala’s types are erased at compile time. This -means that if you were to inspect the runtime type of some instance, you -might not have access to all type information that the Scala compiler has -available at compile time. - -Like `scala.reflect.Manifest`, `TypeTags` can be thought of as objects which -carry along all type information available at compile time, to runtime. For -example, `TypeTag[T]` encapsulates the runtime type representation of some -compile-time type `T`. Note however, that `TypeTag`s should be considered to -be a richer replacement of the pre-2.10 notion of a `Manifest`, that are -additionally fully integrated with Scala reflection. - -There exist three different types of TypeTags: - -1. `scala.reflect.api.TypeTags#TypeTag`. -A full type descriptor of a Scala type. For example, a `TypeTag[List[String]]` contains all type information, in this case, of type `scala.List[String]`. - -2. `scala.reflect.ClassTag`. -A partial type descriptor of a Scala type. For example, a `ClassTag[List[String]]` contains only the erased class type information, in this case, of type `scala.collection.immutable.List`. `ClassTag`s provide access only to the runtime class of a type. Analogous to `scala.reflect.ClassManifest`. - -3. `scala.reflect.api.TypeTags#WeakTypeTag`. -A type descriptor for abstract types (see corresponding subsection below). - -## Obtaining a `TypeTag` - -Like `Manifest`s, `TypeTag`s are always generated by the compiler, and can be obtained in three ways. - -### via the Methods `typeTag`, `classTag`, or `weakTypeTag` - -One can directly obtain a `TypeTag` for a specific type by simply using -method `typeTag`, available through `Universe`. - -For example, to obtain a `TypeTag` which represents `Int`, we can do: - - import scala.reflect.runtime.universe._ - val tt = typeTag[Int] - -Or likewise, to obtain a `ClassTag` which represents `String`, we can do: - - import scala.reflect._ - val ct = classTag[String] - -Each of these methods constructs a `TypeTag[T]` or `ClassTag[T]` for the given -type argument `T`. - -### Using an Implicit Parameter of Type `TypeTag[T]`, `ClassTag[T]`, or `WeakTypeTag[T]` - -As with `Manifest`s, one can in effect _request_ that the compiler generate a -`TypeTag`. This is done by simply specifying an implicit _evidence_ parameter -of type `TypeTag[T]`. If the compiler fails to find a matching implicit value -during implicit search, it will automatically generate a `TypeTag[T]`. - -_Note_: this is typically achieved by using an implicit parameter on methods -and classes only. - -For example, we can write a method which takes some arbitrary object, and -using a `TypeTag`, prints information about that object's type arguments: - - import scala.reflect.runtime.universe._ - - def paramInfo[T](x: T)(implicit tag: TypeTag[T]): Unit = { - val targs = tag.tpe match { case TypeRef(_, _, args) => args } - println(s"type of $x has type arguments $targs") - } - -Here, we write a generic method `paramInfo` parameterized on `T`, and we -supply an implicit parameter `(implicit tag: TypeTag[T])`. We can then -directly access the type (of type `Type`) that `tag` represents using method -`tpe` of `TypeTag`. - -We can then use our method `paramInfo` as follows: - - scala> paramInfo(42) - type of 42 has type arguments List() - - scala> paramInfo(List(1, 2)) - type of List(1, 2) has type arguments List(Int) - -### Using a Context bound of a Type Parameter - -A less verbose way to achieve exactly the same as above is by using a context -bound on a type parameter. Instead of providing a separate implicit parameter, -one can simply include the `TypeTag` in the type parameter list as follows: - - def myMethod[T: TypeTag] = ... - -Given context bound `[T: TypeTag]`, the compiler will simply generate an -implicit parameter of type `TypeTag[T]` and will rewrite the method to look -like the example with the implicit parameter in the previous section. - -The above example rewritten to use context bounds is as follows: - - import scala.reflect.runtime.universe._ - - def paramInfo[T: TypeTag](x: T): Unit = { - val targs = typeOf[T] match { case TypeRef(_, _, args) => args } - println(s"type of $x has type arguments $targs") - } - - scala> paramInfo(42) - type of 42 has type arguments List() - - scala> paramInfo(List(1, 2)) - type of List(1, 2) has type arguments List(Int) - -## WeakTypeTags - -`WeakTypeTag[T]` generalizes `TypeTag[T]`. Unlike a regular `TypeTag`, -components of its type representation can be references to type parameters or -abstract types. However, `WeakTypeTag[T]` tries to be as concrete as possible, -_i.e.,_ if type tags are available for the referenced type arguments or abstract -types, they are used to embed the concrete types into the `WeakTypeTag[T]`. - -Continuing the example above: - - def weakParamInfo[T](x: T)(implicit tag: WeakTypeTag[T]): Unit = { - val targs = tag.tpe match { case TypeRef(_, _, args) => args } - println(s"type of $x has type arguments $targs") - } - - scala> def foo[T] = weakParamInfo(List[T]()) - foo: [T]=> Unit - - scala> foo[Int] - type of List() has type arguments List(T) - -## TypeTags and Manifests - -`TypeTag`s correspond loosely to the pre-2.10 notion of -`scala.reflect.Manifest`s. While `scala.reflect.ClassTag` corresponds to -`scala.reflect.ClassManifest` and `scala.reflect.api.TypeTags#TypeTag` mostly -corresponds to `scala.reflect.Manifest`, other pre-2.10 Manifest types do not -have a direct correspondence with a 2.10 "`Tag`" type. - -- **scala.reflect.OptManifest is not supported.** -This is because `Tag`s can reify arbitrary types, so they are always available. - -- **There is no equivalent for scala.reflect.AnyValManifest.** -Instead, one can compare their `Tag` with one of the base `Tag`s (defined in the corresponding companion objects) in order to find out whether or not it represents a primitive value class. Additionally, it's possible to simply use `.tpe.typeSymbol.isPrimitiveValueClass`. - -- **There are no replacement for factory methods defined in the Manifest companion objects.** -Instead, one could generate corresponding types using the reflection APIs provided by Java (for classes) and Scala (for types). - -- **Certain manifest operations(i.e., `<:<`, `>:>` and `typeArguments`) are not supported.** -Instead, one could use the reflection APIs provided by Java (for classes) and Scala (for types). - -In Scala 2.10, `scala.reflect.ClassManifest` are deprecated, and it is -planned to deprecate `scala.reflect.Manifest` in favor of `TypeTag`s and -`ClassTag`s in an upcoming point release. Thus, it is advisable to migrate any -`Manifest`-based APIs to use `Tag`s. diff --git a/overviews/repl/overview.md b/overviews/repl/overview.md deleted file mode 100644 index 6c5b2a03aa..0000000000 --- a/overviews/repl/overview.md +++ /dev/null @@ -1,60 +0,0 @@ ---- -layout: overview-large -title: Overview - -discourse: true - -partof: repl -num: 1 -outof: 2 ---- - -The Scala REPL is a tool (_scala_) for evaluating expressions in Scala. - -The _scala_ command will execute a source script by wrapping it in a template and -then compiling and executing the resulting program. - -In interactive mode, the REPL reads expressions at the prompt, wraps them in -an executable template, and then compiles and executes the result. - -Previous results are automatically imported into the scope of the current -expression as required. - -The REPL also provides some command facilities, described below. - -An alternative REPL is available in [the Ammonite project](https://github.com/lihaoyi/Ammonite), -which also provides a richer shell environment. - -Useful REPL features include: - - - the REPL's IMain is bound to `$intp`. - - the REPL's last exception is bound to `lastException`. - - use tab for completion. - - use `//print` to show typed desugarings. - - use `:help` for a list of commands. - - use `:paste` to enter a class and object as companions. - - use `:paste -raw` to disable code wrapping, to define a package. - - use `:javap` to inspect class artifacts. - - use `-Yrepl-outdir` to inspect class artifacts with external tools. - - use `:power` to enter power mode and import compiler components. - -Implementation notes: - - - user code can be wrapped in either an object (so that the code runs during class initialization) - or a class (so that the code runs during instance construction). The switch is `-Yrepl-class-based`. - -### Power Mode - -`:power` mode imports identifiers from the interpreter's compiler. - -That is analogous to importing from the runtime reflective context using `import reflect.runtime._, universe._`. - -Power mode also offers some utility methods as documented in the welcome banner. - -Its facilities can be witnessed using `:imports` or `-Xprint:parser`. - -### Contributing to Scala REPL - -The REPL source is part of the Scala project. Issues are tracked by the standard -mechanism for the project and pull requests are accepted at [the github repository](https://github.com/scala/scala). - diff --git a/overviews/scaladoc/basics.md b/overviews/scaladoc/basics.md deleted file mode 100644 index 0f724bee58..0000000000 --- a/overviews/scaladoc/basics.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -layout: redirected -sitemap: false -permalink: /overviews/scaladoc/basics.html -redirect_to: /overviews/scaladoc/for-library-authors.html ---- diff --git a/overviews/scaladoc/overview.md b/overviews/scaladoc/overview.md deleted file mode 100644 index 7ef367c738..0000000000 --- a/overviews/scaladoc/overview.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -layout: overview-large -title: Overview - -discourse: true - -partof: scaladoc -num: 1 -outof: 3 ---- - -Scaladoc is Scala's main documentation _tool_. Scaladoc is a documentation -system that lives in the comments of Scala source code and which generates -documentation related to the code structure within which it is written. It is -based on other comment based documentation systems like Javadoc. - -There are two flavors of Scaladoc documentation: - - - **[Using the Scaladoc interface](/overviews/scaladoc/interface.html)** – how to navigate and use generated Scaladoc documentation to learn more about a library. - - **[Generating documentation for your library with Scaladoc](/overviews/scaladoc/for-library-authors.html)** – how to use Scaladoc to generate documentation for your library. - -### Contributing to Scaladoc - -If you are interested in contributing to the API documentation of the Scala -standard library (the documentation generated by Scaladoc), please read the -[Scaladoc for Library Authors](/overviews/scaladoc/basics.html) first. - -If you'd like to contribute to the actual Scaladoc documentation generation -tool itself, then please see the -[Hacker Set Up Guide](http://scala-lang.org/contribute/hacker-guide.html#2_set_up) -which covers the steps and workflow necessary work on the Scaladoc tool. diff --git a/pl/cheatsheets/index.md b/pl/cheatsheets/index.md deleted file mode 100644 index 37201bcf85..0000000000 --- a/pl/cheatsheets/index.md +++ /dev/null @@ -1,86 +0,0 @@ ---- -layout: cheatsheet -istranslation: true -title: Scalacheat -by: Filip Czaplicki -about: Podziękowania dla Brendan O'Connor. Ten cheatsheet ma być szybkim podsumowaniem konstrukcji składniowych Scali. Licencjonowany przez Brendan O'Connor pod licencją CC-BY-SA 3.0. -language: pl ---- - -###### Contributed by {{ page.by }} - -| | | -| ------ | ------ | -| zmienne | | -| `var x = 5` | zmienna | -| Dobrze `val x = 5`
        Źle `x=6` | stała | -| `var x: Double = 5` | zmienna z podanym typem | -| funkcje | | -| Dobrze `def f(x: Int) = { x*x }`
        Źle `def f(x: Int) { x*x }` | definicja funkcji
        ukryty błąd: bez znaku = jest to procedura zwracająca Unit; powoduje to chaos | -| Dobrze `def f(x: Any) = println(x)`
        Źle `def f(x) = println(x)` | definicja funkcji
        błąd składni: potrzebne są typy dla każdego argumentu. | -| `type R = Double` | alias typu | -| `def f(x: R)` vs.
        `def f(x: => R)` | wywołanie przez wartość
        wywołanie przez nazwę (parametry leniwe) | -| `(x:R) => x*x` | funkcja anonimowa | -| `(1 to 5).map(_*2)` vs.
        `(1 to 5).reduceLeft( _+_ )` | funkcja anonimowa: podkreślenie to argument pozycjonalny | -| `(1 to 5).map( x => x*x )` | funkcja anonimowa: aby użyć argumentu dwa razy, musisz go nazwać. | -| Dobrze `(1 to 5).map(2*)`
        Źle `(1 to 5).map(*2)` | funkcja anonimowa: związana metoda infiksowa. Możesz użyć także `2*_`. | -| `(1 to 5).map { x => val y=x*2; println(y); y }` | funkcja anonimowa: z bloku zwracane jest ostatnie wyrażenie. | -| `(1 to 5) filter {_%2 == 0} map {_*2}` | funkcja anonimowa: styl potokowy. (lub ponawiasowane). | -| `def compose(g:R=>R, h:R=>R) = (x:R) => g(h(x))`
        `val f = compose({_*2}, {_-1})` | funkcja anonimowa: aby przekazać kilka bloków musisz użyć nawiasów. | -| `val zscore = (mean:R, sd:R) => (x:R) => (x-mean)/sd` | rozwijanie funkcji, oczywista składnia. | -| `def zscore(mean:R, sd:R) = (x:R) => (x-mean)/sd` | rozwijanie funkcji, oczywista składnia. | -| `def zscore(mean:R, sd:R)(x:R) = (x-mean)/sd` | rozwijanie funkcji, lukier składniowy. ale wtedy: | -| `val normer = zscore(7, 0.4) _` | potrzeba wiodącego podkreślenia, aby wydobyć funkcję częściowo zaaplikowaną, tylko dla wersji z lukrem składniowym. | -| `def mapmake[T](g:T=>T)(seq: List[T]) = seq.map(g)` | typ generyczny. | -| `5.+(3); 5 + 3`
        `(1 to 5) map (_*2)` | lukier składniowy dla operatorów infiksowych. | -| `def sum(args: Int*) = args.reduceLeft(_+_)` | zmienna liczba argumentów funkcji. | -| pakiety | | -| `import scala.collection._` | import wszystkiego z danego pakietu. | -| `import scala.collection.Vector`
        `import scala.collection.{Vector, Sequence}` | import selektywny. | -| `import scala.collection.{Vector => Vec28}` | import ze zmianą nazwy. | -| `import java.util.{Date => _, _}` | importowanie wszystkiego z java.util poza Date. | -| `package pkg` _na początku pliku_
        `package pkg { ... }` | deklaracja pakietu. | -| struktury danych | | -| `(1,2,3)` | literał krotki. (`Tuple3`) | -| `var (x,y,z) = (1,2,3)` | przypisanie z podziałem: rozpakowywanie krotki przy pomocy dopasowywania wzorca. | -| Źle`var x,y,z = (1,2,3)` | ukryty błąd: do każdego przypisana cała krotka. | -| `var xs = List(1,2,3)` | lista (niezmienna). | -| `xs(2)` | indeksowanie za pomocą nawiasów. ([slajdy](http://www.slideshare.net/Odersky/fosdem-2009-1013261/27)) | -| `1 :: List(2,3)` | operator dołożenia elementu na początek listy. | -| `1 to 5` _to samo co_ `1 until 6`
        `1 to 10 by 2` | składnia dla przedziałów. | -| `()` _(puste nawiasy)_ | jedyny obiekt typu Unit (podobnie jak void w C/Java). | -| konstrukcje kontrolne | | -| `if (check) happy else sad` | warunek. | -| `if (check) happy` _to samo co_
        `if (check) happy else ()` | lukier składniowy dla warunku. | -| `while (x < 5) { println(x); x += 1}` | pętla while. | -| `do { println(x); x += 1} while (x < 5)` | pętla do while. | -| `import scala.util.control.Breaks._`
        `breakable {`
        ` for (x <- xs) {`
        ` if (Math.random < 0.1) break`
        ` }`
        `}`| instrukcja przerwania pętli (break). ([slides](http://www.slideshare.net/Odersky/fosdem-2009-1013261/21)) | -| `for (x <- xs if x%2 == 0) yield x*10` _to samo co_
        `xs.filter(_%2 == 0).map(_*10)` | instrukcja for: filtrowanie / mapowanie | -| `for ((x,y) <- xs zip ys) yield x*y` _to samo co_
        `(xs zip ys) map { case (x,y) => x*y }` | instrukcja for: przypisanie z podziałem | -| `for (x <- xs; y <- ys) yield x*y` _to samo co_
        `xs flatMap {x => ys map {y => x*y}}` | instrukcja for: iloczyn kartezjański | -| `for (x <- xs; y <- ys) {`
        `println("%d/%d = %.1f".format(x, y, x/y.toFloat))`
        `}` | instrukcja for: imperatywnie
        [sprintf-style](http://java.sun.com/javase/6/docs/api/java/util/Formatter.html#syntax) | -| `for (i <- 1 to 5) {`
        `println(i)`
        `}` | instrukcja for: iterowanie aż do górnej granicy | -| `for (i <- 1 until 5) {`
        `println(i)`
        `}` | instrukcja for: iterowanie poniżej górnej granicy | -| pattern matching (dopasowywanie wzorca) | | -| Dobrze `(xs zip ys) map { case (x,y) => x*y }`
        Źle `(xs zip ys) map( (x,y) => x*y )` | używaj słowa kluczowego case w funkcjach w celu dopasowywania wzorca (pattern matching). | -| Źle
        `val v42 = 42`
        `Some(3) match {`
        ` case Some(v42) => println("42")`
        ` case _ => println("Not 42")`
        `}` | "v42" jest interpretowane jako nazwa pasująca do każdej wartości typu Int, więc "42" zostaje wypisywane. | -| Dobrze
        `val v42 = 42`
        `Some(3) match {`
        `` case Some(`v42`) => println("42")``
        `case _ => println("Not 42")`
        `}` | "\`v42\`" z grawisami jest interpretowane jako istniejąca wartość `v42`, więc "Not 42" zostaje wypisywane. | -| Dobrze
        `val UppercaseVal = 42`
        `Some(3) match {`
        ` case Some(UppercaseVal) => println("42")`
        ` case _ => println("Not 42")`
        `}` | `UppercaseVal` jest traktowane jako istniejąca wartość, nie jako zmienna wzorca, bo zaczyna się z wielkiej litery. W takim razie wartość przechowywana w `UppercaseVal` jest porównywana z `3`, więc "Not 42" zostaje wypisywane. | -| obiektowość | | -| `class C(x: R)` _to samo co_
        `class C(private val x: R)`
        `var c = new C(4)` | parametry konstruktora - prywatne | -| `class C(val x: R)`
        `var c = new C(4)`
        `c.x` | parametry konstruktora - publiczne | -| `class C(var x: R) {`
        `assert(x > 0, "positive please")`
        `var y = x`
        `val readonly = 5`
        `private var secret = 1`
        `def this = this(42)`
        `}`|

        konstruktor jest ciałem klasy
        deklaracja publicznego pola
        deklaracja publicznej stałej
        deklaracja pola prywatnego
        alternatywny konstruktor| -| `new{ ... }` | klasa anonimowa | -| `abstract class D { ... }` | definicja klasy abstrakcyjnej. (nie da się stworzyć obiektu tej klasy) | -| `class C extends D { ... }` | definicja klasy pochodnej. | -| `class D(var x: R)`
        `class C(x: R) extends D(x)` | dziedziczenie i parametry konstruktora. (wishlist: domyślne, automatyczne przekazywanie parametrów) -| `object O extends D { ... }` | definicja singletona. (w stylu modułu) | -| `trait T { ... }`
        `class C extends T { ... }`
        `class C extends D with T { ... }` | cechy.
        interface'y z implementacją. bez parametrów konstruktora. [możliwość mixin'ów]({{ site.baseurl }}/tutorials/tour/mixin-class-composition.html). -| `trait T1; trait T2`
        `class C extends T1 with T2`
        `class C extends D with T1 with T2` | wiele cech. | -| `class C extends D { override def f = ...}` | w przeciążeniach funkcji wymagane jest słowo kluczowe override. | -| `new java.io.File("f")` | tworzenie obiektu. | -| Źle `new List[Int]`
        Dobrze `List(1,2,3)` | błąd typu: typ abstrakcyjny
        zamiast tego konwencja: wywoływalna fabryka przysłaniająca typ | -| `classOf[String]` | literał klasy. | -| `x.isInstanceOf[String]` | sprawdzenie typu (w czasie wykonania) | -| `x.asInstanceOf[String]` | rzutowanie typu (w czasie wykonania) | -| `x: String` | oznaczenie typu (w czasie kompilacji) | diff --git a/pl/tutorials/tour/_posts/2017-02-13-abstract-types.md b/pl/tutorials/tour/_posts/2017-02-13-abstract-types.md deleted file mode 100644 index 85022d3dc8..0000000000 --- a/pl/tutorials/tour/_posts/2017-02-13-abstract-types.md +++ /dev/null @@ -1,79 +0,0 @@ ---- -layout: tutorial -title: Typy abstrakcyjne - -discourse: false - -tutorial: scala-tour -categories: tour -num: 22 -language: pl -next-page: compound-types -previous-page: 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ę, ż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/_posts/2017-02-13-annotations.md b/pl/tutorials/tour/_posts/2017-02-13-annotations.md deleted file mode 100644 index 5a88052d63..0000000000 --- a/pl/tutorials/tour/_posts/2017-02-13-annotations.md +++ /dev/null @@ -1,145 +0,0 @@ ---- -layout: tutorial -title: Adnotacje - -discourse: false - -tutorial: scala-tour -categories: tour -num: 31 -next-page: default-parameter-values -previous-page: 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`](https://www.scala-lang.org/api/current/scala/SerialVersionUID.html) | [`serialVersionUID`](http://java.sun.com/j2se/1.5.0/docs/api/java/io/Serializable.html#navbar_bottom) (pole) | -| [`scala.deprecated`](https://www.scala-lang.org/api/current/scala/deprecated.html) | [`java.lang.Deprecated`](http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Deprecated.html) | -| [`scala.inline`](https://www.scala-lang.org/api/current/scala/inline.html) (since 2.6.0) | brak odpowiednika | -| [`scala.native`](https://www.scala-lang.org/api/current/scala/native.html) (since 2.6.0) | [`native`](http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html) (słowo kluczowe) | -| [`scala.remote`](https://www.scala-lang.org/api/current/scala/remote.html) | [`java.rmi.Remote`](http://java.sun.com/j2se/1.5.0/docs/api/java/rmi/Remote.html) | -| [`scala.throws`](https://www.scala-lang.org/api/current/scala/throws.html) | [`throws`](http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html) (słowo kluczowe) | -| [`scala.transient`](https://www.scala-lang.org/api/current/scala/transient.html) | [`transient`](http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html) (słowo kluczowe) | -| [`scala.unchecked`](https://www.scala-lang.org/api/current/scala/unchecked.html) (od 2.4.0) | brak odpowiednika | -| [`scala.volatile`](https://www.scala-lang.org/api/current/scala/volatile.html) | [`volatile`](http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html) (słowo kluczowe) | -| [`scala.beans.BeanProperty`](https://www.scala-lang.org/api/current/scala/beans/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ących klasy rzucanych wyjątków. - -``` -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/_posts/2017-02-13-anonymous-function-syntax.md b/pl/tutorials/tour/_posts/2017-02-13-anonymous-function-syntax.md deleted file mode 100644 index eb982526ea..0000000000 --- a/pl/tutorials/tour/_posts/2017-02-13-anonymous-function-syntax.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -layout: tutorial -title: Funkcje anonimowe - -discourse: false - -tutorial: scala-tour -categories: tour -num: 6 -language: pl -next-page: higher-order-functions -previous-page: 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/_posts/2017-02-13-automatic-closures.md b/pl/tutorials/tour/_posts/2017-02-13-automatic-closures.md deleted file mode 100644 index 832071be91..0000000000 --- a/pl/tutorials/tour/_posts/2017-02-13-automatic-closures.md +++ /dev/null @@ -1,74 +0,0 @@ ---- -layout: tutorial -title: Automatyczna konstrukcja domknięć - -discourse: false - -tutorial: scala-tour -categories: tour -num: 30 -language: pl -next-page: annotations -previous-page: 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/_posts/2017-02-13-case-classes.md b/pl/tutorials/tour/_posts/2017-02-13-case-classes.md deleted file mode 100644 index a44f2d4720..0000000000 --- a/pl/tutorials/tour/_posts/2017-02-13-case-classes.md +++ /dev/null @@ -1,144 +0,0 @@ ---- -layout: tutorial -title: Klasy przypadków - -discourse: false - -tutorial: scala-tour -categories: tour -num: 10 -language: pl -next-page: pattern-matching -previous-page: 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` implementującą 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/_posts/2017-02-13-classes.md b/pl/tutorials/tour/_posts/2017-02-13-classes.md deleted file mode 100644 index 7cf728f455..0000000000 --- a/pl/tutorials/tour/_posts/2017-02-13-classes.md +++ /dev/null @@ -1,54 +0,0 @@ ---- -layout: tutorial -title: Klasy - -discourse: false - -tutorial: scala-tour -categories: tour -num: 3 -language: pl -next-page: traits -previous-page: 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/_posts/2017-02-13-compound-types.md b/pl/tutorials/tour/_posts/2017-02-13-compound-types.md deleted file mode 100644 index c786c168e0..0000000000 --- a/pl/tutorials/tour/_posts/2017-02-13-compound-types.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -layout: tutorial -title: Typy złożone - -discourse: false - -tutorial: scala-tour -categories: tour -num: 23 -language: pl -next-page: explicitly-typed-self-references -previous-page: 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/_posts/2017-02-13-currying.md b/pl/tutorials/tour/_posts/2017-02-13-currying.md deleted file mode 100644 index 5d1016301a..0000000000 --- a/pl/tutorials/tour/_posts/2017-02-13-currying.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -layout: tutorial -title: Rozwijanie funkcji (Currying) - -discourse: false - -tutorial: scala-tour -categories: tour -num: 9 -language: pl -next-page: case-classes -previous-page: 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, zwraca 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/_posts/2017-02-13-default-parameter-values.md b/pl/tutorials/tour/_posts/2017-02-13-default-parameter-values.md deleted file mode 100644 index e77e0aa0c3..0000000000 --- a/pl/tutorials/tour/_posts/2017-02-13-default-parameter-values.md +++ /dev/null @@ -1,73 +0,0 @@ ---- -layout: tutorial -title: Domyślne wartości parametrów - -discourse: false - -tutorial: scala-tour -categories: tour -num: 32 -language: pl -next-page: named-parameters -previous-page: 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ę, 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/_posts/2017-02-13-explicitly-typed-self-references.md b/pl/tutorials/tour/_posts/2017-02-13-explicitly-typed-self-references.md deleted file mode 100644 index 52d778b860..0000000000 --- a/pl/tutorials/tour/_posts/2017-02-13-explicitly-typed-self-references.md +++ /dev/null @@ -1,125 +0,0 @@ ---- -layout: tutorial -title: Jawnie typowane samoreferencje - -discourse: false - -tutorial: scala-tour -categories: tour -num: 24 -language: pl -next-page: implicit-parameters -previous-page: 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/_posts/2017-02-13-extractor-objects.md b/pl/tutorials/tour/_posts/2017-02-13-extractor-objects.md deleted file mode 100644 index ca281a660c..0000000000 --- a/pl/tutorials/tour/_posts/2017-02-13-extractor-objects.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -layout: tutorial -title: Obiekty ekstraktorów - -discourse: false - -tutorial: scala-tour -categories: tour -num: 15 -language: pl -next-page: sequence-comprehensions -previous-page: 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/_posts/2017-02-13-generic-classes.md b/pl/tutorials/tour/_posts/2017-02-13-generic-classes.md deleted file mode 100644 index 19fd3fa9e6..0000000000 --- a/pl/tutorials/tour/_posts/2017-02-13-generic-classes.md +++ /dev/null @@ -1,50 +0,0 @@ ---- -layout: tutorial -title: Klasy generyczne - -discourse: false - -tutorial: scala-tour -categories: tour -num: 17 -language: pl -next-page: variances -previous-page: 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/_posts/2017-02-13-higher-order-functions.md b/pl/tutorials/tour/_posts/2017-02-13-higher-order-functions.md deleted file mode 100644 index 6270503180..0000000000 --- a/pl/tutorials/tour/_posts/2017-02-13-higher-order-functions.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -layout: tutorial -title: Funkcje wyższego rzędu - -discourse: false - -tutorial: scala-tour -categories: tour -num: 7 -language: pl -next-page: nested-functions -previous-page: 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óra 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/_posts/2017-02-13-implicit-conversions.md b/pl/tutorials/tour/_posts/2017-02-13-implicit-conversions.md deleted file mode 100644 index bd07002244..0000000000 --- a/pl/tutorials/tour/_posts/2017-02-13-implicit-conversions.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -layout: tutorial -title: Konwersje niejawne - -discourse: false - -tutorial: scala-tour -categories: tour -num: 26 -language: pl -next-page: polymorphic-methods -previous-page: 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/_posts/2017-02-13-implicit-parameters.md b/pl/tutorials/tour/_posts/2017-02-13-implicit-parameters.md deleted file mode 100644 index 201c66f993..0000000000 --- a/pl/tutorials/tour/_posts/2017-02-13-implicit-parameters.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -layout: tutorial -title: Parametry domniemane - -discourse: false - -tutorial: scala-tour -categories: tour -num: 25 -language: pl -next-page: implicit-conversions -previous-page: 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/_posts/2017-02-13-inner-classes.md b/pl/tutorials/tour/_posts/2017-02-13-inner-classes.md deleted file mode 100644 index 99768bd17a..0000000000 --- a/pl/tutorials/tour/_posts/2017-02-13-inner-classes.md +++ /dev/null @@ -1,99 +0,0 @@ ---- -layout: tutorial -title: Klasy wewnętrzne - -discourse: false - -tutorial: scala-tour -categories: tour -num: 21 -language: pl -next-page: abstract-types -previous-page: 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 chcielibyśmy znieść to ograniczenie, należy zmienić typ zmiennej `nodes` na `Graph#Node`. diff --git a/pl/tutorials/tour/_posts/2017-02-13-local-type-inference.md b/pl/tutorials/tour/_posts/2017-02-13-local-type-inference.md deleted file mode 100644 index 3724c20207..0000000000 --- a/pl/tutorials/tour/_posts/2017-02-13-local-type-inference.md +++ /dev/null @@ -1,64 +0,0 @@ ---- -layout: tutorial -title: Lokalna inferencja typów - -discourse: false - -tutorial: scala-tour -categories: tour -num: 28 -language: pl -next-page: operators -previous-page: 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 ilustrujący to przykład: - -``` -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/_posts/2017-02-13-lower-type-bounds.md b/pl/tutorials/tour/_posts/2017-02-13-lower-type-bounds.md deleted file mode 100644 index 2f67d7133f..0000000000 --- a/pl/tutorials/tour/_posts/2017-02-13-lower-type-bounds.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -layout: tutorial -title: Dolne ograniczenia typów - -discourse: false - -tutorial: scala-tour -categories: tour -num: 20 -language: pl -next-page: inner-classes -previous-page: 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/_posts/2017-02-13-mixin-class-composition.md b/pl/tutorials/tour/_posts/2017-02-13-mixin-class-composition.md deleted file mode 100644 index 967953b774..0000000000 --- a/pl/tutorials/tour/_posts/2017-02-13-mixin-class-composition.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -layout: tutorial -title: Kompozycja domieszek - -discourse: false - -tutorial: scala-tour -categories: tour -num: 5 -language: pl -next-page: anonymous-function-syntax -previous-page: 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 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 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/_posts/2017-02-13-named-parameters.md b/pl/tutorials/tour/_posts/2017-02-13-named-parameters.md deleted file mode 100644 index 859b84e9c9..0000000000 --- a/pl/tutorials/tour/_posts/2017-02-13-named-parameters.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -layout: tutorial -title: Parametry nazwane - -discourse: false - -tutorial: scala-tour -categories: tour -num: 33 -language: pl -previous-page: 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/_posts/2017-02-13-nested-functions.md b/pl/tutorials/tour/_posts/2017-02-13-nested-functions.md deleted file mode 100644 index 928cf876f7..0000000000 --- a/pl/tutorials/tour/_posts/2017-02-13-nested-functions.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -layout: tutorial -title: Funkcje zagnieżdżone - -discourse: false - -tutorial: scala-tour -categories: tour -num: 8 -language: pl -next-page: currying -previous-page: 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/_posts/2017-02-13-operators.md b/pl/tutorials/tour/_posts/2017-02-13-operators.md deleted file mode 100644 index 7cb051592d..0000000000 --- a/pl/tutorials/tour/_posts/2017-02-13-operators.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -layout: tutorial -title: Operatory - -discourse: false - -tutorial: scala-tour -categories: tour -num: 29 -language: pl -next-page: automatic-closures -previous-page: 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/_posts/2017-02-13-pattern-matching.md b/pl/tutorials/tour/_posts/2017-02-13-pattern-matching.md deleted file mode 100644 index 4d8ed13d1e..0000000000 --- a/pl/tutorials/tour/_posts/2017-02-13-pattern-matching.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -layout: tutorial -title: Dopasowanie wzorców (Pattern matching) - -discourse: false - -tutorial: scala-tour -categories: tour -num: 11 -language: pl -next-page: singleton-objects -previous-page: case-classes ---- - -Scala posiada wbudowany mechanizm dopasowania wzorców. Umożliwia on dopasowanie dowolnego rodzaju danych, przy czym zawsze zwracamy 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 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/_posts/2017-02-13-polymorphic-methods.md b/pl/tutorials/tour/_posts/2017-02-13-polymorphic-methods.md deleted file mode 100644 index d772d53148..0000000000 --- a/pl/tutorials/tour/_posts/2017-02-13-polymorphic-methods.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -layout: tutorial -title: Metody polimorficzne - -discourse: false - -tutorial: scala-tour -categories: tour -num: 27 -language: pl -next-page: local-type-inference -previous-page: 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/_posts/2017-02-13-regular-expression-patterns.md b/pl/tutorials/tour/_posts/2017-02-13-regular-expression-patterns.md deleted file mode 100644 index 5e7f136882..0000000000 --- a/pl/tutorials/tour/_posts/2017-02-13-regular-expression-patterns.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -layout: tutorial -title: Wzorce wyrażeń regularnych - -discourse: false - -tutorial: scala-tour -categories: tour -num: 14 -language: pl - -next-page: extractor-objects -previous-page: singleton-objects ---- - -## 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/_posts/2017-02-13-sequence-comprehensions.md b/pl/tutorials/tour/_posts/2017-02-13-sequence-comprehensions.md deleted file mode 100644 index 1ca9e0cb88..0000000000 --- a/pl/tutorials/tour/_posts/2017-02-13-sequence-comprehensions.md +++ /dev/null @@ -1,69 +0,0 @@ ---- -layout: tutorial -title: Instrukcje for (For comprehension) - -discourse: false - -tutorial: scala-tour -categories: tour -num: 16 -language: pl -next-page: generic-classes -previous-page: 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/_posts/2017-02-13-singleton-objects.md b/pl/tutorials/tour/_posts/2017-02-13-singleton-objects.md deleted file mode 100644 index c507fad8a7..0000000000 --- a/pl/tutorials/tour/_posts/2017-02-13-singleton-objects.md +++ /dev/null @@ -1,71 +0,0 @@ ---- -layout: tutorial -title: Obiekty singleton - -discourse: false - -tutorial: scala-tour -categories: tour -num: 12 -language: pl - -next-page: regular-expression-patterns -previous-page: 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 towarzyszą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) takimi jak `ipord` powyżej, zdefiniowanymi 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, pomocnicze 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 *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/_posts/2017-02-13-tour-of-scala.md b/pl/tutorials/tour/_posts/2017-02-13-tour-of-scala.md deleted file mode 100644 index 4395e7c319..0000000000 --- a/pl/tutorials/tour/_posts/2017-02-13-tour-of-scala.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -layout: tutorial -title: Wprowadzenie - -discourse: false - -tutorial: scala-tour -categories: tour -num: 1 -outof: 33 -language: pl -next-page: 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), a także 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 zapewniający, ż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 tak, 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/_posts/2017-02-13-traits.md b/pl/tutorials/tour/_posts/2017-02-13-traits.md deleted file mode 100644 index 804aaad74a..0000000000 --- a/pl/tutorials/tour/_posts/2017-02-13-traits.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -layout: tutorial -title: Cechy - -discourse: false - -tutorial: scala-tour -categories: tour -num: 4 -language: pl -next-page: mixin-class-composition -previous-page: 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/_posts/2017-02-13-unified-types.md b/pl/tutorials/tour/_posts/2017-02-13-unified-types.md deleted file mode 100644 index 72bd052ae8..0000000000 --- a/pl/tutorials/tour/_posts/2017-02-13-unified-types.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -layout: tutorial -title: Hierarchia typów - -discourse: false - -tutorial: scala-tour -categories: tour -num: 2 -language: pl -next-page: classes -previous-page: 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/_posts/2017-02-13-upper-type-bounds.md b/pl/tutorials/tour/_posts/2017-02-13-upper-type-bounds.md deleted file mode 100644 index f5406f6806..0000000000 --- a/pl/tutorials/tour/_posts/2017-02-13-upper-type-bounds.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -layout: tutorial -title: Górne ograniczenia typów - -discourse: false - -tutorial: scala-tour -categories: tour -num: 19 -language: pl -next-page: lower-type-bounds -previous-page: 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/_posts/2017-02-13-variances.md b/pl/tutorials/tour/_posts/2017-02-13-variances.md deleted file mode 100644 index f5c896dc56..0000000000 --- a/pl/tutorials/tour/_posts/2017-02-13-variances.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -layout: tutorial -title: Wariancje - -discourse: false - -tutorial: scala-tour -categories: tour -num: 18 -language: pl -next-page: upper-type-bounds -previous-page: 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ę, ż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[+T] { - def push[S >: T](elem: S): Stack[S] = new Stack[S] { - override def top: S = elem - override def pop: Stack[S] = Stack.this - override def toString: String = - elem.toString + " " + Stack.this.toString - } - def top: T = sys.error("no element on stack") - def pop: Stack[T] = sys.error("no element on stack") - override def toString: String = "" -} - -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/pt-br/cheatsheets/index.md b/pt-br/cheatsheets/index.md deleted file mode 100644 index 9ae93150df..0000000000 --- a/pt-br/cheatsheets/index.md +++ /dev/null @@ -1,86 +0,0 @@ ---- -layout: cheatsheet -istranslation: true -title: Scalacheat -by: Reginaldo Russinholi -about: Agradecimentos a Brendan O'Connor, este 'cheatsheet' se destina a ser uma referência rápida às construções sintáticas de Scala. Licenciado por Brendan O'Connor sobre a licença CC-BY-SA 3.0. -language: pt-br ---- - -###### Contribuição de {{ page.by }} - -| | | -| ------ | ------ | -| variáveis | | -| `var x = 5` | variável | -| Bom `val x = 5`
        Ruim `x=6` | constante | -| `var x: Double = 5` | tipo explícito | -| funções | | -| Bom `def f(x: Int) = { x*x }`
        Ruim `def f(x: Int) { x*x }` | define uma função
        erro omitido: sem = é uma procedure que retorna Unit; causa dano | -| Bom `def f(x: Any) = println(x)`
        Ruim `def f(x) = println(x)` | define uma função
        erro de sintaxe: necessita tipos para todos os argumentos. | -| `type R = Double` | alias de tipo | -| `def f(x: R)` vs.
        `def f(x: => R)` | chamada-por-valor
        chamada-por-nome (parâmetros 'lazy') | -| `(x:R) => x*x` | função anônima | -| `(1 to 5).map(_*2)` vs.
        `(1 to 5).reduceLeft( _+_ )` | função anônima: 'underscore' está associado a posição do argumento. | -| `(1 to 5).map( x => x*x )` | função anônima: para usar um argumento duas vezes, tem que dar nome a ele. | -| Bom `(1 to 5).map(2*)`
        Ruim `(1 to 5).map(*2)` | função anônima: método infixo encadeado. Use `2*_` para ficar mais claro. | -| `(1 to 5).map { val x=_*2; println(x); x }` | função anônima: estilo em bloco retorna a última expressão. | -| `(1 to 5) filter {_%2 == 0} map {_*2}` | função anônima: estilo 'pipeline'. (ou também parênteses). | -| `def compose(g:R=>R, h:R=>R) = (x:R) => g(h(x))`
        `val f = compose({_*2}, {_-1})` | função anônima: para passar múltiplos blocos é necessário colocar entre parênteses. | -| `val zscore = (mean:R, sd:R) => (x:R) => (x-mean)/sd` | currying, sintáxe óbvia. | -| `def zscore(mean:R, sd:R) = (x:R) => (x-mean)/sd` | currying, sintáxe óbvia | -| `def zscore(mean:R, sd:R)(x:R) = (x-mean)/sd` | currying, sintáxe 'sugar'. mas então: | -| `val normer = zscore(7, 0.4)_` | precisa de 'underscore' no final para obter o parcial, apenas para a versão 'sugar'. | -| `def mapmake[T](g:T=>T)(seq: List[T]) = seq.map(g)` | tipo genérico. | -| `5.+(3); 5 + 3`
        `(1 to 5) map (_*2)` | sintáxe 'sugar' para operadores infixos. | -| `def sum(args: Int*) = args.reduceLeft(_+_)` | varargs. | -| pacotes | | -| `import scala.collection._` | caracter coringa para importar tudo de um pacote. | -| `import scala.collection.Vector`
        `import scala.collection.{Vector, Sequence}` | importação seletiva. | -| `import scala.collection.{Vector => Vec28}` | renomear uma importação. | -| `import java.util.{Date => _, _}` | importar tudo de java.util exceto Date. | -| `package pkg` _no início do arquivo_
        `package pkg { ... }` | declara um pacote. | -| estruturas de dados | | -| `(1,2,3)` | literal de tupla. (`Tuple3`) | -| `var (x,y,z) = (1,2,3)` | atribuição desestruturada: desempacotando uma tupla através de "pattern matching". | -| Ruim`var x,y,z = (1,2,3)` | erro oculto: cada variável é associada a tupla inteira. | -| `var xs = List(1,2,3)` | lista (imutável). | -| `xs(2)` | indexação por parênteses. ([slides](http://www.slideshare.net/Odersky/fosdem-2009-1013261/27)) | -| `1 :: List(2,3)` | concatenação. | -| `1 to 5` _o mesmo que_ `1 until 6`
        `1 to 10 by 2` | sintáxe 'sugar' para intervalo. | -| `()` _(parênteses vazio)_ | um membro do tipo Unit (igual ao void de C/Java). | -| estruturas de controle | | -| `if (check) happy else sad` | condicional. | -| `if (check) happy` _o mesmo que_
        `if (check) happy else ()` | condicional 'sugar'. | -| `while (x < 5) { println(x); x += 1}` | while. | -| `do { println(x); x += 1} while (x < 5)` | do while. | -| `import scala.util.control.Breaks._`
        `breakable {`
        ` for (x <- xs) {`
        ` if (Math.random < 0.1) break`
        ` }`
        `}`| break. ([slides](http://www.slideshare.net/Odersky/fosdem-2009-1013261/21)) | -| `for (x <- xs if x%2 == 0) yield x*10` _o mesmo que_
        `xs.filter(_%2 == 0).map(_*10)` | for: filter/map | -| `for ((x,y) <- xs zip ys) yield x*y` _o mesmo que_
        `(xs zip ys) map { case (x,y) => x*y }` | for: associação desestruturada | -| `for (x <- xs; y <- ys) yield x*y` _o mesmo que_
        `xs flatMap {x => ys map {y => x*y}}` | for: produto cruzado | -| `for (x <- xs; y <- ys) {`
        `println("%d/%d = %.1f".format(x, y, x/y.toFloat))`
        `}` | for: estilo imperativo
        [sprintf-style](http://java.sun.com/javase/6/docs/api/java/util/Formatter.html#syntax) | -| `for (i <- 1 to 5) {`
        `println(i)`
        `}` | for: itera incluindo o limite superior | -| `for (i <- 1 until 5) {`
        `println(i)`
        `}` | for: itera omitindo o limite superior | -| pattern matching | | -| Bom `(xs zip ys) map { case (x,y) => x*y }`
        Ruim `(xs zip ys) map( (x,y) => x*y )` | use 'case' nos argumentos de funções para fazer a associação via 'pattern matching'. | -| Ruim
        `val v42 = 42`
        `Some(3) match {`
        ` case Some(v42) => println("42")`
        ` case _ => println("Not 42")`
        `}` | "v42" é interpretado como um nome que será comparado com qualquer valor Int, e "42" é impresso. | -| Bom
        `val v42 = 42`
        `Some(3) match {`
        `` case Some(`v42`) => println("42")``
        `case _ => println("Not 42")`
        `}` | "\`v42\`" entre crases é interpretado como existindo o valor `v42`, e "Not 42" é impresso. | -| Bom
        `val UppercaseVal = 42`
        `Some(3) match {`
        ` case Some(UppercaseVal) => println("42")`
        ` case _ => println("Not 42")`
        `}` | `UppercaseVal` é tratado como um valor existente, mais do que uma nova variável de padrão, porque ele inicia com uma letra maiúscula. Assim, o valor contido em `UppercaseVal` é checado contra `3`, e "Not 42" é impresso. | -| orientação a objetos | | -| `class C(x: R)` _o mesmo que_
        `class C(private val x: R)`
        `var c = new C(4)` | parâmetros do construtor - privado | -| `class C(val x: R)`
        `var c = new C(4)`
        `c.x` | parâmetros do construtor - público | -| `class C(var x: R) {`
        `assert(x > 0, "positive please")`
        `var y = x`
        `val readonly = 5`
        `private var secret = 1`
        `def this = this(42)`
        `}`|
        o construtor é o corpo da classe
        declara um membro público
        declara um membro que pode ser obtido mas não alterado
        declara um membro privado
        construtor alternativo| -| `new{ ... }` | classe anônima | -| `abstract class D { ... }` | define uma classe abstrata. (que não pode ser instanciada) | -| `class C extends D { ... }` | define uma classe com herança. | -| `class D(var x: R)`
        `class C(x: R) extends D(x)` | herança e parâmetros do construtor. (lista de desejos: automaticamente passar parâmetros por 'default') -| `object O extends D { ... }` | define um 'singleton'. (module-like) | -| `trait T { ... }`
        `class C extends T { ... }`
        `class C extends D with T { ... }` | traits.
        interfaces-com-implementação. sem parâmetros de construtor. [mixin-able]({{ site.baseurl }}/tutorials/tour/mixin-class-composition.html). -| `trait T1; trait T2`
        `class C extends T1 with T2`
        `class C extends D with T1 with T2` | multiplos traits. | -| `class C extends D { override def f = ...}` | é necessário declarar a sobrecarga de métodos. | -| `new java.io.File("f")` | cria/instancia um objeto. | -| Ruim `new List[Int]`
        Bom `List(1,2,3)` | erro de tipo: tipo abstrato
        ao invés, por convenção: a 'factory' chamada já aplica o tipo implicitamente | -| `classOf[String]` | literal de classe. | -| `x.isInstanceOf[String]` | checagem de tipo (em tempo de execução) | -| `x.asInstanceOf[String]` | conversão/'cast' de tipo (em tempo de execução) | -| `x: String` | atribuição de tipo (em tempo de compilação) | diff --git a/pt-br/tutorials/tour/_posts/2017-02-13-abstract-types.md b/pt-br/tutorials/tour/_posts/2017-02-13-abstract-types.md deleted file mode 100644 index 27465ec323..0000000000 --- a/pt-br/tutorials/tour/_posts/2017-02-13-abstract-types.md +++ /dev/null @@ -1,80 +0,0 @@ ---- -layout: tutorial -title: Tipos Abstratos - -discourse: false - -tutorial: scala-tour -categories: tour -num: 22 -next-page: compound-types -previous-page: inner-classes -language: pt-br ---- - -Em Scala, as classes são parametrizadas com valores (os parâmetros de construtor) e com tipos (se as [classes genéricas](generic-classes.html)). Por razões de regularidade, só não é possível ter valores como membros de um objeto; tipos juntamente com valores são membros de objetos. Além disso, ambas as formas de membros podem ser concretas e abstratas. - -Aqui está um exemplo que mostra uma definição de valor diferido e uma definição de tipo abstrato como membros de uma [trait](traits.html) chamada `Buffer`. - -```tut -trait Buffer { - type T - val element: T -} -``` - -*Tipos Abstratos* são tipos cuja identidade não é precisamente conhecida. No exemplo acima, só sabemos que cada objeto da classe `Buffer` tem um membro de tipo `T`, mas a definição de classe `Buffer` não revela a qual tipo concreto o membro do tipo `T` corresponde. Como definições de valores, podemos sobrescrever definições de tipos em subclasses. Isso nos permite revelar mais informações sobre um tipo abstrato ao limitar o tipo associado (o qual descreve as possíveis instâncias concretas do tipo abstrato). - -No seguinte programa temos uma classe `SeqBuffer` que nos permite armazenar apenas as sequências no buffer ao definir que o tipo `T` precisa ser um subtipo de `Seq[U]` para um novo tipo abstrato `U`: - -```tut -abstract class SeqBuffer extends Buffer { - type U - type T <: Seq[U] - def length = element.length -} -``` - -[Traits](traits.html) ou [classes](classes.html) com membros de tipo abstratos são frequentemente utilizadas em combinação com instâncias de classe anônimas. Para ilustrar isso, agora analisamos um programa que lida com um buffer de sequência que se refere a uma lista de inteiros: - -```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) -} -``` - -O tipo de retorno do método `newIntSeqBuf` refere-se a uma especialização da trait `Buffer` no qual o tipo `U` é agora equivalente a `Int`. Declaramos um tipo *alias* semelhante ao que temos na instanciação da classe anônima dentro do corpo do método `newIntSeqBuf`. Criamos uma nova instância de `IntSeqBuffer` na qual o tipo `T` refere-se a `List[Int]`. - -Observe que muitas vezes é possível transformar os membros de tipo abstrato em parâmetros de tipo de classes e vice-versa. Aqui está uma versão do código acima que usa apenas parâmetros de tipo: - -```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) -} -``` - -Note que temos que usar [anotação de variância](variances.html) aqui; Caso contrário, não seríamos capazes de ocultar o tipo implementado pela sequência concreta do objeto retornado pelo método `newIntSeqBuf`. Além disso, há casos em que não é possível substituir tipos abstratos com parâmetros de tipo. - diff --git a/pt-br/tutorials/tour/_posts/2017-02-13-annotations.md b/pt-br/tutorials/tour/_posts/2017-02-13-annotations.md deleted file mode 100644 index 260465148d..0000000000 --- a/pt-br/tutorials/tour/_posts/2017-02-13-annotations.md +++ /dev/null @@ -1,150 +0,0 @@ ---- -layout: tutorial -title: Anotações - -discourse: false - -tutorial: scala-tour -categories: tour -num: 31 -next-page: default-parameter-values -previous-page: automatic-closures -language: pt-br ---- - -Anotações associam meta-informação com definições. - -Uma cláusula de anotação simples tem a forma `@C` ou `@C(a1,..., an)`. Aqui, `C` é um construtor de uma classe `C`, que deve estar em conformidade com a classe `scala.Annotation`. Todos os argumentos de construtor fornecidos `a1, .., an` devem ser expressões constantes (isto é, expressões em literais numéricos, strings, literais de classes, enumerações Java e matrizes uni-dimensionais). - -Uma cláusula de anotação se aplica à primeira definição ou declaração que a segue. Mais de uma cláusula de anotação pode preceder uma definição e uma declaração. Não importa a ordem em que essas cláusulas são declaradas. - -O significado das cláusulas de anotação é _dependente da implementação_. Na plataforma Java, as seguintes anotações Scala têm um significado padrão. - -| Scala | Java | -| ------ | ------ | -| [`scala.SerialVersionUID`](https://www.scala-lang.org/api/current/scala/SerialVersionUID.html) | [`serialVersionUID`](http://java.sun.com/j2se/1.5.0/docs/api/java/io/Serializable.html#navbar_bottom) (field) | -| [`scala.deprecated`](https://www.scala-lang.org/api/current/scala/deprecated.html) | [`java.lang.Deprecated`](http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Deprecated.html) | -| [`scala.inline`](https://www.scala-lang.org/api/current/scala/inline.html) (desde 2.6.0) | não há equivalente | -| [`scala.native`](https://www.scala-lang.org/api/current/scala/native.html) (desde 2.6.0) | [`native`](http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html) (keyword) | -| [`scala.remote`](https://www.scala-lang.org/api/current/scala/remote.html) | [`java.rmi.Remote`](http://java.sun.com/j2se/1.5.0/docs/api/java/rmi/Remote.html) | -| [`scala.throws`](https://www.scala-lang.org/api/current/scala/throws.html) | [`throws`](http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html) (keyword) | -| [`scala.transient`](https://www.scala-lang.org/api/current/scala/transient.html) | [`transient`](http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html) (keyword) | -| [`scala.unchecked`](https://www.scala-lang.org/api/current/scala/unchecked.html) (since 2.4.0) | não há equivalente | -| [`scala.volatile`](https://www.scala-lang.org/api/current/scala/volatile.html) | [`volatile`](http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html) (keyword) | -| [`scala.beans.BeanProperty`](https://www.scala-lang.org/api/current/scala/beans/BeanProperty.html) | [`Design pattern`](http://docs.oracle.com/javase/tutorial/javabeans/writing/properties.html) | - -No exemplo a seguir, adicionamos a anotação `throws` à definição do método `read` para capturar a exceção lançada no código Java. - -> Um compilador Java verifica se um programa contém manipuladores para [exceções verificadas](http://docs.oracle.com/javase/specs/jls/se5.0/html/exceptions.html) analisando quais exceções verificadas podem resultar da execução de um método ou construtor. Para cada exceção verificada que é um resultado possível, a cláusula **throws** para o método ou construtor _deve_ mencionar a classe dessa exceção ou uma das superclasses da classe dessa exceção. -> Como Scala não tem exceções verificadas, os métodos Scala _devem_ ser anotados com uma ou mais anotações `throws`, de forma que o código Java possa capturar exceções lançadas por um método Scala. - - -Exemplo de classe Scala que lança uma exceção do tipo `IOException`: - -``` -package examples -import java.io._ -class Reader(fname: String) { - private val in = new BufferedReader(new FileReader(fname)) - @throws(classOf[IOException]) - def read() = in.read() -} -``` - -O programa Java a seguir imprime o conteúdo do arquivo cujo nome é passado como o primeiro argumento para o método `main`. - -``` -package test; -import examples.Reader; // Classe Scala acima declarada!! -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()); - } - } -} -``` - -Comentando-se a anotação `throws` na classe `Reader` o compilador produz a seguinte mensagem de erro ao compilar o programa principal Java: - -``` -Main.java:11: exception java.io.IOException is never thrown in body of -corresponding try statement - } catch (java.io.IOException e) { - ^ -1 error -``` - -### Anotações Java ### - -**Nota:** Certifique-se de usar a opção `-target: jvm-1.5` com anotações Java. - -Java 1.5 introduziu metadados definidos pelo usuário na forma de [anotações](http://java.sun.com/j2se/1.5.0/docs/guide/language/annotations.html). Uma característica chave das anotações é que elas dependem da especificação de pares no formato nome-valor para inicializar seus elementos. Por exemplo, se precisamos de uma anotação para rastrear a origem de alguma classe, podemos defini-la como: - -``` -@interface Source { - public String URL(); - public String mail(); -} -``` - -O uso da anotação Source fica da seguinte forma - -``` -@Source(URL = "http://coders.com/", - mail = "support@coders.com") -public class MyClass extends HisClass ... -``` - -A uso de anotações em Scala parece uma invocação de construtor, para instanciar uma anotação Java é preciso usar argumentos nomeados: - -``` -@Source(URL = "http://coders.com/", - mail = "support@coders.com") -class MyScalaClass ... -``` - -Esta sintaxe é bastante tediosa, se a anotação contiver apenas um parâmetro (sem valor padrão), por convenção, se o nome for especificado como `value`, ele pode ser aplicado em Java usando uma sintaxe semelhante a Scala, ou seja parecido com a invocação de um construtor: - -``` -@interface SourceURL { - public String value(); - public String mail() default ""; -} -``` - -O uso da anotação SourceURL fica da seguinte forma - -``` -@SourceURL("http://coders.com/") -public class MyClass extends HisClass ... -``` - -Neste caso, a Scala oferece a mesma possibilidade - -``` -@SourceURL("http://coders.com/") -class MyScalaClass ... -``` - -O elemento `mail` foi especificado com um valor padrão, portanto não precisamos fornecer explicitamente um valor para ele. No entanto, se precisarmos fazer isso, não podemos misturar e combinar os dois estilos em Java: - -``` -@SourceURL(value = "http://coders.com/", - mail = "support@coders.com") -public class MyClass extends HisClass ... -``` - -Scala proporciona mais flexibilidade a respeito disso: - -``` -@SourceURL("http://coders.com/", - mail = "support@coders.com") - class MyScalaClass ... -``` diff --git a/pt-br/tutorials/tour/_posts/2017-02-13-anonymous-function-syntax.md b/pt-br/tutorials/tour/_posts/2017-02-13-anonymous-function-syntax.md deleted file mode 100644 index 08b450c3aa..0000000000 --- a/pt-br/tutorials/tour/_posts/2017-02-13-anonymous-function-syntax.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -layout: tutorial -title: Sintaxe de Função Anônima - -discourse: false - -tutorial: scala-tour -categories: tour -num: 6 -next-page: higher-order-functions -previous-page: mixin-class-composition -language: pt-br ---- - -Scala fornece uma sintaxe relativamente leve para definir funções anônimas. A expressão a seguir cria uma função sucessor para inteiros: - -```tut -(x: Int) => x + 1 -``` - -Isso é uma abreviação para a definição de classe anônima a seguir: - -```tut -new Function1[Int, Int] { - def apply(x: Int): Int = x + 1 -} -``` - -Também é possível definir funções com múltiplos parâmetros: - -```tut -(x: Int, y: Int) => "(" + x + ", " + y + ")" -``` - -ou sem parâmetros: - -```tut -() => { System.getProperty("user.dir") } -``` - -Há também uma forma muito simples de escrever tipos de funções. Aqui estão os tipos da três funções acima definidas: - -``` -Int => Int -(Int, Int) => String -() => String -``` - -Essa sintaxe é uma abreviação para os seguintes tipos: - -``` -Function1[Int, Int] -Function2[Int, Int, String] -Function0[String] -``` diff --git a/pt-br/tutorials/tour/_posts/2017-02-13-automatic-closures.md b/pt-br/tutorials/tour/_posts/2017-02-13-automatic-closures.md deleted file mode 100644 index b5e35732af..0000000000 --- a/pt-br/tutorials/tour/_posts/2017-02-13-automatic-closures.md +++ /dev/null @@ -1,75 +0,0 @@ ---- -layout: tutorial -title: Construção Automática de Closures de Tipo-Dependente - -discourse: false - -tutorial: scala-tour -categories: tour -num: 30 -next-page: annotations -previous-page: operators -language: pt-br ---- - -_Nota de tradução: A palavra `closure` em pode ser traduzida como encerramento/fechamento, porém é preferível utilizar a notação original_ - -Scala permite funções sem parâmetros como parâmetros de métodos. Quando um tal método é chamado, os parâmetros reais para nomes de função sem parâmetros não são avaliados e uma função nula é passada em vez disso, tal função encapsula a computação do parâmetro correspondente (isso é conhecido por avaliação *call-by-name*). - -O código a seguir demonstra esse mecanismo: - -```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 - } -} -``` - -A função `whileLoop` recebe dois parâmetros: `cond` e `body`. Quando a função é aplicada, os parâmetros reais não são avaliados. Mas sempre que os parâmetros formais são usados no corpo de `whileLoop`, as funções nulas criadas implicitamente serão avaliadas em seu lugar. Assim, o nosso método `whileLoop` implementa um while-loop Java-like com um esquema de implementação recursiva. - -Podemos combinar o uso de [operadores infix/postfix](operators.html) com este mecanismo para criar declarações mais complexas (com uma sintaxe agradável). - -Aqui está a implementação de uma instrução que executa loop a menos que uma condição seja satisfeita: - -```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) -} -``` - -A função `loop` aceita apenas um corpo e retorna uma instância da classe` LoopUnlessCond` (que encapsula este objeto de corpo). Note que o corpo ainda não foi avaliado. A classe `LoopUnlessCond` tem um método `unless` que podemos usar como um *operador infix*. Dessa forma, obtemos uma sintaxe bastante natural para nosso novo loop: `loop { } unless ( )`. - -Aqui está a saída de quando o `TargetTest2` é executado: - -``` -i = 10 -i = 9 -i = 8 -i = 7 -i = 6 -i = 5 -i = 4 -i = 3 -i = 2 -i = 1 -``` diff --git a/pt-br/tutorials/tour/_posts/2017-02-13-case-classes.md b/pt-br/tutorials/tour/_posts/2017-02-13-case-classes.md deleted file mode 100644 index 1b9dc8392b..0000000000 --- a/pt-br/tutorials/tour/_posts/2017-02-13-case-classes.md +++ /dev/null @@ -1,142 +0,0 @@ ---- -layout: tutorial -title: Classes Case - -discourse: false - -tutorial: scala-tour -categories: tour -num: 10 -next-page: pattern-matching -previous-page: currying -language: pt-br ---- - -Scala suporta o conceito de _classes case_. Classes case são classes regulares que são: - -* Imutáveis por padrão -* Decompostas por meio de [correspondência de padrões](pattern-matching.html) -* Comparadas por igualdade estrutural ao invés de referência -* Sucintas para instanciar e operar - -Aqui temos um exemplo de hierarquia de tipos para *Notification* que consiste em uma super classe abstrata `Notification` e três tipos concretos de notificação implementados com classes case `Email`, `SMS`, e `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 -``` - -Instânciar uma classe case é fácil: (Perceba que nós não precisamos da palavra-chave `new`) - -```tut -val emailDeJohn = Email("john.doe@mail.com", "Saudações do John!", "Olá Mundo") -``` - -Os parâmetros do construtor de uma classe case são tratados como valores públicos e podem ser acessados diretamente. - -```tut -val titulo = emailDeJohn.title -println(titulo) // prints "Saudações do John!" -``` - -Com classes case, você não pode alterar seus campos diretamente. (ao menos que você declare `var` antes de um campo, mas fazê-lo geralmente é desencorajado). - -```tut:fail -emailDeJohn.title = "Adeus do John!" // Erro the compilação. Não podemos atribuir outro valor para um campo que foi declarado como val, lembrando que todos os campos de classes case são val por padrão. -``` - -Ao invés disso, faça uma cópia utilizando o método `copy`. Como descrito abaixo, então você poderá substituir alguns dos campos: - -```tut -val emailEditado = emailDeJohn.copy(title = "Estou aprendendo Scala!", body = "É muito legal!") - -println(emailDeJohn) // prints "Email(john.doe@mail.com,Saudações do John!,Hello World!)" -println(emailEditado) // prints "Email(john.doe@mail.com,Estou aprendendo Scala,É muito legal!)" -``` - -Para cada classe case em Scala o compilador gera um método `equals` que implementa a igualdade estrutural e um método `toString`. Por exemplo: - -```tut -val primeiroSMS = SMS("12345", "Hello!") -val segundoSMS = SMS("12345", "Hello!") - -if (primeiroSMS == segundoSMS) { - println("Somos iguais!") -} - -println("SMS é: " + primeiroSMS) -``` - -Irá gerar como saída: - -``` -Somos iguais! -SMS é: SMS(12345, Hello!) -``` - -Com classes case, você pode utilizar **correspondência de padrões** para manipular seus dados. Aqui temos um exemplo de uma função que escreve como saída diferente mensagens dependendo do tipo de notificação recebida: - -```tut -def mostrarNotificacao(notificacao: Notification): String = { - notificacao match { - case Email(email, title, _) => - "Você recebeu um email de " + email + " com o título: " + title - case SMS(number, message) => - "Você recebeu um SMS de" + number + "! Mensagem: " + message - case VoiceRecording(name, link) => - "Você recebeu uma Mensagem de Voz de " + name + "! Clique no link para ouvir: " + link - } -} - -val algumSMS = SMS("12345", "Você está aí?") -val algumaMsgVoz = VoiceRecording("Tom", "voicerecording.org/id/123") - -println(mostrarNotificacao(algumSMS)) -println(mostrarNotificacao(algumaMsgVoz)) - -// Saída: -// Você recebeu um SMS de 12345! Mensagem: Você está aí? -// Você recebeu uma Mensagem de Voz de Tom! Clique no link para ouvir: voicerecording.org/id/123 -``` - -Aqui um exemplo mais elaborado utilizando a proteção `if`. Com a proteção `if`, o correspondência de padrão irá falhar se a condição de proteção retorna falso. - -```tut -def mostrarNotificacaoEspecial(notificacao: Notification, emailEspecial: String, numeroEspecial: String): String = { - notificacao match { - case Email(email, _, _) if email == emailEspecial => - "Você recebeu um email de alguém especial!" - case SMS(numero, _) if numero == numeroEspecial => - "Você recebeu um SMS de alguém especial!" - case outro => - mostrarNotificacao(outro) // Nada especial para mostrar, então delega para nossa função original mostrarNotificacao - } -} - -val NumeroEspecial = "55555" -val EmailEspecial = "jane@mail.com" -val algumSMS = SMS("12345", "Você está aí?") -val algumaMsgVoz = VoiceRecording("Tom", "voicerecording.org/id/123") -val emailEspecial = Email("jane@mail.com", "Beber hoje a noite?", "Estou livre depois das 5!") -val smsEspecial = SMS("55555", "Estou aqui! Onde está você?") - -println(mostrarNotificacaoEspecial(algumSMS, EmailEspecial, NumeroEspecial)) -println(mostrarNotificacaoEspecial(algumaMsgVoz, EmailEspecial, NumeroEspecial)) -println(mostrarNotificacaoEspecial(smsEspecial, EmailEspecial, NumeroEspecial)) -println(mostrarNotificacaoEspecial(smsEspecial, EmailEspecial, NumeroEspecial)) - -// Saída: -// Você recebeu um SMS de 12345! Mensagem: Você está aí? -// Você recebeu uma Mensagem de Voz de Tom! Clique no link para ouvir: voicerecording.org/id/123 -// Você recebeu um email de alguém especial! -// Você recebeu um SMS de alguém especial! - -``` - -Ao programar em Scala, recomenda-se que você use classes case de forma pervasiva para modelar / agrupar dados, pois elas ajudam você a escrever código mais expressivo e passível de manutenção: - -* Imutabilidade libera você de precisar acompanhar onde e quando as coisas são mutadas -* Comparação por valor permite comparar instâncias como se fossem valores primitivos - não há mais incerteza sobre se as instâncias de uma classe é comparada por valor ou referência -* Correspondência de padrões simplifica a lógica de ramificação, o que leva a menos bugs e códigos mais legíveis. \ No newline at end of file diff --git a/pt-br/tutorials/tour/_posts/2017-02-13-classes.md b/pt-br/tutorials/tour/_posts/2017-02-13-classes.md deleted file mode 100644 index 8ac8293327..0000000000 --- a/pt-br/tutorials/tour/_posts/2017-02-13-classes.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -layout: tutorial -title: Classes - -discourse: false - -tutorial: scala-tour -categories: tour -num: 3 -next-page: traits -previous-page: unified-types -language: pt-br ---- - -Classes em Scala são templates estáticos que podem ser instanciados como vários objetos em tempo de execução. -Aqui uma definição de classe que define a classe `Ponto`: - -```tut -class Ponto(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 + ")" -} -``` - -Classes em Scala são parametrizadas com argumentos de construtor. O código acima define dois argumentos de construtor, `x` e `y`; ambos são acessíveis por todo o corpo da classe. - -A classe também inclui dois métodos, `move` and `toString`. `move` recebe dois parâmetros inteiros mas não retorna um valor (o tipo de retorno `Unit` equivale ao `void` em linguagens como Java). `toString`, por outro lado, não recebe parâmetro algum mas retorna um valor `String`. Dado que `toString` sobrescreve o método pré-definido `toString`, o mesmo é marcado com a palavra-chave `override`. - -Perceba que em Scala, não é necessário declarar `return` para então retornar um valor. O valor retornado em um método é simplesmente o último valor no corpo do método. No caso do método `toString` acima, a expressão após o sinal de igual é avaliada e retornada para quem chamou a função. - -Classes são instânciadas com a primitiva `new`, por exemplo: - -```tut -object Classes { - def main(args: Array[String]) { - val pt = new Ponto(1, 2) - println(pt) - pt.move(10, 10) - println(pt) - } -} -``` - -O programa define uma aplicação executável chamada Classes como um [Objeto Singleton](singleton-objects) dentro do método `main`. O método `main` cria um novo `Ponto` e armazena o valor em `pt`. Perceba que valores definidos com o construtor `val` são diferentes das variáveis definidas com o construtor `var` (veja acima a classe `Ponto`), `val` não permite atualização do valor, ou seja, o valor é uma constante. - -Aqui está a saída do programa: - -``` -(1, 2) -(11, 12) -``` diff --git a/pt-br/tutorials/tour/_posts/2017-02-13-compound-types.md b/pt-br/tutorials/tour/_posts/2017-02-13-compound-types.md deleted file mode 100644 index feca05c441..0000000000 --- a/pt-br/tutorials/tour/_posts/2017-02-13-compound-types.md +++ /dev/null @@ -1,54 +0,0 @@ ---- -layout: tutorial -title: Tipos Compostos - -discourse: false - -tutorial: scala-tour -categories: tour -num: 23 -next-page: explicitly-typed-self-references -previous-page: abstract-types -language: pt-br ---- - -Às vezes é necessário expressar que o tipo de um objeto é um subtipo de vários outros tipos. Em Scala isso pode ser expresso com a ajuda de *tipos compostos*, que são interseções de tipos de objetos. - -Suponha que temos duas traits `Cloneable` and `Resetable`: - -```tut -trait Cloneable extends java.lang.Cloneable { - override def clone(): Cloneable = { - super.clone().asInstanceOf[Cloneable] - } -} -trait Resetable { - def reset: Unit -} -``` - -Agora supondo que queremos escrever uma função `cloneAndReset` que recebe um objeto, clona e reseta o objeto original: - -``` -def cloneAndReset(obj: ?): Cloneable = { - val cloned = obj.clone() - obj.reset - cloned -} -``` - -A questão é: qual é o tipo do parâmetro `obj`? Se for `Cloneable` então o objeto pode ser clonado, mas não resetado; Se for `Resetable` nós podemos resetar, mas não há nenhuma operação para clonar. Para evitar conversão de tipos em tal situação, podemos especificar o tipo de `obj` para ser tanto `Cloneable` como `Resetable`. Este tipo composto pode ser escrito da seguinte forma em Scala: `Cloneable with Resetable`. - -Aqui está a função atualizada: - -``` -def cloneAndReset(obj: Cloneable with Resetable): Cloneable = { - //... -} -``` - -Os tipos de compostos podem consistir em vários tipos de objeto e eles podem ter um único refinamento que pode ser usado para restrigir a assinatura de membros de objetos existentes. - -A forma geral é: `A with B with C ... { refinamento }` - -Um exemplo para o uso de refinamentos é dado na página sobre [tipos abstratos](abstract-types.html). diff --git a/pt-br/tutorials/tour/_posts/2017-02-13-currying.md b/pt-br/tutorials/tour/_posts/2017-02-13-currying.md deleted file mode 100644 index 0c6d2b4cba..0000000000 --- a/pt-br/tutorials/tour/_posts/2017-02-13-currying.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -layout: tutorial -title: Currying - -discourse: false - -tutorial: scala-tour -categories: tour -num: 9 -next-page: case-classes -previous-page: nested-functions -language: pt-br ---- - -_Nota de tradução: Currying é uma técnica de programação Funcional nomeada em honra ao matemático e lógico Haskell Curry. Por essa razão a palavra Currying não será traduzida. Entende-se que é uma ação, uma técnica básica de Programação Funcional._ - -Métodos podem definir múltiplas listas de parâmetros. Quando um método é chamado com uma lista menor de parâmetros, então será retornada uma função que recebe a lista que parâmetros que falta como argumentos. - -Aqui um exemplo: - -```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))) -} -``` - -_Nota: o método `modN` é parcialmente aplicado em duas chamadas de `filter`; por exemplo: somente o primeiro argumento é realmente aplicado. O termo `modN(2)` retorna uma função do tipo `Int => Boolean` e esta se torna uma possível candidata a segundo argumento da função `filter`._ - -A saída do programa acima produz: - -``` -List(2,4,6,8) -List(3,6) -``` diff --git a/pt-br/tutorials/tour/_posts/2017-02-13-default-parameter-values.md b/pt-br/tutorials/tour/_posts/2017-02-13-default-parameter-values.md deleted file mode 100644 index d30de93cfe..0000000000 --- a/pt-br/tutorials/tour/_posts/2017-02-13-default-parameter-values.md +++ /dev/null @@ -1,75 +0,0 @@ ---- -layout: tutorial -title: Parâmetro com Valor Padrão - -discourse: false - -tutorial: scala-tour -categories: tour -num: 32 -next-page: named-parameters -previous-page: annotations -language: pt-br ---- - -Scala provê a capacidade de fornecer parâmetros com valores padrão que podem ser usados para permitir que um usuário possa omitir tais parâmetros se preciso. - -Em Java, é comum ver um monte de métodos sobrecarregados que servem apenas para fornecer valores padrão para determinados parâmetros de um método maior. Isso é especialmente verdadeiro com os construtores: - -```java -public class HashMap { - public HashMap(Map m); - /** Cria um novo HashMap com a capacidade padrão (16) - * and loadFactor (0.75) - */ - public HashMap(); - /** Cria um novo HashMap com um fator de carga padrão (0.75) */ - public HashMap(int initialCapacity); - public HashMap(int initialCapacity, float loadFactor); -} -``` - -Há realmente apenas dois construtores aqui; Um que recebe um map e outro que tem uma capacidade e um fator de carga. O terceiro e o quarto construtores estão lá para permitir que os usuários do HashMap criem instâncias com os valores padrões de fator de carga e capacidade, que provavelmente são bons para a maioria dos casos. - -O maior problema é que os valores usados como padrões estão declarados no Javadoc *e* no código. Manter isso atualizado é complicado, pois pode ser esquecido facilmente. Um abordagem típica nesses casos seria adicionar constantes públicas cujos valores aparecerão no 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); - /** Cria um novo HashMap com capacidade padrão (16) - * e fator de carga padrão (0.75) - */ - public HashMap(); - /** Cria um novo HashMap com um fator de carga padrão (0.75) */ - public HashMap(int initialCapacity); - public HashMap(int initialCapacity, float loadFactor); -} -``` - -Enquanto isso nos impede de nos repetir, é menos do que expressivo. - -Scala adiciona suporte direto para isso: - -```tut -class HashMap[K,V](initialCapacity:Int = 16, loadFactor:Float = 0.75f) { -} - -// Utiliza os valores padrões (16, 0.75f) -val m1 = new HashMap[String,Int] - -// Inicial com capacidade 20, e fator de carga padrão -val m2= new HashMap[String,Int](20) - -// Sobreescreve ambos os valores -val m3 = new HashMap[String,Int](20,0.8f) - -// Sobreescreve somente o fator de carga -// parâmetro nomeado -val m4 = new HashMap[String,Int](loadFactor = 0.8f) -``` - -Observe como podemos tirar proveito de *qualquer* valor padrão usando [parâmetros nomeados](named-parameters.html). - diff --git a/pt-br/tutorials/tour/_posts/2017-02-13-explicitly-typed-self-references.md b/pt-br/tutorials/tour/_posts/2017-02-13-explicitly-typed-self-references.md deleted file mode 100644 index 73682527a4..0000000000 --- a/pt-br/tutorials/tour/_posts/2017-02-13-explicitly-typed-self-references.md +++ /dev/null @@ -1,123 +0,0 @@ ---- -layout: tutorial -title: Auto Referências Explicitamente Tipadas - -discourse: false - -tutorial: scala-tour -categories: tour -num: 24 -next-page: implicit-parameters -previous-page: compound-types -language: pt-br ---- - -Ao desenvolver um software extensível, às vezes é útil declarar explicitamente o tipo do valor `this`. Para ilustrar isso, criaremos uma pequena representação extensível de uma estrutura de dados de grafo em Scala. - -Aqui está uma definição que descreve um grafo: - -```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 -} -``` - -Um grafo consiste em uma lista de nós e arestas onde o nó e o tipo de aresta são declarados como abstratos. O uso de [tipos abstratos](abstract-types.html) permite que a implementação da trait `Graph` forneça suas próprias classes concretas para nós e arestas. Além disso, existe um método `addNode` para adicionar novos nós a um grafo. Os nós são conectados usando o método `connectWith`. - -Uma possível implementação de `Graph` é ilustrada na classe a seguir: - -```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 - } -} -``` - -A classe `DirectedGraph` estende a classe `Graph` fornecendo uma implementação parcial. A implementação é apenas parcial porque gostaríamos de poder ampliar o `DirectedGraph`. Portanto, esta classe deixa todos os detalhes de implementação abertos e assim, tanto as arestas quanto os nós são definidos como abstratos. No entanto, a classe `DirectedGraph` revela alguns detalhes adicionais sobre a implementação do tipo das arestas ao restringir o limite de tipo para a classe `EdgeImpl`. Além disso, temos algumas implementações preliminares de arestas e nós representados pelas classes `EdgeImpl` e `NodeImpl`. Uma vez que é necessário criar novos objetos nó e aresta dentro da nossa implementação de grafo, também temos que adicionar os métodos de construção `newNode` e `newEdge`. Os métodos `addNode` e `connectWith` são ambos definidos em termos destes métodos de construção. Uma análise mais detalhada da implementação do método `connectWith` revela que, para criar uma aresta, temos que passar a auto-referência `this` para o método de construção `newEdge`. Mas a `this` é atribuído o tipo `NodeImpl`, por isso não é compatível com o tipo `Node` que é exigido pelo método de construção correspondente. Como consequência, o programa acima não é bem-formado e o compilador Scala irá emitir uma mensagem de erro. - -Em Scala é possível vincular uma classe a outro tipo (que será implementado no futuro) ao fornecer a auto referência `this` ao outro tipo explicitamente. Podemos usar esse mecanismo para corrigir nosso código acima. O tipo explícito de `this` é especificado dentro do corpo da classe `DirectedGraph`. - -Aqui está o programa já corrigido: - -```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 => // nova linha adicionada - def connectWith(node: Node): Edge = { - val edge = newEdge(this, node) // agora válido - 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 - } -} -``` - -Nesta nova definição de classe `NodeImpl`, `this` tem o tipo `Node`. Como o tipo `Node` é abstrato e, portanto, ainda não sabemos se `NodeImpl` é realmente um subtipo de `Node`, o sistema de tipo Scala não nos permitirá instanciar esta classe. No entanto declaramos com a anotação de tipo explícito que, em algum ponto, (uma subclasse de) `NodeImpl` precisa denotar um subtipo de tipo `Node` para ser instantiável. - -Aqui está uma especialização concreta de `DirectedGraph` onde todos os membros da classe abstrata são definidos: - -```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) -} -``` - -Observe que nesta classe, podemos instanciar `NodeImpl` porque agora sabemos que `NodeImpl` representa um subtipo de tipo `Node` (que é simplesmente um *alias* para `NodeImpl`). - -Aqui está um exemplo de uso da classe `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) -} -``` \ No newline at end of file diff --git a/pt-br/tutorials/tour/_posts/2017-02-13-extractor-objects.md b/pt-br/tutorials/tour/_posts/2017-02-13-extractor-objects.md deleted file mode 100644 index a773765155..0000000000 --- a/pt-br/tutorials/tour/_posts/2017-02-13-extractor-objects.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -layout: tutorial -title: Objetos Extratores - -discourse: false - -tutorial: scala-tour -categories: tour -num: 15 -next-page: sequence-comprehensions -previous-page: regular-expression-patterns -language: pt-br ---- - -Em Scala, padrões podem ser definidos independentemente de classes case. Para este fim, um método chamado `unapply` é definido para retornar um extrator. Um extrator pode ser pensado como um método especial que inverte o efeito da aplicação de um determinado objeto em algumas entradas. Seu objetivo é "extrair" as entradas que estavam presentes antes da operação `apply`. Por exemplo, o código a seguir define um [objeto](singleton-objects.html) extrator chamado 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) } // prints 21 -} -``` - -Existem duas convenções sintáticas em ação aqui: - -O padrão `case Twice (n)` causa a invocação do método `Twice.unapply`, que é usado para fazer a comparação de qualquer número par; O valor de retorno de `unapply` indica se a comparação falhou ou não, e quaisquer sub-valores que possam ser utilizados para uma seguinte comparação. Aqui, o sub-valor é `z/2`. - -O método `apply` não é necessário na correspondência de padrões. É utilizado somente para simular um construtor. `val x = Twice(21)` é expandido para `val x = Twice.apply(21)`. - -O tipo de retorno de uma chamada `unapply` deveria ser escolhido da seguinta forma: - -* Se é somente um teste, retorne `Boolean`. Por exemplo `case even()` -* Se retorna um único subvalor to tipo `T`, retorne `Option[T]` -* Se você quer retornar vários subvalores `T1,...,Tn`, agrupe todos em uma tupla opcional como `Option[(T1,...,Tn)]`. - -Algumas vezes, o número de subvalores é fixo e você precisa retornar uma sequência. Para isso, você pode definir padrões através da chamada `unapplySeq`. O último subvalor do tipo `Tn` precisa ser `Seq[S]`. Tal mecanismo é utilizado como exemplo no padrão `case List(x1, ..., xn)`. - -Extratores podem tornar o código mais fácil de manter. Para mais detalhes, leia o artigo ["Matching Objects with Patterns"](https://infoscience.epfl.ch/record/98468/files/MatchingObjectsWithPatterns-TR.pdf) (veja a seção 4) by Emir, Odersky and Williams (January 2007). diff --git a/pt-br/tutorials/tour/_posts/2017-02-13-generic-classes.md b/pt-br/tutorials/tour/_posts/2017-02-13-generic-classes.md deleted file mode 100644 index 325c3321a0..0000000000 --- a/pt-br/tutorials/tour/_posts/2017-02-13-generic-classes.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -layout: tutorial -title: Classes Genéricas - -discourse: false - -tutorial: scala-tour -categories: tour -num: 17 -next-page: variances -previous-page: sequence-comprehensions -language: pt-br ---- - -Semelhante ao Java 5 (aka. [JDK 1.5](http://java.sun.com/j2se/1.5/)), Scala tem suporte nativo para classes parametrizadas com tipos. Essas classes genéricas são particularmente úteis para o desenvolvimento de classes que representam coleções de dados. -Aqui temos um exemplo que demonstra isso: - -```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 } -} -``` - -A classe `Stack` modela uma pilha mutável que contém elementos de um tipo arbitrário `T`. Os parâmetros de tipo garantem que somente os elementos legais (que são do tipo `T`) são inseridos na pilha. Da mesma forma, com os parâmetros de tipo podemos expressar que o método `top` retorna somente elementos de um único tipo de dado, no caso, `T`. - -Aqui temos mais alguns exemplos de uso: - -```tut -object GenericsTest extends App { - val stack = new Stack[Int] - stack.push(1) - stack.push('a') - println(stack.top) - stack.pop() - println(stack.top) -} -``` - -A saída do programa é: - -``` -97 -1 -``` -_Nota: subtipos de tipos genéricos são *invariantes*. Isto significa que se tivermos uma pilha de caracteres do tipo `Stack[Char]` então ela não pode ser usada como uma pilha de inteiros do tipo `Stack[Int]`. Isso seria incorreto porque isso nos permitiria inserir inteiros verdadeiros na pilha de caracteres. Para concluir, `Stack[T]` é um subtipo de de `Stack[S]` se e somente se `S = T`. Como isso pode ser bastante restritivo, Scala oferece um [mecanismo de anotação de parâmetro de tipo](variances.html) para controlar o comportamento de subtipo de tipos genéricos._ \ No newline at end of file diff --git a/pt-br/tutorials/tour/_posts/2017-02-13-higher-order-functions.md b/pt-br/tutorials/tour/_posts/2017-02-13-higher-order-functions.md deleted file mode 100644 index 5e0c526345..0000000000 --- a/pt-br/tutorials/tour/_posts/2017-02-13-higher-order-functions.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -layout: tutorial -title: Funções de ordem superior - -discourse: false - -tutorial: scala-tour -categories: tour -num: 7 -next-page: nested-functions -previous-page: anonymous-function-syntax -language: pt-br ---- - -Scala permite definir funções de ordem superior. Tais funções _recebem outras funções como parâmetros_, ou _resultam em uma função_. Por exemplo, a função `apply` recebe outra função `f` e um valor `v` então aplica a função `f` em`v`: - -```tut -def apply(f: Int => String, v: Int) = f(v) -``` - -_Nota: métodos são automaticamente convertidos em funções se o contexto demandar.**_ - -Outro exemplo: - -```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)) -} -``` - -A execução produz a saída: - -``` -[7] -``` - -Nesse exemplo, o método `decorator.layout` é automaticamente convertido em um valor do tipo `Int => String` conforme o método `apply` demanda. Note que o método `decorator.layout` é um _método polimórfico_ (por exemplo: ele abstrai alguns tipos de sua assinatura) e o compilador Scala precisa primeiro instanciar corretamento o tipo do método. diff --git a/pt-br/tutorials/tour/_posts/2017-02-13-implicit-conversions.md b/pt-br/tutorials/tour/_posts/2017-02-13-implicit-conversions.md deleted file mode 100644 index d88b765f5d..0000000000 --- a/pt-br/tutorials/tour/_posts/2017-02-13-implicit-conversions.md +++ /dev/null @@ -1,54 +0,0 @@ ---- -layout: tutorial -title: Conversões Implícitas - -discourse: false - -tutorial: scala-tour -categories: tour -num: 26 -next-page: polymorphic-methods -previous-page: implicit-parameters -language: pt-br ---- - -Uma conversão implícita do tipo `S` para o tipo `T` é definida por um valor implícito que tem o tipo de função `S => T`, ou por um método implícito convertível em um valor de tal tipo. - -As conversões implícitas são aplicadas em duas situações: - -* Se uma expressão `e` for do tipo `S` e `S` não estiver em conformidade com o tipo esperado `T` da expressão. -* Em uma seleção `e.m` com `e` do tipo `T`, se o seletor `m` não representar um membro de `T`. - -No primeiro caso, é procurada uma conversão `c` que seja aplicável a `e` e cujo tipo de resultado esteja em conformidade com `T`. - -No segundo caso, é procurada uma conversão `c` que seja aplicável a `e` e cujo resultado contém um membro chamado `m`. - -A seguinte operação nas duas listas xs e ys do tipo `List[Int]` é válida: - -``` -xs <= ys -``` - -Assuma que os métodos implícitos `list2ordered` e` int2ordered` definidos abaixo estão no mesmo escopo: - -``` -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] { /* .. */ } -``` - -O objeto implicitamente importado `scala.Predef` declara vários tipos predefinidos (por exemplo, `Pair`) e métodos (por exemplo, `assert`), mas também várias conversões implícitas. - -Por exemplo, ao chamar um método Java que espera um `java.lang.Integer`, você está livre para passar um `scala.Int` em vez disso. Isso ocorre porque `Predef` inclui as seguintes conversões implícitas: - -```tut -import scala.language.implicitConversions - -implicit def int2Integer(x: Int) = - java.lang.Integer.valueOf(x) -``` - -Para definir suas próprias conversões implícitas, primeiro você deve importar `scala.language.implicitConversions` (ou invocar o compilador com a opção `-language: implicitConversions`). Tal recurso deve ser explicitamente habilitado porque pode se tornar complexo se usado indiscriminadamente. diff --git a/pt-br/tutorials/tour/_posts/2017-02-13-implicit-parameters.md b/pt-br/tutorials/tour/_posts/2017-02-13-implicit-parameters.md deleted file mode 100644 index 7e859bf343..0000000000 --- a/pt-br/tutorials/tour/_posts/2017-02-13-implicit-parameters.md +++ /dev/null @@ -1,60 +0,0 @@ ---- -layout: tutorial -title: Parâmetros Implícitos - -discourse: false - -tutorial: scala-tour -categories: tour -num: 25 -next-page: implicit-conversions -previous-page: explicitly-typed-self-references -language: pt-br ---- - -Um método com _parâmetros implícitos_ pode ser aplicado a argumentos como um método normal. Neste caso, o rótulo implícito não tem efeito. No entanto, se faltarem argumentos para os parâmetros implícitos declarados, tais argumentos serão automaticamente fornecidos. - -Os argumentos reais que são elegíveis para serem passados para um parâmetro implícito se dividem em duas categorias: - -* Primeira, são elegíveis todos os identificadores x que podem ser acessados no ponto da chamada do método sem um prefixo e que denotam uma definição implícita ou um parâmetro implícito. - -* Segunda, são elegíveis também todos os membros dos módulos acompanhantes do tipo do parâmetro implícito que são rotulados como `implicit`. - -No exemplo a seguir, definimos um método `sum` que calcula a soma de uma lista de elementos usando as operações `add` e `unit` do monoide. Observe que valores implícitos não podem ser *top-level*, eles precisam ser membros de um modelo. - -```tut -/** Este exemplo usa uma estrutura da álgebra abstrata para mostrar como funcionam os parâmetros implícitos. Um semigrupo é uma estrutura algébrica em um conjunto A com uma operação (associativa), chamada add, que combina um par de A's e retorna um outro A. */ -abstract class SemiGroup[A] { - def add(x: A, y: A): A -} -/** Um monóide é um semigrupo com um elemento distinto de A, chamado unit, que quando combinado com qualquer outro elemento de A retorna esse outro elemento novamente. */ -abstract class Monoid[A] extends SemiGroup[A] { - def unit: A -} -object ImplicitTest extends App { - /** Para mostrar como os parâmetros implícitos funcionam, primeiro definimos os monóides para strings e inteiros. A palavra-chave implicit indica que o objeto correspondente pode ser usado implicitamente, dentro deste escopo, como um parâmetro de uma função definia como implícita. */ - 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 - } - /** Este método recebe uma List[A] retorna um A que representa o valor da combinação resultante ao aplicar a operação monóide sucessivamente em toda a lista. Tornar o parâmetro m implícito aqui significa que só temos de fornecer o parâmetro xs no local de chamada, pois se temos uma List[A] sabemos qual é realmente o tipo A e, portanto, o qual o tipo do Monoid[A] é necessário. Podemos então encontrar implicitamente qualquer val ou objeto no escopo atual que também tem esse tipo e usá-lo sem precisar especificá-lo explicitamente. */ - def sum[A](xs: List[A])(implicit m: Monoid[A]): A = - if (xs.isEmpty) m.unit - else m.add(xs.head, sum(xs.tail)) - - /** Aqui chamamos a função sum duas vezes, com apenas um parâmetro cada vez. Como o segundo parâmetro de soma, m, está implícito, seu valor é procurado no escopo atual, com base no tipo de monóide exigido em cada caso, o que significa que ambas as expressões podem ser totalmente avaliadas. */ - println(sum(List(1, 2, 3))) // uses IntMonoid implicitly - println(sum(List("a", "b", "c"))) // uses StringMonoid implicitly -} -``` - -Aqui está a saída do programa: - -``` -6 -abc -``` diff --git a/pt-br/tutorials/tour/_posts/2017-02-13-inner-classes.md b/pt-br/tutorials/tour/_posts/2017-02-13-inner-classes.md deleted file mode 100644 index ffc36cda3d..0000000000 --- a/pt-br/tutorials/tour/_posts/2017-02-13-inner-classes.md +++ /dev/null @@ -1,98 +0,0 @@ ---- -layout: tutorial -title: Classes Internas - -discourse: false - -tutorial: scala-tour -categories: tour -num: 21 -next-page: abstract-types -previous-page: lower-type-bounds -language: pt-br ---- - -Em Scala é possível declarar classes que tenham outras classes como membros. Em contraste com a linguagenm Java, onde classes internas são membros da classe em que foram declaradas, em Scala as classes internas são ligadas ao objeto exterior. Para ilustrar essa diferença, rapidamente esboçamos a implementação de grafo como um tipo de dados: - -```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 - } -} -``` - -Em nosso programa, os grafos são representados por uma lista de nós. Os nós são objetos da classe interna `Node`. Cada nó tem uma lista de vizinhos, que são armazenados na lista `connectedNodes`. Agora podemos configurar um grafo com alguns nós e conectar os nós de forma incremental: - -```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) -} -``` - -Agora melhoramos o exemplo acima com tipos, para assim declarar explicitamente qual o tipo das várias entidades definidas: - -```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) -} -``` - -Este código mostra claramente que o tipo nó é prefixado com sua instância externa (em nosso exemplo é o objeto `g`). Se agora temos dois grafos, o sistema de tipos de Scala não nos permite misturar nós definidos dentro de um grafo com os nós de outro, já que os nós do outro grafo têm um tipo diferente. -Aqui está um programa inválido: - -```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) // legal - val h: Graph = new Graph - val n3: h.Node = h.newNode - n1.connectTo(n3) // illegal! -} -``` - -Observe que em Java a última linha no programa do exemplo anterior é válida. Para nós de ambos os grafos, Java atribuiria o mesmo tipo `Graph.Node`; isto é, `Node` é prefixado com a classe `Graph`. Em Scala, esse tipo também pode ser expresso, e é escrito `Graph#Node`. Se quisermos ser capazes de conectar nós de diferentes grafos, temos que mudar a definição inicial da nossa implementação do grafo da seguinte maneira: - -```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 - } -} -``` - -> Note que este programa não nos permite anexar um nó a dois grafos diferentes. Se quisermos também remover esta restrição, temos de mudar o tipo da variável `nodes` para `Graph#Node`. diff --git a/pt-br/tutorials/tour/_posts/2017-02-13-local-type-inference.md b/pt-br/tutorials/tour/_posts/2017-02-13-local-type-inference.md deleted file mode 100644 index b83390cd44..0000000000 --- a/pt-br/tutorials/tour/_posts/2017-02-13-local-type-inference.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -layout: tutorial -title: Inferência de Tipo Local - -discourse: false - -tutorial: scala-tour -categories: tour -num: 28 -next-page: operators -previous-page: polymorphic-methods -language: pt-br ---- - -Scala tem um mecanismo nativo de inferência de tipos que permite ao programador omitir certas anotações. Por exemplo, muitas vezes não é necessário especificar o tipo de uma variável, uma vez que o compilador pode deduzir o tipo a partir da expressão de inicialização da variável. Os tipos de retorno de métodos também podem muitas vezes ser omitidos, uma vez que correspondem ao tipo do corpo do método, que é inferido pelo compilador. - -Por exemplo: - -```tut -object InferenceTest1 extends App { - val x = 1 + 2 * 3 // o tipo de x é Int - val y = x.toString() // o tipo de y é String - def succ(x: Int) = x + 1 // o método succ retorna um valor Int -} -``` - -Para métodos recursivos, o compilador não é capaz de inferir o tipo de retorno. - -Exemplo de um método que não irá compilar por este motivo: - -```tut:fail -object InferenceTest2 { - def fac(n: Int) = if (n == 0) 1 else n * fac(n - 1) -} -``` - -Também não é obrigatório especificar os tipos dos parâmetros quando [métodos polimórficos](polymorphic-methods.html) são invocados ou são criadas instâncias de [classes genéricas](generic-classes.html). O compilador Scala irá inferir tais parâmetros que não estão presentes a partir do contexto das chamadas e dos tipos dos parâmetros reais do método/construtor. - -Por exemplo: - -``` -case class MyPair[A, B](x: A, y: B); -object InferenceTest3 extends App { - def id[T](x: T) = x - val p = MyPair(1, "scala") // type: MyPair[Int, String] - val q = id(1) // type: Int -} -``` - -As duas últimas linhas deste programa são equivalentes ao seguinte código onde todos os tipos inferidos são declarados explicitamente: - -``` -val x: MyPair[Int, String] = MyPair[Int, String](1, "scala") -val y: Int = id[Int](1) -``` - -Em algumas situações, pode ser muito perigoso confiar no mecanismo de inferência de tipos de Scala como mostra o seguinte programa: - - -```tut:fail -object InferenceTest4 { - var obj = null - obj = new Object() -} -``` - -Este programa não compila porque o tipo inferido para a variável `obj` é `Null`. Como o único valor desse tipo é `null`, é impossível fazer essa variável se referir a outro valor. diff --git a/pt-br/tutorials/tour/_posts/2017-02-13-lower-type-bounds.md b/pt-br/tutorials/tour/_posts/2017-02-13-lower-type-bounds.md deleted file mode 100644 index 52327e6b94..0000000000 --- a/pt-br/tutorials/tour/_posts/2017-02-13-lower-type-bounds.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -layout: tutorial -title: Limitante Inferior de Tipos - -discourse: false - -tutorial: scala-tour -categories: tour -num: 20 -next-page: inner-classes -previous-page: upper-type-bounds -language: pt-br ---- - -Enquanto o [limitante superior de tipos](upper-type-bounds.html) limita um tipo a um subtipo de outro tipo, o *limitante inferior de tipos* declara um tipo para ser supertipo de outro tipo. O termo `T>: A` expressa que o parâmetro de tipo `T` ou tipo abstracto `T` refere-se a um supertipo do tipo `A`. - -Aqui está um exemplo onde isso é útil: - -```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) -} -``` - -O programa acima implementa uma linked list com uma operação de pré-inserção. Infelizmente, esse tipo é invariante no parâmetro de tipo da classe `ListNode`; Ou seja, `ListNode [String]` não é um subtipo de `ListNode [Any]`. Com a ajuda de [anotações de variância](variances.html) podemos expressar tal semântica de subtipo: - -``` -case class ListNode[+T](h: T, t: ListNode[T]) { ... } -``` - -Infelizmente, este programa não compila, porque uma anotação de covariância só é possível se a variável de tipo é usada somente em posições covariantes. Como a variável de tipo `T` aparece como um parâmetro de tipo do método `prepend`, tal regra é violada. Porém com a ajuda de um *limitante inferior de tipo*, podemos implementar um método de pré-inserção onde `T` só aparece em posições covariantes. - -Aqui está o código correspondente: - -```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) -} -``` - -_Nota:_ o novo método `prepend` tem um tipo ligeiramente menos restritivo. Permite, por exemplo, inserir um objeto de um supertipo a uma lista existente. A lista resultante será uma lista deste supertipo. - -Aqui está o código que ilustra isso: - -```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/pt-br/tutorials/tour/_posts/2017-02-13-mixin-class-composition.md b/pt-br/tutorials/tour/_posts/2017-02-13-mixin-class-composition.md deleted file mode 100644 index c5fba76c5c..0000000000 --- a/pt-br/tutorials/tour/_posts/2017-02-13-mixin-class-composition.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -layout: tutorial -title: Composição de Classes Mixin - -discourse: false - -tutorial: scala-tour -categories: tour -num: 5 -next-page: anonymous-function-syntax -previous-page: traits -language: pt-br ---- -_Nota de tradução: A palavra `mixin` pode ser traduzida como mescla, porém é preferível utilizar a notação original_ - -Ao contrário de linguagens que suportam somente _herança simples_, Scala tem uma noção mais abrangente sobre a reutilização de classes. Scala torna possível reutilizar a _nova definição de membros de uma classe_ (por exemplo: o relacionamento delta para com a superclasse) na definição de uma nova classe. Isso é expressado como uma _composição de classe mixin ou mixin-class composition_. Considere a seguinte abstração para iterators. - -```tut -abstract class AbsIterator { - type T - def hasNext: Boolean - def next: T -} -``` - -A seguir, considere a classe mixin que estende `AbsIterator` com um método `foreach` que aplica uma dada função para cada elemento retornado pelo iterator. Para definir tal classe que será utilizada como um mixin a palavra-chave `trait` deve ser declarada. - -```tut -trait RichIterator extends AbsIterator { - def foreach(f: T => Unit) { while (hasNext) f(next) } -} -``` - -Aqui uma classes iterator concreta a qual retorna sucessivos caracteres de uma dada string: - -```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 } -} -``` - -Poderíamos combinar a funcionalidade de `StringIterator` e `RichIterator` em uma só classe. Com herança simples e interfaces isso é impossível, pois ambas as classes contém implementações para seus membros. Scala nos ajuda com a sua _composição de classes mixin_. Isso permite que programadores reutilizem o delta de uma definição de uma classe, por exemplo: todas as novas definições não são herdadas. Esse mecanismo torna possível combinar `StringIterator` com `RichIterator`, como pode ser visto no programa teste a seguir, que imprime uma coluna de todos os caracteres de uma dada string. - -```tut -object StringIteratorTest { - def main(args: Array[String]) { - class Iter extends StringIterator(args(0)) with RichIterator - val iter = new Iter - iter foreach println - } -} -``` - -A classe `Iter` na função `main` é construída a partir de uma composição dos pais `StringIterator` e `RichIterator` com a palavra-chave `with`. O primeiro pai é chamado de _superclass_ de `Iter`, já o segundo pai (e qualquer outro que venha após) é chamado de _mixin_ ou mescla. \ No newline at end of file diff --git a/pt-br/tutorials/tour/_posts/2017-02-13-named-parameters.md b/pt-br/tutorials/tour/_posts/2017-02-13-named-parameters.md deleted file mode 100644 index 07a32a3db1..0000000000 --- a/pt-br/tutorials/tour/_posts/2017-02-13-named-parameters.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -layout: tutorial -title: Parâmetros Nomeados - -discourse: false - -tutorial: scala-tour -categories: tour -num: 33 -previous-page: default-parameter-values -language: pt-br ---- - -Ao chamar métodos e funções, você pode utilizar explicitamente o nome das variáveis nas chamadas, por exemplo: - -```tut - def imprimeNome(nome:String, sobrenome:String) = { - println(nome + " " + sobrenome) - } - - imprimeNome("John","Smith") - // Imprime "John Smith" - imprimeNome(nome = "John",sobrenome = "Smith") - // Imprime "John Smith" - imprimeNome(sobrenome = "Smith",nome = "John") - // Imprime "John Smith" -``` - -Perceba que a ordem não importa quando você utiliza parâmetros nomeados nas chamadas de métodos e funções, desde que todos os parâmetros sejam declarados. Essa funcionalidade pode ser combinada com [parâmetros com valor padrão](default-parameter-values.html): - -```tut - def imprimeNome(nome:String = "John", sobrenome:String = "Smith") = { - println(nome + " " + sobrenome) - } - - imprimeNome(sobrenome = "Forbeck") - // Imprime "John Forbeck" -``` - -Dado que é permitido declarar os parâmetros em qualquer ordem, você pode utilizar o valor padrão para parâmetros que aparecem primeiro na lista de parâmetros da função. diff --git a/pt-br/tutorials/tour/_posts/2017-02-13-nested-functions.md b/pt-br/tutorials/tour/_posts/2017-02-13-nested-functions.md deleted file mode 100644 index db5658e591..0000000000 --- a/pt-br/tutorials/tour/_posts/2017-02-13-nested-functions.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -layout: tutorial -title: Funções Aninhadas - -discourse: false - -tutorial: scala-tour -categories: tour -num: 8 -next-page: currying -previous-page: higher-order-functions -language: pt-br ---- - -Em scala é possível aninhar definições de funções. O objeto a seguir fornece uma função `filter` para extrair valores de uma lista de inteiros que são abaixo de um determinado valor: - -```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)) -} -``` - -_Nota: a função aninhada `process` refere-se a variável `threshold` definida em um escopo externo como um parâmetro da função `filter`._ - -A saída gerada pelo programa é: - -``` -List(1,2,3,4) -``` diff --git a/pt-br/tutorials/tour/_posts/2017-02-13-operators.md b/pt-br/tutorials/tour/_posts/2017-02-13-operators.md deleted file mode 100644 index a68f860930..0000000000 --- a/pt-br/tutorials/tour/_posts/2017-02-13-operators.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -layout: tutorial -title: Operadores - -discourse: false - -tutorial: scala-tour -categories: tour -num: 29 -next-page: automatic-closures -previous-page: local-type-inference -language: pt-br ---- - -Qualquer método que tenha um único parâmetro pode ser usado como um *operador infix* em Scala. Aqui está a definição da classe `MyBool` que inclui os métodos `add` e `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) -} -``` - -Agora é possível utilizar as funções `and` and `or` como operadores infix: - -```tut -def not(x: MyBool) = x.negate -def xor(x: MyBool, y: MyBool) = (x or y) and not(x and y) -``` - -Isso ajuda a tornar a definição de `xor` mais legível. - -Aqui está o código correspondente em uma sintaxe de linguagem de programação orientada a objetos mais tradicional: - -```tut -def not(x: MyBool) = x.negate -def xor(x: MyBool, y: MyBool) = x.or(y).and(x.and(y).negate) -``` diff --git a/pt-br/tutorials/tour/_posts/2017-02-13-pattern-matching.md b/pt-br/tutorials/tour/_posts/2017-02-13-pattern-matching.md deleted file mode 100644 index 95c98f3396..0000000000 --- a/pt-br/tutorials/tour/_posts/2017-02-13-pattern-matching.md +++ /dev/null @@ -1,50 +0,0 @@ ---- -layout: tutorial -title: Correspondência de Padrões - -discourse: false - -tutorial: scala-tour -categories: tour -num: 11 - -next-page: singleton-objects -previous-page: case-classes -language: pt-br ---- - -_Nota de tradução: A palavra cujo o significado melhor corresponde a palavra `match` em inglês seria `correspondência`. Também podemos entender que `match` é como "coincidir" ou "concordar" com algo._ - -Scala possui mecanismo de correspondência de padrão embutido. Isso permite realizar o match de qualquer tipo de dados com a política de primeiro match. -Aqui um pequeno exemplo que mostrar como realizar o match de um número inteiro: - -```tut -object MatchTest1 extends App { - def matchTest(x: Int): String = x match { - case 1 => "um" - case 2 => "dois" - case _ => "muitos" - } - println(matchTest(3)) -} -``` - -O bloco com a declaração `case` define a função que mapeia inteiros para strings. A palavra-chave `match` fornece uma maneira conveniente de aplicar uma função (como a função de correspondência de padrões acima) em um objeto. - -Aqui um segundo exemplo no qual o match é realizado em valores de diferentes tipos: - -```tut -object MatchTest2 extends App { - def matchTest(x: Any): Any = x match { - case 1 => "um" - case "dois" => 2 - case y: Int => "scala.Int" - } - println(matchTest("dois")) -} -``` - -O primeiro `case` realiza o match se `x` refere-se a um valor inteiro `1`. O segundo `case` realiza o match se `x` é igual a string `"dois"`. O terceiro `case` é padrão tipado; realiza o match de qualquer valor que seja um inteiro e associa o valor do match de `x` a uma variável `y` do tipo `Int`. - -A correspondência de padrões de Scala é mais útil para realizar os matches de tipos algébricos expressados com [classes case](case-classes.html). -Scala também permite a definição de padrões independentemente de classes case, basta utilizar o método `unapply` em um [objeto extrator](extractor-objects.html). diff --git a/pt-br/tutorials/tour/_posts/2017-02-13-polymorphic-methods.md b/pt-br/tutorials/tour/_posts/2017-02-13-polymorphic-methods.md deleted file mode 100644 index 465abc17b0..0000000000 --- a/pt-br/tutorials/tour/_posts/2017-02-13-polymorphic-methods.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -layout: tutorial -title: Métodos Polimórficos - -discourse: false - -tutorial: scala-tour -categories: tour -num: 27 - -next-page: local-type-inference -previous-page: implicit-conversions -language: pt-br ---- - -Os métodos em Scala podem ser parametrizados com valores e tipos. Como no nível de classe, os parâmetros de valor são declarados entre parênteses, enquanto os parâmetros de tipo são declarados entre colchetes. - -Por exemplo: - -```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)) // primeira chamada -println(dup("three", 3)) // segunda chamada -``` - -O método `dup` é parametrizado com o tipo `T` e com os parâmetros de valor `x: T` e `n: Int`. Na primeira chamada de `dup`, o programador fornece os parâmetros necessários, mas como mostra a seguinte linha, o programador não é obrigado a fornecer explicitamente os parâmetros de tipos. O sistema de tipos de Scala pode inferir tais tipos sem problemas. Isso é feito observando-se os tipos dos parâmetros de valor fornecidos ao método e qual o contexto que o mesmo é chamado. diff --git a/pt-br/tutorials/tour/_posts/2017-02-13-regular-expression-patterns.md b/pt-br/tutorials/tour/_posts/2017-02-13-regular-expression-patterns.md deleted file mode 100644 index 7e08494c91..0000000000 --- a/pt-br/tutorials/tour/_posts/2017-02-13-regular-expression-patterns.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -layout: tutorial -title: Padrões de Expressões Regulares - -discourse: false - -tutorial: scala-tour -categories: tour -num: 14 - -next-page: extractor-objects -previous-page: singleton-objects -language: pt-br ---- - -## Padrões de sequência que ignoram a direita ## - -Padrões de sequência que ignoram a direita são uma funcionalidade útil para decompor qualquer dado sendo ele um subtipo de `Seq[A]` ou uma classe case com um parâmetro iterador formal, como por exemplo: - -``` -Elem(prefix:String, label:String, attrs:MetaData, scp:NamespaceBinding, children:Node*) -``` -Em tais casos, Scala permite que padrões tenham o curinga `_*` na posição mais à direita para ter lugar para sequências arbitrariamente longas. -O exemplo a seguir demonstra um padrão que faz o match de um prefixo de uma sequência e vincula o resto à variável `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 - } - } -} -``` - -Em contraste com versões anteriores Scala, não é mais permitido ter expressões regulares arbitrárias, pelas razões descritas abaixo. - -###Padrões genéricos de expressões regulares `RegExp` temporariamente retirados de Scala### - -Desde que descobrimos um problema de precisão, esse recurso está temporariamente removido da linguagem Scala. Se houver solicitação da comunidade de usuários, poderemos reativá-la de forma aprimorada. - -De acordo com nossa opinião, os padrões de expressões regulares não eram tão úteis para o processamento XML como estimamos. Em aplicações de processamento de XML de vida real, XPath parece uma opção muito melhor. Quando descobrimos que nossos padrões de expressões regulares ou de tradução tinham alguns bugs para padrões raros que são incomuns e difíceis de excluir, escolhemos que seria hora de simplificar a linguagem. - diff --git a/pt-br/tutorials/tour/_posts/2017-02-13-sequence-comprehensions.md b/pt-br/tutorials/tour/_posts/2017-02-13-sequence-comprehensions.md deleted file mode 100644 index 83c6d1d8ee..0000000000 --- a/pt-br/tutorials/tour/_posts/2017-02-13-sequence-comprehensions.md +++ /dev/null @@ -1,70 +0,0 @@ ---- -layout: tutorial -title: Sequence Comprehensions - -discourse: false - -tutorial: scala-tour -categories: tour -num: 16 -next-page: generic-classes -previous-page: extractor-objects -language: pt-br ---- - -Scala oferece uma notação simples para expressar *compreensões de sequência*. As compreensões têm a forma `for (enumerators) yield e`, onde` enumerators` se refere a uma lista de enumeradores separados por ponto-e-vírgula. Um *enumerator* é um gerador que introduz novas variáveis ou é um filtro. A compreensão avalia o corpo `e` para cada associação gerada pelos enumeradores e retorna uma sequência desses valores. - -Por exemplo: - -```tut -object ComprehensionTest1 extends App { - def par(de: Int, ate: Int): List[Int] = - for (i <- List.range(de, ate) if i % 2 == 0) yield i - Console.println(par(0, 20)) -} -``` - -A função for-expression introduz uma nova variável `i` do tipo `Int`, que é subsequentemente associada a todos os valores da lista `List (de, de + 1, ..., ate - 1)`. A restrição `if i% 2 == 0` ignora todos os números ímpares para que o corpo (que só consiste na expressão i) seja avaliado somente para números pares. Consequentemente, toda a expressão `for` retorna uma lista de números pares. - -O programa produz a seguinte saída: - -``` -List(0, 2, 4, 6, 8, 10, 12, 14, 16, 18) -``` - -Agora um exemplo mais complicado que calcula todos os pares de números entre `0` e` n-1` cuja soma é igual a um dado valor `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)") - } -} -``` - -Este exemplo mostra que as compreensões não estão restritas às listas. Pois o programa anterior usa iteradores. Todo tipo de dados que suporta as operações `withFilter`, `map`, e `flatMap` (com os tipos apropriados) pode ser usado em compreensões de sequência. - -Aqui está a saída do programa: - -``` -(13, 19) -(14, 18) -(15, 17) -(16, 16) -``` - -Há também uma forma especial de compreensão de sequência que retorna `Unit`. Aqui as associações que são criadas a partir da lista de geradores e filtros são utilizadas para gerar efeitos colaterais. O programador precisa omitir a palavra-chave `yield` para fazer uso de tal compreensão de sequência. -Aqui está um programa que é equivalente ao anterior, mas usa uma forma especial de for-expression que retorna `Unit`: - -``` -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/pt-br/tutorials/tour/_posts/2017-02-13-singleton-objects.md b/pt-br/tutorials/tour/_posts/2017-02-13-singleton-objects.md deleted file mode 100644 index 2c38e8a28b..0000000000 --- a/pt-br/tutorials/tour/_posts/2017-02-13-singleton-objects.md +++ /dev/null @@ -1,74 +0,0 @@ ---- -layout: tutorial -title: Objetos Singleton - -discourse: false - -tutorial: scala-tour -categories: tour -num: 12 - -next-page: regular-expression-patterns -previous-page: pattern-matching -language: pt-br ---- - -Métodos e valores que não são associados com instâncias individuais de uma [classe](classes.html) são considerados *objetos singleton*, denotados através da palavra-chave `object` ao invés de `class`. - -``` -package test - -object Blah { - def sum(l: List[Int]): Int = l.sum -} -``` - -O método `sum` é disponível globalmente, e pode ser referenciado ou importado como `test.Blah.sum`. - -Objetos Singleton são um tipo de mescla e abreviação para definir uma classe de uso único, a qual não pode ser diretamente instanciada, e um membro `val` durante a definição do `object`. De fato, como `val`, objetos singleton podem ser definidos como membros de uma [trait](traits.html) ou [classe](classes.html), porém isso não é comum. - -Um objeto singleton pode estender classes e traits. Já uma [classe clase](case-classes.html) sem [parâmetros com tipo](generic-classes.html) por padrão irá criar um objeto singleton como o mesmo nome e com uma [`Função*`](http://www.scala-lang.org/api/current/scala/Function1.html) trait implementada. - -## Acompanhantes ## - -A maioria dos objetos singleton não estão sozinhos, mas sim associados com uma classe de mesmo nome. O “objeto singleton de mesmo nome” que uma classe case, acima mencionado, é um exemplo disso. Quando isso acontece, o objeto singleton é chamado de *objeto acompanhante* de uma classe e, a classe é chamada de *classe acompanhante* de um objeto. - -[Scaladoc](https://wiki.scala-lang.org/display/SW/Introduction) possui um recurso especial para navegar entre classes e seus acompanhantes: se o grande círculo contendo “C” ou “O” possui sua extremidade superior dobrada para baixo, você pode clicar no círculo para acessar o acompanhante. - -Se houver um objeto acompanhante para uma classe, ambos devem ser definidos no mesmo aquivo fonte. Por exemplo: - -```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)) -} -``` - -É comum ver instâncias de classes de tipo como [valores implícitos](implicit-parameters.html), como `ipord` acima, definido no objeto acompanhante quando se segue o padrão da *typeclass*. Isso ocorre porque os membros do objeto acompanhante são incluídos por padrão na busca de valores implícitos. - -## Nota para programadores Java ## - -`static` não é uma palavra-chave em Scala. Ao invés disso, todos os membros que devem ser estáticos, incluindo classes, devem ser declarados no objeto singleton. Eles podem ser referenciados com a mesma sintaxe, importados gradativamente ou como um grupo, e assim por diante. - -Frequentemente, os programadores Java definem membros estáticos, talvez `private`, como auxiliares de implementação para seus membros de instância. Estes são movidos para o acompanhante também; Um padrão comum é importar os membros do objeto acompanhante na classe, da seguinte forma: -``` -class X { - import X._ - - def blah = foo -} - -object X { - private def foo = 42 -} -``` - -Isso demonstra outra característica: no contexto `private`, as classes e seus acompanhantes são amigos. `object X` pode acessar membro privados da `class X`, e vice versa. Para fazer com que um membro seja *realmente* para um ou outro, utilize `private[this]`. - -Por uma melhor interoperabilidade com Java, métodos, incluindo `var`s e `val`s, definidos diretamente em um objeto singleton também têm um método estático definido na classe acompanhante, chamado *encaminhadores estáticos*. Outros membros são acessíveis por meio de campos estáticos `X$.MODULE$` para o `object X`. - -Se você mover tudo para um objeto acompanhante e descobrir que tudo o que resta é uma classe que você não deseja que seja instanciada, simplesmente exclua a classe. Encaminhadores estáticos ainda serão criados. \ No newline at end of file diff --git a/pt-br/tutorials/tour/_posts/2017-02-13-tour-of-scala.md b/pt-br/tutorials/tour/_posts/2017-02-13-tour-of-scala.md deleted file mode 100644 index d163572f5f..0000000000 --- a/pt-br/tutorials/tour/_posts/2017-02-13-tour-of-scala.md +++ /dev/null @@ -1,50 +0,0 @@ ---- -layout: tutorial -title: Introdução - -discourse: false - -tutorial: scala-tour -categories: tour -num: 1 -outof: 33 -next-page: unified-types -language: pt-br ---- - -Scala é uma linguagem de programação moderna e multi-paradigma desenvolvida para expressar padrões de programação comuns em uma forma concisa, elegante e com tipagem segura. Integra facilmente características de linguagens orientadas a objetos e funcional. - -## Scala é orientada a objetos ## -Scala é uma linguagem puramente orientada a objetos no sentido que [todo valor é um objeto](unified-types.html). Tipos e comportamentos de objetos são descritos por [classes](classes.html) e [traits](traits.html). Classes são estendidas por subclasses e por um flexível mecanismo [de composição mesclada](mixin-class-composition.html) como uma alternativa para herança múltipla. - -## Scala é funcional ## -Scala é também uma linguagem funcional no sentido que [toda função é um valor](unified-types.html). Scala fornece uma [sintaxe leve](anonymous-function-syntax.html) para definir funções anônimas, suporta [funções de primeira ordem](higher-order-functions.html), permite funções [aninhadas](nested-functions.html), e suporta [currying](currying.html). As [case classes](case-classes.html) da linguagem Scala e o suporte embutido para [correspondência de padrões](pattern-matching.html) modelam tipos algébricos utilizados em muitas linguagens de programação funcional. [Objetos Singleton](singleton-objects.html) fornecem uma alternativa conveniente para agrupar funções que não são membros de uma classe. - -Além disso, a noção de correspondência de padrões em Scala se estende naturalmente ao [processamento de dados de um XML](xml-processing.html) com a ajuda de [expressões regulares](regular-expression-patterns.html), por meio de uma extensão via [objetos extratores](extractor-objects.html). Nesse contexto, [compreensões de sequência](sequence-comprehensions.html) são úteis para formular consultas. Essas funcionalidades tornam Scala ideal para desenvolver aplicações como serviços web. - -## Scala é estaticamente tipada ## -Scala é equipada com um expressivo sistema de tipos que reforça estaticamente que abstrações são utilizadas de uma forma segura e coerente. Particularmente, o sistema de tipos suporta: - -* [Classes genéricas](generic-classes.html) -* [Anotações variáveis](variances.html) -* [Limites de tipos superiores](upper-type-bounds.html) e [limites de tipos inferiores](lower-type-bounds.html), -* [Classes internas](inner-classes.html) e [tipos abstratos](abstract-types.html) como membros de um objeto -* [Tipos compostos](compound-types.html) -* [Auto referências explicitamente tipadas](explicitly-typed-self-references.html) -* [parâmetros implícitos](implicit-parameters.html) e [conversões implícitas](implicit-conversions.html) -* [métodos polimórficos](polymorphic-methods.html) - -Um [mecanismo de inferência de tipo local](local-type-inference.html) se encarrega para que o usuário não seja obrigado a anotar o programa com informações reduntante de tipos. Combinados, esses recursos fornecem uma base poderosa para a reutilização segura de abstrações de programação e para a extensão de tipos seguro do software. - -## Scala é extensível ## - -Na prática, o desenvolvimento de aplicações de um determinado domínio geralmente requer uma linguagem de domínio específico. Scala fornece uma combinação única de mecanismos de linguagem que facilitam a adição suave de novas construções de linguagem na forma de bibliotecas: - -* qualquer método pode ser utilizado como um [operador infix ou postfix](operators.html) -* [closures são construídas automaticamente dependendo do tipo esperado](automatic-closures.html) (tipo alvo). - -Uma utilização conjunta de ambos os recursos facilita a definição de novas instruções sem estender a sintaxe e sem usar meta-programação como macros. - -Scala é projetada para interoperar bem com o popular Java 2 Runtime Environment (JRE). Em particular, a interação com a linguagem de programação orientada a objetos Java é o mais suave possível. Funcionalidades novas do Java como [annotations](annotations.html) e Java generics têm correspondentes diretos em Scala. Esses recursos Scala sem correspondentes Java, como [valor default de parâmetros](default-parameter-values.html) e [parâmetros nomeados](named-parameters.html), compilam de forma semelhante ao Java. Scala tem o mesmo modelo de compilação que Java (compilação separada, carregamento de classe dinâmica) e permite o acesso a milhares de bibliotecas de alta qualidade existentes. - -Continue na próxima página para ler mais. diff --git a/pt-br/tutorials/tour/_posts/2017-02-13-traits.md b/pt-br/tutorials/tour/_posts/2017-02-13-traits.md deleted file mode 100644 index bd7114c4ab..0000000000 --- a/pt-br/tutorials/tour/_posts/2017-02-13-traits.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -layout: tutorial -title: Traits - -discourse: false - -tutorial: scala-tour -categories: tour -num: 4 -next-page: mixin-class-composition -previous-page: classes -language: pt-br ---- - -Similar a interfaces em Java, traits são utilizadas para definir tipos de objetos apenas especificando as assinaturas dos métodos suportados. Como em Java 8, Scala permite que traits sejam parcialmente implementadas; ex. é possível definir uma implementação padrão para alguns métodos. Diferentemente de classes, traits não precisam ter construtores com parâmetros. -Veja o exemplo a seguir: - -```tut -trait Similaridade { - def eSemelhante(x: Any): Boolean - def naoESemelhante(x: Any): Boolean = !eSemelhante(x) -} -``` - -Tal trait consiste em dois métodos `eSemelhante` e `naoESemelhante`. Equanto `eSemelhante` não fornece um método com implementação concreta (que é semelhante ao abstract na linguagem Java), o método `naoESemelhante` define um implementação concreta. Consequentemente, classes que integram essa trait só precisam fornecer uma implementação concreta para o método `eSemelhante`. O comportamento para `naoESemelhante` é herdado diretamente da trait. Traits são tipicamente integradas a uma [classe](classes.html) (ou outras traits) utilizando a [composição mesclada de classes](mixin-class-composition.html): - -```tut -class Point(xc: Int, yc: Int) extends Similaridade { - var x: Int = xc - var y: Int = yc - def eSemelhante(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.eSemelhante(p2)) - println(p1.eSemelhante(p3)) - // Ponto.naoESemelhante foi definido na classe Similaridade - println(p1.naoESemelhante(2)) - println(p1.naoESemelhante(p4)) -} -``` - -Aqui a saída do programa: - -``` -true -false -true -false -``` diff --git a/pt-br/tutorials/tour/_posts/2017-02-13-unified-types.md b/pt-br/tutorials/tour/_posts/2017-02-13-unified-types.md deleted file mode 100644 index 25cf43688a..0000000000 --- a/pt-br/tutorials/tour/_posts/2017-02-13-unified-types.md +++ /dev/null @@ -1,50 +0,0 @@ ---- -layout: tutorial -title: Tipos Unificados - -discourse: false - -tutorial: scala-tour -categories: tour -num: 2 -next-page: classes -previous-page: tour-of-scala -language: pt-br ---- - -Diferente de Java, todos os valores em Scala são objetos (incluindo valores numéricos e funções). Dado que Scala é baseada em classes, todos os valores são instâncias de uma classe. O diagrama a seguir ilustra a hierarquia de classes. - -![Hierarquia de Tipos Scala]({{ site.baseurl }}/resources/images/classhierarchy.img_assist_custom.png) - -## Hierarquia de Tipos Scala ## - -A superclass de todas as classes `scala.Any` tem duas subclasses diretas `scala.AnyVal` e `scala.AnyRef` representando dois mundos de classes distintos: classes de valor e classes de referência. Todas as classes de valor são predefinidas; elas correspondem aos tipos primitivos em linguagens semelhante a Java. Todas as outras classes definem tipos de referência. Classes definidas pelo usuário definem tipos de referência por padrão; por exemplo, tais classes sempre (indiretamente) são subclasses de `scala.AnyRef`. Toda classes definida pelo usuário em Scala implicitamente estende a trait `scala.ScalaObject`. Classes de infraestrutura nas quais Scala está sendo executado (ex. ambiente de execução do Java) não estendem `scala.ScalaObject`. Se utilizar Scala no contexto do ambiente de execução do Java, então `scala.AnyRef` corresponde à `java.lang.Object`. -Observe que o diagrama acima mostra implicitamente as conversões entre as classes de valores. -Este exemplo demonstra que números numbers, caracteres, valores booleanos, e funções são objetos como qualquer outro objeto: - -```tut -object TiposUnificados extends App { - val set = new scala.collection.mutable.LinkedHashSet[Any] - set += "Sou uma string" // adiciona uma string ao set - set += 732 // adiciona um número - set += 'c' // adiciona um caractere - set += true // adiciona um valor booleano - set += main _ // adiciona a função main - val iter: Iterator[Any] = set.iterator - while (iter.hasNext) { - println(iter.next.toString()) - } -} -``` - -O programa declara uma aplicação chamada `TiposUnificados` em forma de um [objeto Singleton](singleton-objects.html) que estende `App`. A aplicação define uma variável local `set` que se refere a uma instância da classe `LinkedHashSet[Any]`. As demais linhas adicionam vários elementos à variável set. Tais elementos devem estar em conformidade com o tipo `Any` que foi declarado para o set. Por fim, são escritas as representações em string de todos os elementos adicionados ao set. - - -Escrita de saída do programa: -``` -Sou uma string -732 -c -true - -``` diff --git a/pt-br/tutorials/tour/_posts/2017-02-13-upper-type-bounds.md b/pt-br/tutorials/tour/_posts/2017-02-13-upper-type-bounds.md deleted file mode 100644 index 027b994d6f..0000000000 --- a/pt-br/tutorials/tour/_posts/2017-02-13-upper-type-bounds.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -layout: tutorial -title: Limitante Superior de Tipos - -discourse: false - -tutorial: scala-tour -categories: tour -num: 19 -next-page: lower-type-bounds -previous-page: variances -language: pt-br ---- - -Em Scala, [parâmetros de tipos](generic-classes.html) e [tipos abstratos](abstract-types.html) podem ser restringidos por um limitante de tipo. Tal limitante de tipo limita os valores concretos de uma variável de tipo e possivelmente revela mais informações sobre os membros de determinados tipos. Um _limitante superiror de tipos_ `T <: A` declare que a variável tipo `T` refere-se a um subtipo do tipo `A`. -Aqui um exemplo que demonstra um limitante superior de tipo para um parâmetro de tipo da classe `Cage`: - -```tut -abstract class Animal { - def name: String -} - -abstract class Pet extends Animal {} - -class Gato extends Pet { - override def name: String = "Gato" -} - -class Cachorro extends Pet { - override def name: String = "Cachorro" -} - -class Leao extends Animal { - override def name: String = "Leao" -} - -class Jaula[P <: Pet](p: P) { - def pet: P = p -} - -object Main extends App { - var jaulaCachorro = new Jaula[Cachorro](new Cachorro) - var jaulaGato = new Jaula[Gato](new Gato) - /* Não é possível colocar Leao em Jaula pois Leao não estende Pet. */ -// var jaulaLeao = new Jaula[Leao](new Leao) -} -``` - -Um instância da classe `Jaula` pode conter um animal, porém com um limite superior do tipo `Pet`. Um animal to tipo `Leao` não é um pet, pois não estende `Pet`, então não pode ser colocado em uma Jaula. - -O uso de limitantes inferiores de tipo é discutido [aqui](lower-type-bounds.html). diff --git a/pt-br/tutorials/tour/_posts/2017-02-13-variances.md b/pt-br/tutorials/tour/_posts/2017-02-13-variances.md deleted file mode 100644 index 1169f07f33..0000000000 --- a/pt-br/tutorials/tour/_posts/2017-02-13-variances.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -layout: tutorial -title: Variâncias - -discourse: false - -tutorial: scala-tour -categories: tour -num: 18 -next-page: upper-type-bounds -previous-page: generic-classes -language: pt-br ---- - -Scala suporta anotações de variância de parâmetros de tipo de [classes genéricas](generic-classes.html). Em contraste com o Java 5, as anotações de variância podem ser adicionadas quando uma abstração de classe é definida, enquanto que em Java 5, as anotações de variância são fornecidas por clientes quando uma abstração de classe é usada. - -Na página sobre [classes genéricas](generic-classes.html) foi dado um exemplo de uma pilha de estado mutável. Explicamos que o tipo definido pela classe `Stack [T]` está sujeito a subtipos invariantes em relação ao parâmetro de tipo. Isso pode restringir a reutilização da abstração de classe. Derivamos agora uma implementação funcional (isto é, imutável) para pilhas que não tem esta restrição. Por favor, note que este é um exemplo avançado que combina o uso de [métodos polimórficos](polymorphic-methods.html), [limites de tipo inferiores](lower-type-bounds.html) e anotações de parâmetros de tipos covariantes em um estilo não trivial. Além disso, fazemos uso de [classes internas](inner-classes.html) para encadear os elementos da pilha sem links explícitos. - -```tut -class Stack[+T] { - def push[S >: T](elem: S): Stack[S] = new Stack[S] { - override def top: S = elem - override def pop: Stack[S] = Stack.this - override def toString: String = - elem.toString + " " + Stack.this.toString - } - def top: T = sys.error("no element on stack") - def pop: Stack[T] = sys.error("no element on stack") - override def toString: String = "" -} - -object VariancesTest extends App { - var s: Stack[Any] = new Stack().push("hello") - s = s.push(new Object()) - s = s.push(7) - println(s) -} -``` - -A anotação `+T` declara o tipo `T` para ser usado somente em posições covariantes. Da mesma forma, `-T` declara que `T` pode ser usado somente em posições contravariantes. Para os parâmetros de tipo covariante obtemos uma relação de sub-tipo covariante em relação ao parâmetro de tipo. Em nosso exemplo, isso significa que `Stack [T]` é um subtipo de `Stack [S]` se `T` for um subtipo de `S`. O oposto é válido para parâmetros de tipo que são marcados com um `-`. - -No exemplo da pilha teríamos que usar o parâmetro de tipo covariante `T` em uma posição contravariante para podermos definir o método `push`. Uma vez que queremos sub-tipagem covariante para pilhas, usamos um truque e abstraímos o tipo de parâmetro do método `push`. Então temos um método polimórfico no qual usamos o tipo de elemento `T` como um limite inferior da variável de tipo da função `push`. Isso faz com que a variância de `T` fique em acordo com sua declaração, como um parâmetro de tipo covariante. Agora as pilhas são covariantes, mas a nossa solução permite que, por exemplo, seja possível inserir uma string em uma pilha de inteiros. O resultado será uma pilha do tipo `Stack [Any]`; Assim detectamos o error somente se o resultado for usado em um contexto onde esperamos uma pilha de números inteiros. Caso contrário, nós apenas criamos uma pilha com um tipo de elemento mais abrangente. diff --git a/reference.md b/reference.md new file mode 100644 index 0000000000..06e00783bc --- /dev/null +++ b/reference.md @@ -0,0 +1,147 @@ +--- +title: Reference +layout: inner-page-no-masthead +redirect_from: + - documentation/api +includeTOC: true +--- + +## Quick Reference +* [Glossary](/glossary/) of Scala terms +* [Cheatsheet](/cheatsheets/) for syntax. This can be useful for quickly picking up the syntax once you understand the basics of the language. + +## Search the API +The searchable API docs below are an invaluable resource as you make use of the Scala libraries. For newcomers to Scala, start with the [Standard Library API for 2.12.2](http://www.scala-lang.org/api/2.12.2/). +### Latest Stable versions + +* Scala 2.12.2 + * [Standard Library API](http://www.scala-lang.org/api/2.12.2/) + * [Compiler API](http://www.scala-lang.org/api/2.12.2/scala-compiler/) + * [Reflection API](http://www.scala-lang.org/api/2.12.2/scala-reflect/#scala.reflect.package) + * Scala Modules + * [XML API](http://www.scala-lang.org/api/2.12.2/scala-xml/#scala.xml.package) + * [Parser Combinators API](http://www.scala-lang.org/api/2.12.2/scala-parser-combinators/) + * [Swing API](http://www.scala-lang.org/api/2.12.2/scala-swing/#scala.swing.package) +* Scala 2.11.8 + * [Standard Library API](http://www.scala-lang.org/api/2.11.8/) + * [Compiler API](http://www.scala-lang.org/api/2.11.8/scala-compiler/) + * [Reflection API](http://www.scala-lang.org/api/2.11.8/scala-reflect/#scala.reflect.package) + * Scala Modules + * [XML API](http://www.scala-lang.org/api/2.11.8/scala-xml/#scala.xml.package) + * [Parser Combinators API](http://www.scala-lang.org/api/2.11.8/scala-parser-combinators/) + * [Actors API](http://www.scala-lang.org/api/2.11.8/scala-actors/#scala.actors.package) (deprecated) + * [Swing API](http://www.scala-lang.org/api/2.11.8/scala-swing/#scala.swing.package) + * [Continuations API](http://www.scala-lang.org/files/archive/api/2.11.8/scala-continuations-library/#scala.util.continuations.package) +* [Scala 2.10.6](http://www.scala-lang.org/api/2.10.6/) + +### Nightly Builds API Search + +* Scala 2.11.x + * [Standard Library API](http://www.scala-lang.org/files/archive/nightly/2.11.x/api/2.11.x/) + * [Compiler API](http://www.scala-lang.org/files/archive/nightly/2.11.x/api/2.11.x/scala-compiler/) +* Scala 2.12.x + * [Standard Library API](http://www.scala-lang.org/files/archive/nightly/2.12.x/api/2.12.x/) + * [Compiler API](http://www.scala-lang.org/files/archive/nightly/2.12.x/api/2.12.x/scala-compiler/) + +### Previous releases +* Scala 2.11.7 + * [Standard Library API](http://www.scala-lang.org/api/2.11.7/) + * [Compiler API](http://www.scala-lang.org/api/2.11.7/scala-compiler/) + * [Reflection API](http://www.scala-lang.org/api/2.11.7/scala-reflect/#scala.reflect.package) + * Scala Modules + * [XML API](http://www.scala-lang.org/api/2.11.7/scala-xml/#scala.xml.package) + * [Parser Combinators API](http://www.scala-lang.org/api/2.11.7/scala-parser-combinators/) + * [Actors API](http://www.scala-lang.org/api/2.11.7/scala-actors/#scala.actors.package) (deprecated) + * [Swing API](http://www.scala-lang.org/api/2.11.7/scala-swing/#scala.swing.package) + * [Continuations API](http://www.scala-lang.org/files/archive/api/2.11.7/scala-continuations-library/#scala.util.continuations.package) +* Scala 2.11.6 + * [Standard Library API](http://www.scala-lang.org/api/2.11.6/) + * [Compiler API](http://www.scala-lang.org/api/2.11.6/scala-compiler/) + * [Reflection API](http://www.scala-lang.org/api/2.11.6/scala-reflect/#scala.reflect.package) + * Scala Modules + * [XML API](http://www.scala-lang.org/api/2.11.6/scala-xml/#scala.xml.package) + * [Parser Combinators API](http://www.scala-lang.org/api/2.11.6/scala-parser-combinators/) + * [Actors API](http://www.scala-lang.org/api/2.11.6/scala-actors/#scala.actors.package) (deprecated) + * [Swing API](http://www.scala-lang.org/api/2.11.6/scala-swing/#scala.swing.package) + * [Continuations API](http://www.scala-lang.org/files/archive/api/2.11.6/scala-continuations-library/#scala.util.continuations.package) +* Scala 2.11.5 + * [Standard Library API](http://www.scala-lang.org/api/2.11.5/) + * [Compiler API](http://www.scala-lang.org/api/2.11.5/scala-compiler/) + * [Reflection API](http://www.scala-lang.org/api/2.11.5/scala-reflect/#scala.reflect.package) + * Scala Modules + * [XML API](http://www.scala-lang.org/api/2.11.5/scala-xml/#scala.xml.package) + * [Parser Combinators API](http://www.scala-lang.org/api/2.11.5/scala-parser-combinators/) + * [Actors API](http://www.scala-lang.org/api/2.11.5/scala-actors/#scala.actors.package) (deprecated) + * [Swing API](http://www.scala-lang.org/api/2.11.5/scala-swing/#scala.swing.package) + * [Continuations API](http://www.scala-lang.org/files/archive/api/2.11.5/scala-continuations-library/#scala.util.continuations.package) +* Scala 2.11.4 + * [Standard Library API](http://www.scala-lang.org/api/2.11.4/) + * [Compiler API](http://www.scala-lang.org/api/2.11.4/scala-compiler/) + * [Reflection API](http://www.scala-lang.org/api/2.11.4/scala-reflect/#scala.reflect.package) + * Scala Modules + * [XML API](http://www.scala-lang.org/api/2.11.4/scala-xml/#scala.xml.package) + * [Parser Combinators API](http://www.scala-lang.org/api/2.11.4/scala-parser-combinators/) + * [Actors API](http://www.scala-lang.org/api/2.11.4/scala-actors/#scala.actors.package) (deprecated) + * [Swing API](http://www.scala-lang.org/api/2.11.4/scala-swing/#scala.swing.package) + * [Continuations API](http://www.scala-lang.org/files/archive/api/2.11.4/scala-continuations-library/#scala.util.continuations.package) +* Scala 2.11.2 + * [Standard Library API](http://www.scala-lang.org/api/2.11.2/) + * [Compiler API](http://www.scala-lang.org/api/2.11.2/scala-compiler/) + * [Reflection API](http://www.scala-lang.org/api/2.11.2/scala-reflect/#scala.reflect.package) + * Scala Modules + * [XML API](http://www.scala-lang.org/api/2.11.2/scala-xml/#scala.xml.package) + * [Parser Combinators API](http://www.scala-lang.org/api/2.11.2/scala-parser-combinators/) + * [Actors API](http://www.scala-lang.org/api/2.11.2/scala-actors/#scala.actors.package) (deprecated) + * [Swing API](http://www.scala-lang.org/api/2.11.2/scala-swing/#scala.swing.package) + * [Continuations API](http://www.scala-lang.org/files/archive/api/2.11.2/scala-continuations-library/#scala.util.continuations.package) +* Scala 2.11.1 + * [Standard Library API](http://www.scala-lang.org/api/2.11.1/) + * [Compiler API](http://www.scala-lang.org/api/2.11.1/scala-compiler/) + * [Reflection API](http://www.scala-lang.org/api/2.11.1/scala-reflect/#scala.reflect.package) + * Scala Modules + * [XML API](http://www.scala-lang.org/api/2.11.1/scala-xml/#scala.xml.package) + * [Parser Combinators API](http://www.scala-lang.org/api/2.11.1/scala-parser-combinators/) + * [Actors API](http://www.scala-lang.org/api/2.11.1/scala-actors/#scala.actors.package) (deprecated) + * [Swing API](http://www.scala-lang.org/api/2.11.1/scala-swing/#scala.swing.package) + * [Continuations API](http://www.scala-lang.org/files/archive/api/2.11.1/scala-continuations-library/#scala.util.continuations.package) +* Scala 2.11.0 + * [Standard Library API](http://www.scala-lang.org/api/2.11.0/) + * [Compiler API](http://www.scala-lang.org/api/2.11.0/scala-compiler/) + * [Reflection API](http://www.scala-lang.org/api/2.11.0/scala-reflect/#scala.reflect.package) + * Scala Modules + * [XML API](http://www.scala-lang.org/api/2.11.0/scala-xml/#scala.xml.package) + * [Parser Combinators API](http://www.scala-lang.org/api/2.11.0/scala-parser-combinators/) + * [Actors API](http://www.scala-lang.org/api/2.11.0/scala-actors/#scala.actors.package) (deprecated) + * [Swing API](http://www.scala-lang.org/api/2.11.0/scala-swing/#scala.swing.package) + * [Continuations API](http://www.scala-lang.org/files/archive/api/2.11.0/scala-continuations-library/#scala.util.continuations.package) +* [Scala 2.10.5](http://www.scala-lang.org/api/2.10.5/) +* [Scala 2.10.4](http://www.scala-lang.org/api/2.10.4/) +* [Scala 2.10.3](http://www.scala-lang.org/api/2.10.3/) +* [Scala 2.10.2](http://www.scala-lang.org/api/2.10.2/) +* [Scala 2.10.1](http://www.scala-lang.org/api/2.10.1/) +* [Scala 2.9.3](http://www.scala-lang.org/api/2.9.3/) +* [Scala 2.9.2](http://www.scala-lang.org/api/2.9.2/) +* [Scala 2.9.1-1](http://www.scala-lang.org/api/2.9.1-1/) +* [Scala 2.9.1.final](http://www.scala-lang.org/api/2.9.1/) +* [Scala 2.9.0.1](http://www.scala-lang.org/api/2.9.0.1/) +* [Scala 2.9.0.final](http://www.scala-lang.org/api/2.9.0/) +* [Scala 2.8.2.final](http://www.scala-lang.org/api/2.8.2/) +* [Scala 2.8.1.final](http://www.scala-lang.org/api/2.8.1/) +* [Scala 2.8.0.final](http://www.scala-lang.org/api/2.8.0/) +* [Scala 2.7.7.final](http://www.scala-lang.org/api/2.7.7/) +* [Scala 2.7.6.final](http://www.scala-lang.org/api/2.7.6/) +* [Scala 2.7.5.final](http://www.scala-lang.org/api/2.7.5/) +* [Scala 2.7.4.final](http://www.scala-lang.org/api/2.7.4/) +* [Scala 2.7.3.final](http://www.scala-lang.org/api/2.7.3/) +* [Scala 2.7.2.final](http://www.scala-lang.org/api/2.7.2/) +* [Scala 2.7.1.final](http://www.scala-lang.org/api/2.7.1/) +* [Scala 2.7.0.final](http://www.scala-lang.org/api/2.7.0/) +* [Scala 2.6.1.final](http://www.scala-lang.org/api/2.6.1/) +* [Scala 2.6.0.final](http://www.scala-lang.org/api/2.6.0/) +* [Scala 2.5.1.final](http://www.scala-lang.org/api/2.5.1/) +* [Scala 2.5.0.final](http://www.scala-lang.org/api/2.5.0/) + +## Language Specification +The language specification is a formal description of the syntax and semantics of the language. +* [Scala 2.12 Language Specification](https://www.scala-lang.org/files/archive/spec/2.12/) +* [Scala 2.11 Language Specification](https://www.scala-lang.org/files/archive/spec/2.11/) diff --git a/resources/stylesheets/bootstrap.css b/resources/css/bootstrap.css similarity index 100% rename from resources/stylesheets/bootstrap.css rename to resources/css/bootstrap.css diff --git a/resources/css/highlightjs.css b/resources/css/highlightjs.css new file mode 100644 index 0000000000..1db8a26ed4 --- /dev/null +++ b/resources/css/highlightjs.css @@ -0,0 +1,102 @@ +/* + +github.com style (c) Vasily Polovnyov + +*/ + +.hljs { + font-family: 'Consolas'; + display: block; + overflow-x: auto; + padding: 0.5em; + color: #333; + background: #fdfdf7; + border-radius: 3px; + border: 1px solid #e7e7d6; +} + +.hljs-comment, +.hljs-quote { + color: #998; + font-style: italic; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-subst { + color: #333; + font-weight: bold; +} + +.hljs-number, +.hljs-literal, +.hljs-variable, +.hljs-template-variable, +.hljs-tag .hljs-attr { + color: #008080; +} + +.hljs-string, +.hljs-doctag { + color: #da322f; +} + +.hljs-title, +.hljs-section, +.hljs-selector-id { + color: #900; + font-weight: bold; +} + +.hljs-subst { + font-weight: normal; +} + +.hljs-type, +.hljs-class .hljs-title { + color: #2f8ad2; + font-weight: bold; +} + +.hljs-tag, +.hljs-name, +.hljs-attribute { + color: #000080; + font-weight: normal; +} + +.hljs-regexp, +.hljs-link { + color: #859a00; +} + +.hljs-symbol, +.hljs-bullet { + color: #990073; +} + +.hljs-built_in, +.hljs-builtin-name { + color: #2f8ad2; +} + +.hljs-meta { + color: #93a1a1; + font-weight: bold; +} + +.hljs-deletion { + background: #fdd; +} + +.hljs-addition { + background: #dfd; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/resources/css/monospace.css b/resources/css/monospace.css new file mode 100644 index 0000000000..67e622e269 --- /dev/null +++ b/resources/css/monospace.css @@ -0,0 +1,42 @@ +--- +--- + +@font-face { + font-family: 'Consolas'; + src: url('{{ site.baseurl }}/resources/glyphs/Consolas.eot'); + src: url('{{ site.baseurl }}/resources/glyphs/Consolas.eot?#iefix') format('embedded-opentype'), + url('{{ site.baseurl }}/resources/glyphs/Consolas.woff') format('woff'), + url('{{ site.baseurl }}/resources/glyphs/Consolas.ttf') format('truetype'); + font-weight: normal; + font-style: normal; +} + +@font-face { + font-family: 'Consolas'; + src: url('{{ site.baseurl }}/resources/glyphs/Consolas-BoldItalic.eot'); + src: url('{{ site.baseurl }}/resources/glyphs/Consolas-BoldItalic.eot?#iefix') format('embedded-opentype'), + url('{{ site.baseurl }}/resources/glyphs/Consolas-BoldItalic.woff') format('woff'), + url('{{ site.baseurl }}/resources/glyphs/Consolas-BoldItalic.ttf') format('truetype'); + font-weight: bold; + font-style: italic; +} + +@font-face { + font-family: 'Consolas'; + src: url('{{ site.baseurl }}/resources/glyphs/Consolas-Italic.eot'); + src: url('{{ site.baseurl }}/resources/glyphs/Consolas-Italic.eot?#iefix') format('embedded-opentype'), + url('{{ site.baseurl }}/resources/glyphs/Consolas-Italic.woff') format('woff'), + url('{{ site.baseurl }}/resources/glyphs/Consolas-Italic.ttf') format('truetype'); + font-weight: normal; + font-style: italic; +} + +@font-face { + font-family: 'Consolas'; + src: url('{{ site.baseurl }}/resources/glyphs/Consolas-Bold.eot'); + src: url('{{ site.baseurl }}/resources/glyphs/Consolas-Bold.eot?#iefix') format('embedded-opentype'), + url('{{ site.baseurl }}/resources/glyphs/Consolas-Bold.woff') format('woff'), + url('{{ site.baseurl }}/resources/glyphs/Consolas-Bold.ttf') format('truetype'); + font-weight: bold; + font-style: normal; +} diff --git a/resources/css/prettify.css b/resources/css/prettify.css new file mode 100644 index 0000000000..3f8eaf2de3 --- /dev/null +++ b/resources/css/prettify.css @@ -0,0 +1,29 @@ + +.prettyprint { background-color: #073642; font-size: 1em; } +.prettyprint code { color: #B0BABC; overflow: auto; } +.prettyprint .pln { color: inherit; } +.prettyprint .str, .prettyprint .atv { color: #5BCCC4; } +.prettyprint .lit { color: #DB5A98;} +.prettyprint .kwd { color: #A4B536; } +.prettyprint .com, +.prettyprint .dec { color: #6B868E; font-style: italic; } +.prettyprint .typ { color: #1C97EF; } +.prettyprint .pun { color: inherit; } +.prettyprint .opn { color: inherit; } +.prettyprint .clo { color: inherit; } +.prettyprint .tag { color: #1C97EF; font-weight: bold; } +.prettyprint .atn { color: inherit; } + +pre.prettyprint { + overflow-x: auto; + padding: 9px; + border: 1px solid #073642; + -webkit-border-radius: 0px 0px 4px 4px; + -moz-border-radius: 0px 0px 4px 4px; + border-radius: 0px 0px 4px 4px; + border-top: 0px; +} + +/* Specify class=linenums on a pre to get line numbering */ +ol.linenums { margin: 0 0 0 40px; } /* IE indents via margin-left */ +ol.linenums > li { color: rgba(67, 94, 101, 0.6); line-height: 20px; width: 100%; s} diff --git a/resources/stylesheets/search.css b/resources/css/search.css similarity index 100% rename from resources/stylesheets/search.css rename to resources/css/search.css diff --git a/resources/css/style.scss b/resources/css/style.scss new file mode 100755 index 0000000000..d6ce6e6aa6 --- /dev/null +++ b/resources/css/style.scss @@ -0,0 +1,72 @@ +--- +# Front matter comment to ensure Jekyll properly reads file. +--- + +// VENDORS +//------------------------------------------------ +@import 'vendors/bourbon/bourbon'; +@import 'vendors/neat/neat'; +@import 'vendors/unslider/unslider'; // UTILS +//------------------------------------------------ +@import 'utils/variables'; +@import 'utils/mixins'; // BASE +//------------------------------------------------ +@import 'base/body'; +@import 'base/form'; +@import 'base/helper'; +@import 'base/lists'; +@import 'base/media'; +@import 'base/typography'; // LAYOUT +//------------------------------------------------ +// Home +@import 'layout/header'; +@import 'layout/inner-text'; +@import 'layout/scala-main-resources'; +@import 'layout/site-main'; +@import 'layout/navigation'; +@import 'layout/doc-navigation'; +@import 'layout/twitter-feed'; +@import 'layout/cheatsheet'; +@import 'layout/ides'; +@import 'layout/nutshell'; +@import 'layout/overviews'; +@import 'layout/sips'; +@import 'layout/toc'; +@import 'layout/glossary'; +@import 'layout/style-guide'; +@import 'layout/courses'; +@import 'layout/documentation'; +@import 'layout/upcoming-events'; +@import 'layout/scala-ecosystem'; +@import 'layout/new-blog'; +@import 'layout/talk-to-us'; +@import 'layout/maintenance'; +@import 'layout/footer'; +@import 'layout/marker'; +@import 'layout/runs'; +@import 'layout/run-scala'; +@import 'layout/scaladex'; // Inner Page +@import 'layout/inner-main'; +@import 'layout/title-page'; +@import 'layout/type-md'; +@import 'layout/table-of-content'; +@import 'layout/tools'; +@import 'layout/books'; +@import 'layout/training-events'; +@import 'layout/blog'; +@import 'layout/download'; // COMPONENTS +//------------------------------------------------ +//------------------------------------------------ +@import 'components/buttons'; +@import 'components/call-to-action'; +@import 'components/slider'; +@import 'components/heading-line'; +@import 'components/card'; +@import 'components/calendar'; +@import 'components/tooltip'; +@import 'components/code'; +@import 'components/pagination'; +@import 'components/tab'; +@import 'components/tag'; +@import 'components/search'; +@import 'components/dropdown'; diff --git a/resources/css/unslider-dots.css b/resources/css/unslider-dots.css new file mode 100755 index 0000000000..65327c0078 --- /dev/null +++ b/resources/css/unslider-dots.css @@ -0,0 +1,33 @@ +/** + * Here's where everything gets included. You don't need + * to change anything here, and doing so might break + * stuff. Here be dragons and all that. + */ +/** + * Default variables + * + * While these can be set with JavaScript, it's probably + * better and faster to just set them here, compile to + * CSS and include that instead to use some of that + * hardware-accelerated goodness. + */ +.unslider-nav ol { + list-style: none; + text-align: center; +} +.unslider-nav ol li { + display: inline-block; + width: 6px; + height: 6px; + margin: 0 4px; + background: transparent; + border-radius: 5px; + overflow: hidden; + text-indent: -999em; + border: 2px solid #fff; + cursor: pointer; +} +.unslider-nav ol li.unslider-active { + background: #fff; + cursor: default; +} diff --git a/resources/css/unslider.css b/resources/css/unslider.css new file mode 100755 index 0000000000..ef41084c06 --- /dev/null +++ b/resources/css/unslider.css @@ -0,0 +1 @@ +.unslider{overflow:auto;margin:0;padding:0}.unslider-wrap{position:relative}.unslider-wrap.unslider-carousel>li{float:left}.unslider-vertical>ul{height:100%}.unslider-vertical li{float:none;width:100%}.unslider-fade{position:relative}.unslider-fade .unslider-wrap li{position:absolute;left:0;top:0;right:0;z-index:8}.unslider-fade .unslider-wrap li.unslider-active{z-index:10}.unslider li,.unslider ol,.unslider ul{list-style:none;margin:0;padding:0;border:none}.unslider-arrow{position:absolute;left:20px;z-index:2;cursor:pointer}.unslider-arrow.next{left:auto;right:20px} \ No newline at end of file diff --git a/resources/css/vendor/codemirror.css b/resources/css/vendor/codemirror.css new file mode 100644 index 0000000000..4e3b46ba60 --- /dev/null +++ b/resources/css/vendor/codemirror.css @@ -0,0 +1,347 @@ +/* BASICS */ + +.CodeMirror { + /* Set height, width, borders, and global font properties here */ + font-family: monospace; + height: 300px; + color: black; +} + +/* PADDING */ + +.CodeMirror-lines { + padding: 4px 0; /* Vertical padding around content */ +} +.CodeMirror pre { + padding: 0 4px; /* Horizontal padding of content */ +} + +.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { + background-color: white; /* The little square between H and V scrollbars */ +} + +/* GUTTER */ + +.CodeMirror-gutters { + border-right: 1px solid #ddd; + background-color: #f7f7f7; + white-space: nowrap; +} +.CodeMirror-linenumbers {} +.CodeMirror-linenumber { + padding: 0 3px 0 0; + min-width: 20px; + text-align: right; + color: #999; + white-space: nowrap; +} + +.CodeMirror-guttermarker { color: black; } +.CodeMirror-guttermarker-subtle { color: #999; } + +/* CURSOR */ + +.CodeMirror-cursor { + border-left: 1px solid black; + border-right: none; + width: 0; +} +/* Shown when moving in bi-directional text */ +.CodeMirror div.CodeMirror-secondarycursor { + border-left: 1px solid silver; +} +.cm-fat-cursor .CodeMirror-cursor { + width: auto; + border: 0 !important; + background: #7e7; +} +.cm-fat-cursor div.CodeMirror-cursors { + z-index: 1; +} + +.cm-animate-fat-cursor { + width: auto; + border: 0; + -webkit-animation: blink 1.06s steps(1) infinite; + -moz-animation: blink 1.06s steps(1) infinite; + animation: blink 1.06s steps(1) infinite; + background-color: #7e7; +} +@-moz-keyframes blink { + 0% {} + 50% { background-color: transparent; } + 100% {} +} +@-webkit-keyframes blink { + 0% {} + 50% { background-color: transparent; } + 100% {} +} +@keyframes blink { + 0% {} + 50% { background-color: transparent; } + 100% {} +} + +/* Can style cursor different in overwrite (non-insert) mode */ +.CodeMirror-overwrite .CodeMirror-cursor {} + +.cm-tab { display: inline-block; text-decoration: inherit; } + +.CodeMirror-rulers { + position: absolute; + left: 0; right: 0; top: -50px; bottom: -20px; + overflow: hidden; +} +.CodeMirror-ruler { + border-left: 1px solid #ccc; + top: 0; bottom: 0; + position: absolute; +} + +/* DEFAULT THEME */ + +.cm-s-default .cm-header {color: blue;} +.cm-s-default .cm-quote {color: #090;} +.cm-negative {color: #d44;} +.cm-positive {color: #292;} +.cm-header, .cm-strong {font-weight: bold;} +.cm-em {font-style: italic;} +.cm-link {text-decoration: underline;} +.cm-strikethrough {text-decoration: line-through;} + +.cm-s-default .cm-keyword {color: #708;} +.cm-s-default .cm-atom {color: #219;} +.cm-s-default .cm-number {color: #164;} +.cm-s-default .cm-def {color: #00f;} +.cm-s-default .cm-variable, +.cm-s-default .cm-punctuation, +.cm-s-default .cm-property, +.cm-s-default .cm-operator {} +.cm-s-default .cm-variable-2 {color: #05a;} +.cm-s-default .cm-variable-3 {color: #085;} +.cm-s-default .cm-comment {color: #a50;} +.cm-s-default .cm-string {color: #a11;} +.cm-s-default .cm-string-2 {color: #f50;} +.cm-s-default .cm-meta {color: #555;} +.cm-s-default .cm-qualifier {color: #555;} +.cm-s-default .cm-builtin {color: #30a;} +.cm-s-default .cm-bracket {color: #997;} +.cm-s-default .cm-tag {color: #170;} +.cm-s-default .cm-attribute {color: #00c;} +.cm-s-default .cm-hr {color: #999;} +.cm-s-default .cm-link {color: #00c;} + +.cm-s-default .cm-error {color: #f00;} +.cm-invalidchar {color: #f00;} + +.CodeMirror-composing { border-bottom: 2px solid; } + +/* Default styles for common addons */ + +div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} +div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} +.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); } +.CodeMirror-activeline-background {background: #e8f2ff;} + +/* STOP */ + +/* The rest of this file contains styles related to the mechanics of + the editor. You probably shouldn't touch them. */ + +.CodeMirror { + position: relative; + overflow: hidden; + background: white; +} + +.CodeMirror-scroll { + overflow: scroll !important; /* Things will break if this is overridden */ + /* 30px is the magic margin used to hide the element's real scrollbars */ + /* See overflow: hidden in .CodeMirror */ + margin-bottom: -30px; margin-right: -30px; + padding-bottom: 30px; + height: 100%; + outline: none; /* Prevent dragging from highlighting the element */ + position: relative; +} +.CodeMirror-sizer { + position: relative; + border-right: 30px solid transparent; +} + +/* The fake, visible scrollbars. Used to force redraw during scrolling + before actual scrolling happens, thus preventing shaking and + flickering artifacts. */ +.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { + position: absolute; + z-index: 6; + display: none; +} +.CodeMirror-vscrollbar { + right: 0; top: 0; + overflow-x: hidden; + overflow-y: scroll; +} +.CodeMirror-hscrollbar { + bottom: 0; left: 0; + overflow-y: hidden; + overflow-x: scroll; +} +.CodeMirror-scrollbar-filler { + right: 0; bottom: 0; +} +.CodeMirror-gutter-filler { + left: 0; bottom: 0; +} + +.CodeMirror-gutters { + position: absolute; left: 0; top: 0; + min-height: 100%; + z-index: 3; +} +.CodeMirror-gutter { + white-space: normal; + height: 100%; + display: inline-block; + vertical-align: top; + margin-bottom: -40px; + /* Hack to make IE7 behave */ + *zoom:1; + *display:inline; +} +.CodeMirror-gutter-wrapper { + position: absolute; + z-index: 4; + background: none !important; + border: none !important; +} +.CodeMirror-gutter-background { + position: absolute; + top: 0; bottom: 0; + z-index: 4; +} +.CodeMirror-gutter-elt { + position: absolute; + cursor: default; + z-index: 4; +} +.CodeMirror-gutter-wrapper { + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; +} + +.CodeMirror-lines { + cursor: text; + min-height: 1px; /* prevents collapsing before first draw */ +} +.CodeMirror pre { + /* Reset some styles that the rest of the page might have set */ + -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; + border-width: 0; + background: transparent; + font-family: inherit; + font-size: inherit; + margin: 0; + white-space: pre; + word-wrap: normal; + line-height: inherit; + color: inherit; + z-index: 2; + position: relative; + overflow: visible; + -webkit-tap-highlight-color: transparent; + -webkit-font-variant-ligatures: none; + font-variant-ligatures: none; +} +.CodeMirror-wrap pre { + word-wrap: break-word; + white-space: pre-wrap; + word-break: normal; +} + +.CodeMirror-linebackground { + position: absolute; + left: 0; right: 0; top: 0; bottom: 0; + z-index: 0; +} + +.CodeMirror-linewidget { + position: relative; + z-index: 2; + overflow: auto; +} + +.CodeMirror-widget {} + +.CodeMirror-code { + outline: none; +} + +/* Force content-box sizing for the elements where we expect it */ +.CodeMirror-scroll, +.CodeMirror-sizer, +.CodeMirror-gutter, +.CodeMirror-gutters, +.CodeMirror-linenumber { + -moz-box-sizing: content-box; + box-sizing: content-box; +} + +.CodeMirror-measure { + position: absolute; + width: 100%; + height: 0; + overflow: hidden; + visibility: hidden; +} + +.CodeMirror-cursor { + position: absolute; + pointer-events: none; +} +.CodeMirror-measure pre { position: static; } + +div.CodeMirror-cursors { + visibility: hidden; + position: relative; + z-index: 3; +} +div.CodeMirror-dragcursors { + visibility: visible; +} + +.CodeMirror-focused div.CodeMirror-cursors { + visibility: visible; +} + +.CodeMirror-selected { background: #d9d9d9; } +.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } +.CodeMirror-crosshair { cursor: crosshair; } +.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; } +.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; } + +.cm-searching { + background: #ffa; + background: rgba(255, 255, 0, .4); +} + +/* IE7 hack to prevent it from returning funny offsetTops on the spans */ +.CodeMirror span { *vertical-align: text-bottom; } + +/* Used to force a border model for a node */ +.cm-force-border { padding-right: .1px; } + +@media print { + /* Hide the cursor when printing */ + .CodeMirror div.CodeMirror-cursors { + visibility: hidden; + } +} + +/* See issue #2901 */ +.cm-tab-wrap-hack:after { content: ''; } + +/* Help users use markselection to safely style text background */ +span.CodeMirror-selectedtext { background: none; } diff --git a/resources/css/vendor/monokai.css b/resources/css/vendor/monokai.css new file mode 100644 index 0000000000..7c8a4c5d00 --- /dev/null +++ b/resources/css/vendor/monokai.css @@ -0,0 +1,36 @@ +/* Based on Sublime Text's Monokai theme */ + +.cm-s-monokai.CodeMirror { background: #272822; color: #f8f8f2; } +.cm-s-monokai div.CodeMirror-selected { background: #49483E; } +.cm-s-monokai .CodeMirror-line::selection, .cm-s-monokai .CodeMirror-line > span::selection, .cm-s-monokai .CodeMirror-line > span > span::selection { background: rgba(73, 72, 62, .99); } +.cm-s-monokai .CodeMirror-line::-moz-selection, .cm-s-monokai .CodeMirror-line > span::-moz-selection, .cm-s-monokai .CodeMirror-line > span > span::-moz-selection { background: rgba(73, 72, 62, .99); } +.cm-s-monokai .CodeMirror-gutters { background: #272822; border-right: 0px; } +.cm-s-monokai .CodeMirror-guttermarker { color: white; } +.cm-s-monokai .CodeMirror-guttermarker-subtle { color: #d0d0d0; } +.cm-s-monokai .CodeMirror-linenumber { color: #d0d0d0; } +.cm-s-monokai .CodeMirror-cursor { border-left: 1px solid #f8f8f0; } + +.cm-s-monokai span.cm-comment { color: #75715e; } +.cm-s-monokai span.cm-atom { color: #ae81ff; } +.cm-s-monokai span.cm-number { color: #ae81ff; } + +.cm-s-monokai span.cm-property, .cm-s-monokai span.cm-attribute { color: #a6e22e; } +.cm-s-monokai span.cm-keyword { color: #f92672; } +.cm-s-monokai span.cm-builtin { color: #66d9ef; } +.cm-s-monokai span.cm-string { color: #e6db74; } + +.cm-s-monokai span.cm-variable { color: #f8f8f2; } +.cm-s-monokai span.cm-variable-2 { color: #9effff; } +.cm-s-monokai span.cm-variable-3 { color: #66d9ef; } +.cm-s-monokai span.cm-def { color: #fd971f; } +.cm-s-monokai span.cm-bracket { color: #f8f8f2; } +.cm-s-monokai span.cm-tag { color: #f92672; } +.cm-s-monokai span.cm-header { color: #ae81ff; } +.cm-s-monokai span.cm-link { color: #ae81ff; } +.cm-s-monokai span.cm-error { background: #f92672; color: #f8f8f0; } + +.cm-s-monokai .CodeMirror-activeline-background { background: #373831; } +.cm-s-monokai .CodeMirror-matchingbracket { + text-decoration: underline; + color: white !important; +} diff --git a/resources/glyphs/Consolas-Bold.eot b/resources/glyphs/Consolas-Bold.eot new file mode 100644 index 0000000000..1981119648 Binary files /dev/null and b/resources/glyphs/Consolas-Bold.eot differ diff --git a/resources/glyphs/Consolas-Bold.ttf b/resources/glyphs/Consolas-Bold.ttf new file mode 100644 index 0000000000..231576b0e9 Binary files /dev/null and b/resources/glyphs/Consolas-Bold.ttf differ diff --git a/resources/glyphs/Consolas-Bold.woff b/resources/glyphs/Consolas-Bold.woff new file mode 100644 index 0000000000..1899dbd063 Binary files /dev/null and b/resources/glyphs/Consolas-Bold.woff differ diff --git a/resources/glyphs/Consolas-BoldItalic.eot b/resources/glyphs/Consolas-BoldItalic.eot new file mode 100644 index 0000000000..107dc0bef7 Binary files /dev/null and b/resources/glyphs/Consolas-BoldItalic.eot differ diff --git a/resources/glyphs/Consolas-BoldItalic.ttf b/resources/glyphs/Consolas-BoldItalic.ttf new file mode 100644 index 0000000000..2a98047bd3 Binary files /dev/null and b/resources/glyphs/Consolas-BoldItalic.ttf differ diff --git a/resources/glyphs/Consolas-BoldItalic.woff b/resources/glyphs/Consolas-BoldItalic.woff new file mode 100644 index 0000000000..828c0282dc Binary files /dev/null and b/resources/glyphs/Consolas-BoldItalic.woff differ diff --git a/resources/glyphs/Consolas-Italic.eot b/resources/glyphs/Consolas-Italic.eot new file mode 100644 index 0000000000..deeed93678 Binary files /dev/null and b/resources/glyphs/Consolas-Italic.eot differ diff --git a/resources/glyphs/Consolas-Italic.ttf b/resources/glyphs/Consolas-Italic.ttf new file mode 100644 index 0000000000..9966bed49f Binary files /dev/null and b/resources/glyphs/Consolas-Italic.ttf differ diff --git a/resources/glyphs/Consolas-Italic.woff b/resources/glyphs/Consolas-Italic.woff new file mode 100644 index 0000000000..9828b0da82 Binary files /dev/null and b/resources/glyphs/Consolas-Italic.woff differ diff --git a/resources/glyphs/Consolas.eot b/resources/glyphs/Consolas.eot new file mode 100644 index 0000000000..3506f34f1e Binary files /dev/null and b/resources/glyphs/Consolas.eot differ diff --git a/resources/glyphs/Consolas.ttf b/resources/glyphs/Consolas.ttf new file mode 100644 index 0000000000..743cbfaf1a Binary files /dev/null and b/resources/glyphs/Consolas.ttf differ diff --git a/resources/glyphs/Consolas.woff b/resources/glyphs/Consolas.woff new file mode 100644 index 0000000000..6078102f80 Binary files /dev/null and b/resources/glyphs/Consolas.woff differ diff --git a/tutorials/tour/Makefile b/resources/images/tour/Makefile similarity index 100% rename from tutorials/tour/Makefile rename to resources/images/tour/Makefile diff --git a/tutorials/tour/type-casting-diagram.dot b/resources/images/tour/type-casting-diagram.dot similarity index 100% rename from tutorials/tour/type-casting-diagram.dot rename to resources/images/tour/type-casting-diagram.dot diff --git a/tutorials/tour/type-casting-diagram.svg b/resources/images/tour/type-casting-diagram.svg similarity index 100% rename from tutorials/tour/type-casting-diagram.svg rename to resources/images/tour/type-casting-diagram.svg diff --git a/tutorials/tour/unified-types-diagram.dot b/resources/images/tour/unified-types-diagram.dot similarity index 100% rename from tutorials/tour/unified-types-diagram.dot rename to resources/images/tour/unified-types-diagram.dot diff --git a/tutorials/tour/unified-types-diagram.svg b/resources/images/tour/unified-types-diagram.svg similarity index 100% rename from tutorials/tour/unified-types-diagram.svg rename to resources/images/tour/unified-types-diagram.svg diff --git a/tutorials/01-post.png b/resources/img/01-post.png similarity index 100% rename from tutorials/01-post.png rename to resources/img/01-post.png diff --git a/tutorials/02-post.png b/resources/img/02-post.png similarity index 100% rename from tutorials/02-post.png rename to resources/img/02-post.png diff --git a/tutorials/03-fork.png b/resources/img/03-fork.png similarity index 100% rename from tutorials/03-fork.png rename to resources/img/03-fork.png diff --git a/tutorials/04-submit.png b/resources/img/04-submit.png similarity index 100% rename from tutorials/04-submit.png rename to resources/img/04-submit.png diff --git a/tutorials/05-review.png b/resources/img/05-review.png similarity index 100% rename from tutorials/05-review.png rename to resources/img/05-review.png diff --git a/resources/img/bee-scala.png b/resources/img/bee-scala.png new file mode 100644 index 0000000000..0fc209b074 Binary files /dev/null and b/resources/img/bee-scala.png differ diff --git a/resources/img/bee-scala@2x.png b/resources/img/bee-scala@2x.png new file mode 100644 index 0000000000..1b45bdd69a Binary files /dev/null and b/resources/img/bee-scala@2x.png differ diff --git a/resources/img/black-topo-pattern.jpg b/resources/img/black-topo-pattern.jpg new file mode 100644 index 0000000000..c035d73505 Binary files /dev/null and b/resources/img/black-topo-pattern.jpg differ diff --git a/resources/img/blog/scaladex/head-project-background.png b/resources/img/blog/scaladex/head-project-background.png new file mode 100644 index 0000000000..a213458a04 Binary files /dev/null and b/resources/img/blog/scaladex/head-project-background.png differ diff --git a/resources/img/blog/scastie/scastie.png b/resources/img/blog/scastie/scastie.png new file mode 100644 index 0000000000..c812d9c7a0 Binary files /dev/null and b/resources/img/blog/scastie/scastie.png differ diff --git a/resources/img/blue-overlay.png b/resources/img/blue-overlay.png new file mode 100644 index 0000000000..4f1def8aec Binary files /dev/null and b/resources/img/blue-overlay.png differ diff --git a/resources/img/books.png b/resources/img/books.png new file mode 100644 index 0000000000..9699ced80c Binary files /dev/null and b/resources/img/books.png differ diff --git a/resources/img/books/BeginningScala.gif b/resources/img/books/BeginningScala.gif new file mode 100644 index 0000000000..49568d3229 Binary files /dev/null and b/resources/img/books/BeginningScala.gif differ diff --git a/resources/img/books/FPiS_93x116.png b/resources/img/books/FPiS_93x116.png new file mode 100644 index 0000000000..c942bd6bab Binary files /dev/null and b/resources/img/books/FPiS_93x116.png differ diff --git a/resources/img/books/Pol_89x116.png b/resources/img/books/Pol_89x116.png new file mode 100644 index 0000000000..0501145113 Binary files /dev/null and b/resources/img/books/Pol_89x116.png differ diff --git a/resources/img/books/ProgScalaJP.gif b/resources/img/books/ProgScalaJP.gif new file mode 100644 index 0000000000..dfb89230c1 Binary files /dev/null and b/resources/img/books/ProgScalaJP.gif differ diff --git a/resources/img/books/ProgrammingInScala.gif b/resources/img/books/ProgrammingInScala.gif new file mode 100644 index 0000000000..06452435db Binary files /dev/null and b/resources/img/books/ProgrammingInScala.gif differ diff --git a/resources/img/books/ProgrammingScala-final-border.gif b/resources/img/books/ProgrammingScala-final-border.gif new file mode 100644 index 0000000000..69f7cb8cad Binary files /dev/null and b/resources/img/books/ProgrammingScala-final-border.gif differ diff --git a/resources/img/books/ProgrammingScala.gif b/resources/img/books/ProgrammingScala.gif new file mode 100644 index 0000000000..2e02454718 Binary files /dev/null and b/resources/img/books/ProgrammingScala.gif differ diff --git a/resources/img/books/ProgrammingScala.jpg b/resources/img/books/ProgrammingScala.jpg new file mode 100644 index 0000000000..07db7ec46a Binary files /dev/null and b/resources/img/books/ProgrammingScala.jpg differ diff --git a/resources/img/books/Scala_in_Action.jpg b/resources/img/books/Scala_in_Action.jpg new file mode 100644 index 0000000000..29784825c7 Binary files /dev/null and b/resources/img/books/Scala_in_Action.jpg differ diff --git a/resources/img/books/Umsteiger.png b/resources/img/books/Umsteiger.png new file mode 100644 index 0000000000..5dcbd1e3f2 Binary files /dev/null and b/resources/img/books/Umsteiger.png differ diff --git a/resources/img/books/buildingRecommendationEngine.jpg b/resources/img/books/buildingRecommendationEngine.jpg new file mode 100644 index 0000000000..ff42b5296a Binary files /dev/null and b/resources/img/books/buildingRecommendationEngine.jpg differ diff --git a/resources/img/books/heiko_117x82.jpg b/resources/img/books/heiko_117x82.jpg new file mode 100644 index 0000000000..8c293271a1 Binary files /dev/null and b/resources/img/books/heiko_117x82.jpg differ diff --git a/resources/img/books/icon_Scala_in_Action_93x116.png b/resources/img/books/icon_Scala_in_Action_93x116.png new file mode 100644 index 0000000000..a2f09a3987 Binary files /dev/null and b/resources/img/books/icon_Scala_in_Action_93x116.png differ diff --git a/resources/img/books/icon_Scala_in_Depth_93x116.png b/resources/img/books/icon_Scala_in_Depth_93x116.png new file mode 100644 index 0000000000..1c273549a4 Binary files /dev/null and b/resources/img/books/icon_Scala_in_Depth_93x116.png differ diff --git a/resources/img/books/icon_funkt_Grundkurs_91x115.png b/resources/img/books/icon_funkt_Grundkurs_91x115.png new file mode 100644 index 0000000000..e80ec4819c Binary files /dev/null and b/resources/img/books/icon_funkt_Grundkurs_91x115.png differ diff --git a/resources/img/books/icon_hanser_90x113.png b/resources/img/books/icon_hanser_90x113.png new file mode 100644 index 0000000000..755a6d1786 Binary files /dev/null and b/resources/img/books/icon_hanser_90x113.png differ diff --git a/resources/img/books/learningConcurrentProgrammingCover120x149.jpg b/resources/img/books/learningConcurrentProgrammingCover120x149.jpg new file mode 100644 index 0000000000..8e3f9d8d47 Binary files /dev/null and b/resources/img/books/learningConcurrentProgrammingCover120x149.jpg differ diff --git a/resources/img/books/puzzlersCover117x89.gif b/resources/img/books/puzzlersCover117x89.gif new file mode 100644 index 0000000000..03424ebce2 Binary files /dev/null and b/resources/img/books/puzzlersCover117x89.gif differ diff --git a/resources/img/books/scala-puzzlers-book.jpg b/resources/img/books/scala-puzzlers-book.jpg new file mode 100644 index 0000000000..2fce14e44c Binary files /dev/null and b/resources/img/books/scala-puzzlers-book.jpg differ diff --git a/resources/img/books/scalaForDataScience.jpg b/resources/img/books/scalaForDataScience.jpg new file mode 100644 index 0000000000..e4b34c5432 Binary files /dev/null and b/resources/img/books/scalaForDataScience.jpg differ diff --git a/resources/img/books/scala_for_the_impatient.png b/resources/img/books/scala_for_the_impatient.png new file mode 100644 index 0000000000..012367038d Binary files /dev/null and b/resources/img/books/scala_for_the_impatient.png differ diff --git a/resources/img/books/steps-v2.gif b/resources/img/books/steps-v2.gif new file mode 100644 index 0000000000..b534c3d61c Binary files /dev/null and b/resources/img/books/steps-v2.gif differ diff --git a/resources/img/books/swedish_86x115.png b/resources/img/books/swedish_86x115.png new file mode 100644 index 0000000000..ce4f66328c Binary files /dev/null and b/resources/img/books/swedish_86x115.png differ diff --git a/resources/img/books@2x.png b/resources/img/books@2x.png new file mode 100644 index 0000000000..63de947a05 Binary files /dev/null and b/resources/img/books@2x.png differ diff --git a/resources/img/carousel-bg.png b/resources/img/carousel-bg.png new file mode 100644 index 0000000000..b545a921e2 Binary files /dev/null and b/resources/img/carousel-bg.png differ diff --git a/resources/img/code-mesh.png b/resources/img/code-mesh.png new file mode 100644 index 0000000000..bf00fdba77 Binary files /dev/null and b/resources/img/code-mesh.png differ diff --git a/resources/img/code-mesh@2x.png b/resources/img/code-mesh@2x.png new file mode 100644 index 0000000000..4e3267cd6c Binary files /dev/null and b/resources/img/code-mesh@2x.png differ diff --git a/resources/img/cufp.png b/resources/img/cufp.png new file mode 100644 index 0000000000..f760a3ec93 Binary files /dev/null and b/resources/img/cufp.png differ diff --git a/resources/img/cufp@2x.png b/resources/img/cufp@2x.png new file mode 100644 index 0000000000..73ab60ff64 Binary files /dev/null and b/resources/img/cufp@2x.png differ diff --git a/resources/img/curryon.png b/resources/img/curryon.png new file mode 100644 index 0000000000..b238b995bf Binary files /dev/null and b/resources/img/curryon.png differ diff --git a/resources/img/curryon@2x.png b/resources/img/curryon@2x.png new file mode 100644 index 0000000000..0ca6d5210b Binary files /dev/null and b/resources/img/curryon@2x.png differ diff --git a/resources/img/data-day.png b/resources/img/data-day.png new file mode 100644 index 0000000000..c2488f6b4c Binary files /dev/null and b/resources/img/data-day.png differ diff --git a/resources/img/data-day@2x.png b/resources/img/data-day@2x.png new file mode 100644 index 0000000000..0232887fda Binary files /dev/null and b/resources/img/data-day@2x.png differ diff --git a/resources/img/date-icon.png b/resources/img/date-icon.png new file mode 100644 index 0000000000..0703749061 Binary files /dev/null and b/resources/img/date-icon.png differ diff --git a/resources/img/date-icon@2x.png b/resources/img/date-icon@2x.png new file mode 100644 index 0000000000..99e4035ded Binary files /dev/null and b/resources/img/date-icon@2x.png differ diff --git a/resources/img/devoxx.png b/resources/img/devoxx.png new file mode 100644 index 0000000000..a736eb550c Binary files /dev/null and b/resources/img/devoxx.png differ diff --git a/resources/img/devoxx@2x.png b/resources/img/devoxx@2x.png new file mode 100644 index 0000000000..a110867f4f Binary files /dev/null and b/resources/img/devoxx@2x.png differ diff --git a/resources/img/diamond.png b/resources/img/diamond.png new file mode 100644 index 0000000000..8e7d44380f Binary files /dev/null and b/resources/img/diamond.png differ diff --git a/resources/img/documentation-logo.png b/resources/img/documentation-logo.png new file mode 100644 index 0000000000..7c6f5750a3 Binary files /dev/null and b/resources/img/documentation-logo.png differ diff --git a/resources/img/documentation-logo@2x.png b/resources/img/documentation-logo@2x.png new file mode 100644 index 0000000000..74d1a689a2 Binary files /dev/null and b/resources/img/documentation-logo@2x.png differ diff --git a/resources/img/dot-grid.png b/resources/img/dot-grid.png new file mode 100644 index 0000000000..7ff16bb434 Binary files /dev/null and b/resources/img/dot-grid.png differ diff --git a/resources/img/dot-grid2.png b/resources/img/dot-grid2.png new file mode 100644 index 0000000000..585e23a2f1 Binary files /dev/null and b/resources/img/dot-grid2.png differ diff --git a/resources/img/dot-grid3.png b/resources/img/dot-grid3.png new file mode 100644 index 0000000000..75c50a207c Binary files /dev/null and b/resources/img/dot-grid3.png differ diff --git a/resources/img/dot-grid4.png b/resources/img/dot-grid4.png new file mode 100644 index 0000000000..cff31546cf Binary files /dev/null and b/resources/img/dot-grid4.png differ diff --git a/resources/img/download.png b/resources/img/download.png new file mode 100644 index 0000000000..9634a7b5c8 Binary files /dev/null and b/resources/img/download.png differ diff --git a/resources/img/download/arrow-asset.png b/resources/img/download/arrow-asset.png new file mode 100644 index 0000000000..a9cd39187f Binary files /dev/null and b/resources/img/download/arrow-asset.png differ diff --git a/resources/img/download/arrow-left.png b/resources/img/download/arrow-left.png new file mode 100644 index 0000000000..cc32eb4968 Binary files /dev/null and b/resources/img/download/arrow-left.png differ diff --git a/resources/img/download/arrow-right.png b/resources/img/download/arrow-right.png new file mode 100644 index 0000000000..3be417210a Binary files /dev/null and b/resources/img/download/arrow-right.png differ diff --git a/resources/img/epfl-bc.jpg b/resources/img/epfl-bc.jpg new file mode 100644 index 0000000000..d2e952710c Binary files /dev/null and b/resources/img/epfl-bc.jpg differ diff --git a/resources/img/epfl-logo.png b/resources/img/epfl-logo.png new file mode 100644 index 0000000000..684289c92d Binary files /dev/null and b/resources/img/epfl-logo.png differ diff --git a/resources/img/epfl-logo@2x.png b/resources/img/epfl-logo@2x.png new file mode 100644 index 0000000000..4875f8e2b3 Binary files /dev/null and b/resources/img/epfl-logo@2x.png differ diff --git a/resources/img/fby.png b/resources/img/fby.png new file mode 100644 index 0000000000..21106797e0 Binary files /dev/null and b/resources/img/fby.png differ diff --git a/resources/img/flatmap-oslo-17.png b/resources/img/flatmap-oslo-17.png new file mode 100644 index 0000000000..d308921dbd Binary files /dev/null and b/resources/img/flatmap-oslo-17.png differ diff --git a/resources/img/flatmap-oslo-17@2x.png b/resources/img/flatmap-oslo-17@2x.png new file mode 100644 index 0000000000..7f7fa67203 Binary files /dev/null and b/resources/img/flatmap-oslo-17@2x.png differ diff --git a/resources/img/flatmap-oslo-new.png b/resources/img/flatmap-oslo-new.png new file mode 100644 index 0000000000..ad0b6fa7db Binary files /dev/null and b/resources/img/flatmap-oslo-new.png differ diff --git a/resources/img/flatmap-oslo-new@2x.png b/resources/img/flatmap-oslo-new@2x.png new file mode 100644 index 0000000000..5f22587d1f Binary files /dev/null and b/resources/img/flatmap-oslo-new@2x.png differ diff --git a/resources/img/flatmap-oslo.png b/resources/img/flatmap-oslo.png new file mode 100644 index 0000000000..805fcfcf0c Binary files /dev/null and b/resources/img/flatmap-oslo.png differ diff --git a/resources/img/flatmap-oslo@2x.png b/resources/img/flatmap-oslo@2x.png new file mode 100644 index 0000000000..7f499f7046 Binary files /dev/null and b/resources/img/flatmap-oslo@2x.png differ diff --git a/resources/img/fp-exchange.png b/resources/img/fp-exchange.png new file mode 100644 index 0000000000..8ff0cc4688 Binary files /dev/null and b/resources/img/fp-exchange.png differ diff --git a/resources/img/fp-exchange@2x.png b/resources/img/fp-exchange@2x.png new file mode 100644 index 0000000000..64355aac45 Binary files /dev/null and b/resources/img/fp-exchange@2x.png differ diff --git a/resources/img/frontpage/47deg-logo.png b/resources/img/frontpage/47deg-logo.png new file mode 100644 index 0000000000..899ecb0511 Binary files /dev/null and b/resources/img/frontpage/47deg-logo.png differ diff --git a/resources/img/frontpage/arrow.png b/resources/img/frontpage/arrow.png new file mode 100644 index 0000000000..2eeda9d2aa Binary files /dev/null and b/resources/img/frontpage/arrow.png differ diff --git a/resources/img/frontpage/atom.png b/resources/img/frontpage/atom.png new file mode 100644 index 0000000000..4f05b1b5e0 Binary files /dev/null and b/resources/img/frontpage/atom.png differ diff --git a/resources/img/frontpage/avatar-twitter-feed.jpg b/resources/img/frontpage/avatar-twitter-feed.jpg new file mode 100644 index 0000000000..aa0af27750 Binary files /dev/null and b/resources/img/frontpage/avatar-twitter-feed.jpg differ diff --git a/resources/img/frontpage/background-header-home.jpg b/resources/img/frontpage/background-header-home.jpg new file mode 100644 index 0000000000..f62bdf49b7 Binary files /dev/null and b/resources/img/frontpage/background-header-home.jpg differ diff --git a/resources/img/frontpage/background-scala-ecosystem.png b/resources/img/frontpage/background-scala-ecosystem.png new file mode 100644 index 0000000000..1b12c55dc5 Binary files /dev/null and b/resources/img/frontpage/background-scala-ecosystem.png differ diff --git a/resources/img/frontpage/background.png b/resources/img/frontpage/background.png new file mode 100644 index 0000000000..e635082643 Binary files /dev/null and b/resources/img/frontpage/background.png differ diff --git a/resources/img/frontpage/beta.png b/resources/img/frontpage/beta.png new file mode 100644 index 0000000000..66739886c0 Binary files /dev/null and b/resources/img/frontpage/beta.png differ diff --git a/resources/img/frontpage/button-github.png b/resources/img/frontpage/button-github.png new file mode 100644 index 0000000000..2d3a32704f Binary files /dev/null and b/resources/img/frontpage/button-github.png differ diff --git a/resources/img/frontpage/company-logo.png b/resources/img/frontpage/company-logo.png new file mode 100644 index 0000000000..4a939959ce Binary files /dev/null and b/resources/img/frontpage/company-logo.png differ diff --git a/resources/img/frontpage/coursera-icon.png b/resources/img/frontpage/coursera-icon.png new file mode 100644 index 0000000000..f20b0f1b9f Binary files /dev/null and b/resources/img/frontpage/coursera-icon.png differ diff --git a/resources/img/frontpage/discourse-logo.png b/resources/img/frontpage/discourse-logo.png new file mode 100644 index 0000000000..7250daa5ac Binary files /dev/null and b/resources/img/frontpage/discourse-logo.png differ diff --git a/resources/img/frontpage/eclipse.png b/resources/img/frontpage/eclipse.png new file mode 100644 index 0000000000..431717679e Binary files /dev/null and b/resources/img/frontpage/eclipse.png differ diff --git a/resources/img/frontpage/edx-icon.png b/resources/img/frontpage/edx-icon.png new file mode 100644 index 0000000000..23b882cb68 Binary files /dev/null and b/resources/img/frontpage/edx-icon.png differ diff --git a/resources/img/frontpage/ensime.png b/resources/img/frontpage/ensime.png new file mode 100644 index 0000000000..d5a2f85986 Binary files /dev/null and b/resources/img/frontpage/ensime.png differ diff --git a/resources/img/frontpage/epfl-bc.jpg b/resources/img/frontpage/epfl-bc.jpg new file mode 100644 index 0000000000..d2e952710c Binary files /dev/null and b/resources/img/frontpage/epfl-bc.jpg differ diff --git a/resources/img/frontpage/epfl-logo.png b/resources/img/frontpage/epfl-logo.png new file mode 100644 index 0000000000..5ea0436848 Binary files /dev/null and b/resources/img/frontpage/epfl-logo.png differ diff --git a/resources/img/frontpage/gitter-logo.png b/resources/img/frontpage/gitter-logo.png new file mode 100644 index 0000000000..ff91a264d4 Binary files /dev/null and b/resources/img/frontpage/gitter-logo.png differ diff --git a/resources/img/frontpage/goldman-logo.png b/resources/img/frontpage/goldman-logo.png new file mode 100644 index 0000000000..68b9d7ebe8 Binary files /dev/null and b/resources/img/frontpage/goldman-logo.png differ diff --git a/resources/img/frontpage/ibm-logo.png b/resources/img/frontpage/ibm-logo.png new file mode 100644 index 0000000000..fa2163825c Binary files /dev/null and b/resources/img/frontpage/ibm-logo.png differ diff --git a/resources/img/frontpage/icon-ensime.png b/resources/img/frontpage/icon-ensime.png new file mode 100644 index 0000000000..b966d91bad Binary files /dev/null and b/resources/img/frontpage/icon-ensime.png differ diff --git a/resources/img/frontpage/intelliJ.png b/resources/img/frontpage/intelliJ.png new file mode 100644 index 0000000000..9fb8863a95 Binary files /dev/null and b/resources/img/frontpage/intelliJ.png differ diff --git a/resources/img/frontpage/java-logo.png b/resources/img/frontpage/java-logo.png new file mode 100644 index 0000000000..0a8dae24eb Binary files /dev/null and b/resources/img/frontpage/java-logo.png differ diff --git a/resources/img/frontpage/js-logo.png b/resources/img/frontpage/js-logo.png new file mode 100644 index 0000000000..c231ecff7e Binary files /dev/null and b/resources/img/frontpage/js-logo.png differ diff --git a/resources/img/frontpage/lightbend-logo.png b/resources/img/frontpage/lightbend-logo.png new file mode 100644 index 0000000000..0ad2844e1b Binary files /dev/null and b/resources/img/frontpage/lightbend-logo.png differ diff --git a/resources/img/frontpage/nitro-logo.png b/resources/img/frontpage/nitro-logo.png new file mode 100644 index 0000000000..7eeea1ad0f Binary files /dev/null and b/resources/img/frontpage/nitro-logo.png differ diff --git a/resources/img/frontpage/sap-logo.png b/resources/img/frontpage/sap-logo.png new file mode 100644 index 0000000000..10d2c001b8 Binary files /dev/null and b/resources/img/frontpage/sap-logo.png differ diff --git a/resources/img/frontpage/scala-logo-white.png b/resources/img/frontpage/scala-logo-white.png new file mode 100644 index 0000000000..c8045b1a34 Binary files /dev/null and b/resources/img/frontpage/scala-logo-white.png differ diff --git a/resources/img/frontpage/scala-logo-white@2x.png b/resources/img/frontpage/scala-logo-white@2x.png new file mode 100644 index 0000000000..364109a994 Binary files /dev/null and b/resources/img/frontpage/scala-logo-white@2x.png differ diff --git a/resources/img/frontpage/scala-spiral.png b/resources/img/frontpage/scala-spiral.png new file mode 100644 index 0000000000..8280fd4bfc Binary files /dev/null and b/resources/img/frontpage/scala-spiral.png differ diff --git a/resources/img/frontpage/scalacenter-logo.png b/resources/img/frontpage/scalacenter-logo.png new file mode 100644 index 0000000000..87925b3841 Binary files /dev/null and b/resources/img/frontpage/scalacenter-logo.png differ diff --git a/resources/img/frontpage/scaladex-logo.png b/resources/img/frontpage/scaladex-logo.png new file mode 100644 index 0000000000..4ffd75478c Binary files /dev/null and b/resources/img/frontpage/scaladex-logo.png differ diff --git a/resources/img/frontpage/sublime.png b/resources/img/frontpage/sublime.png new file mode 100644 index 0000000000..fd4547a66f Binary files /dev/null and b/resources/img/frontpage/sublime.png differ diff --git a/resources/img/frontpage/tapad-logo.png b/resources/img/frontpage/tapad-logo.png new file mode 100644 index 0000000000..1c7baa98da Binary files /dev/null and b/resources/img/frontpage/tapad-logo.png differ diff --git a/resources/img/frontpage/verizon-logo.png b/resources/img/frontpage/verizon-logo.png new file mode 100644 index 0000000000..5cc0171378 Binary files /dev/null and b/resources/img/frontpage/verizon-logo.png differ diff --git a/resources/img/funcconf.png b/resources/img/funcconf.png new file mode 100644 index 0000000000..73748d99b5 Binary files /dev/null and b/resources/img/funcconf.png differ diff --git a/resources/img/funcconf@2x.png b/resources/img/funcconf@2x.png new file mode 100644 index 0000000000..2c57c95c8f Binary files /dev/null and b/resources/img/funcconf@2x.png differ diff --git a/resources/img/gears.png b/resources/img/gears.png new file mode 100644 index 0000000000..e4585c40a3 Binary files /dev/null and b/resources/img/gears.png differ diff --git a/resources/img/gears@2x.png b/resources/img/gears@2x.png new file mode 100644 index 0000000000..2deca55e2f Binary files /dev/null and b/resources/img/gears@2x.png differ diff --git a/resources/img/github-logo.png b/resources/img/github-logo.png new file mode 100644 index 0000000000..069ec6403c Binary files /dev/null and b/resources/img/github-logo.png differ diff --git a/resources/img/github-logo@2x.png b/resources/img/github-logo@2x.png new file mode 100644 index 0000000000..285b0fee2f Binary files /dev/null and b/resources/img/github-logo@2x.png differ diff --git a/resources/img/glyphicons-halflings-white.png b/resources/img/glyphicons-halflings-white.png new file mode 100644 index 0000000000..a20760bfde Binary files /dev/null and b/resources/img/glyphicons-halflings-white.png differ diff --git a/resources/img/glyphicons-halflings.png b/resources/img/glyphicons-halflings.png new file mode 100644 index 0000000000..92d4445dfd Binary files /dev/null and b/resources/img/glyphicons-halflings.png differ diff --git a/resources/img/gray-line.png b/resources/img/gray-line.png new file mode 100644 index 0000000000..c3d7106639 Binary files /dev/null and b/resources/img/gray-line.png differ diff --git a/resources/img/gsoc-2014-scala-logo.png b/resources/img/gsoc-2014-scala-logo.png new file mode 100644 index 0000000000..a417916af4 Binary files /dev/null and b/resources/img/gsoc-2014-scala-logo.png differ diff --git a/resources/img/hof-code.png b/resources/img/hof-code.png new file mode 100644 index 0000000000..b9a88a247a Binary files /dev/null and b/resources/img/hof-code.png differ diff --git a/resources/img/hof-code1.png b/resources/img/hof-code1.png new file mode 100644 index 0000000000..791f5753e3 Binary files /dev/null and b/resources/img/hof-code1.png differ diff --git a/resources/img/hof-code2.png b/resources/img/hof-code2.png new file mode 100644 index 0000000000..833dc30494 Binary files /dev/null and b/resources/img/hof-code2.png differ diff --git a/resources/img/icfp.png b/resources/img/icfp.png new file mode 100644 index 0000000000..05dd68ad1e Binary files /dev/null and b/resources/img/icfp.png differ diff --git a/resources/img/icfp@2x.png b/resources/img/icfp@2x.png new file mode 100644 index 0000000000..f23faa97b8 Binary files /dev/null and b/resources/img/icfp@2x.png differ diff --git a/resources/img/icon-announcement.png b/resources/img/icon-announcement.png new file mode 100644 index 0000000000..7b5021b587 Binary files /dev/null and b/resources/img/icon-announcement.png differ diff --git a/resources/img/icon-dsls.png b/resources/img/icon-dsls.png new file mode 100644 index 0000000000..7d07373dc9 Binary files /dev/null and b/resources/img/icon-dsls.png differ diff --git a/resources/img/icon-functions.png b/resources/img/icon-functions.png new file mode 100644 index 0000000000..ca7ec457a6 Binary files /dev/null and b/resources/img/icon-functions.png differ diff --git a/resources/img/icon-immutability.png b/resources/img/icon-immutability.png new file mode 100644 index 0000000000..6b8822263c Binary files /dev/null and b/resources/img/icon-immutability.png differ diff --git a/resources/img/icon-implicits.png b/resources/img/icon-implicits.png new file mode 100644 index 0000000000..4ce6f73e45 Binary files /dev/null and b/resources/img/icon-implicits.png differ diff --git a/resources/img/icon-java-interop.png b/resources/img/icon-java-interop.png new file mode 100644 index 0000000000..9db39f2a91 Binary files /dev/null and b/resources/img/icon-java-interop.png differ diff --git a/resources/img/icon-parallelism-distribution.png b/resources/img/icon-parallelism-distribution.png new file mode 100644 index 0000000000..e9ef8deee2 Binary files /dev/null and b/resources/img/icon-parallelism-distribution.png differ diff --git a/resources/img/icon-pattern-matching.png b/resources/img/icon-pattern-matching.png new file mode 100644 index 0000000000..dcd290a589 Binary files /dev/null and b/resources/img/icon-pattern-matching.png differ diff --git a/resources/img/icon-traits.png b/resources/img/icon-traits.png new file mode 100644 index 0000000000..01c427674e Binary files /dev/null and b/resources/img/icon-traits.png differ diff --git a/resources/img/icon-type-inference.png b/resources/img/icon-type-inference.png new file mode 100644 index 0000000000..20689b977a Binary files /dev/null and b/resources/img/icon-type-inference.png differ diff --git a/resources/img/img-overlay.png b/resources/img/img-overlay.png new file mode 100644 index 0000000000..212771582f Binary files /dev/null and b/resources/img/img-overlay.png differ diff --git a/resources/img/katsconf.png b/resources/img/katsconf.png new file mode 100644 index 0000000000..59a6198dbf Binary files /dev/null and b/resources/img/katsconf.png differ diff --git a/resources/img/katsconf@2x.png b/resources/img/katsconf@2x.png new file mode 100644 index 0000000000..43f179f6a8 Binary files /dev/null and b/resources/img/katsconf@2x.png differ diff --git a/resources/img/lac-leman.jpg b/resources/img/lac-leman.jpg new file mode 100644 index 0000000000..8a27cc03aa Binary files /dev/null and b/resources/img/lac-leman.jpg differ diff --git a/resources/img/lambda-days.png b/resources/img/lambda-days.png new file mode 100644 index 0000000000..ae7d3ddb0b Binary files /dev/null and b/resources/img/lambda-days.png differ diff --git a/resources/img/lambda-days2.png b/resources/img/lambda-days2.png new file mode 100644 index 0000000000..f395184552 Binary files /dev/null and b/resources/img/lambda-days2.png differ diff --git a/resources/img/lambda-days2@2x.png b/resources/img/lambda-days2@2x.png new file mode 100644 index 0000000000..7386c55504 Binary files /dev/null and b/resources/img/lambda-days2@2x.png differ diff --git a/resources/img/lambda-days@2x.png b/resources/img/lambda-days@2x.png new file mode 100644 index 0000000000..eab3620aa8 Binary files /dev/null and b/resources/img/lambda-days@2x.png differ diff --git a/resources/img/lambda-jam.png b/resources/img/lambda-jam.png new file mode 100644 index 0000000000..59fd5eb177 Binary files /dev/null and b/resources/img/lambda-jam.png differ diff --git a/resources/img/lambda-jam@2x.png b/resources/img/lambda-jam@2x.png new file mode 100644 index 0000000000..4254ccbfb6 Binary files /dev/null and b/resources/img/lambda-jam@2x.png differ diff --git a/resources/img/lambda-world.png b/resources/img/lambda-world.png new file mode 100644 index 0000000000..9e32fd2eb8 Binary files /dev/null and b/resources/img/lambda-world.png differ diff --git a/resources/img/lambda-world@2x.png b/resources/img/lambda-world@2x.png new file mode 100644 index 0000000000..57fc4dae4b Binary files /dev/null and b/resources/img/lambda-world@2x.png differ diff --git a/resources/img/lambdaconf.png b/resources/img/lambdaconf.png new file mode 100644 index 0000000000..31213d7553 Binary files /dev/null and b/resources/img/lambdaconf.png differ diff --git a/resources/img/lambdaconf@2x.png b/resources/img/lambdaconf@2x.png new file mode 100644 index 0000000000..869cbf6767 Binary files /dev/null and b/resources/img/lambdaconf@2x.png differ diff --git a/resources/img/list.png b/resources/img/list.png new file mode 100644 index 0000000000..c5c077c1a1 Binary files /dev/null and b/resources/img/list.png differ diff --git a/resources/img/list@2x.png b/resources/img/list@2x.png new file mode 100644 index 0000000000..c3e320dc55 Binary files /dev/null and b/resources/img/list@2x.png differ diff --git a/resources/img/logos/Apple_logo.png b/resources/img/logos/Apple_logo.png new file mode 100644 index 0000000000..0e03986fe6 Binary files /dev/null and b/resources/img/logos/Apple_logo.png differ diff --git a/resources/img/logos/Apple_logo_black.png b/resources/img/logos/Apple_logo_black.png new file mode 100644 index 0000000000..20ad8d9f46 Binary files /dev/null and b/resources/img/logos/Apple_logo_black.png differ diff --git a/resources/img/logos/Apple_logo_black.svg b/resources/img/logos/Apple_logo_black.svg new file mode 100644 index 0000000000..a2d2a5f784 --- /dev/null +++ b/resources/img/logos/Apple_logo_black.svg @@ -0,0 +1,26 @@ + + + + + + + image/svg+xml + + Apple Logo + + + + + + + diff --git a/resources/img/logos/Bsd_daemon.png b/resources/img/logos/Bsd_daemon.png new file mode 100644 index 0000000000..ba5a976fec Binary files /dev/null and b/resources/img/logos/Bsd_daemon.png differ diff --git a/resources/img/logos/Bsd_daemon.svg b/resources/img/logos/Bsd_daemon.svg new file mode 100644 index 0000000000..1a1e9ecece --- /dev/null +++ b/resources/img/logos/Bsd_daemon.svg @@ -0,0 +1,146 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + diff --git a/resources/img/logos/Bsd_daemon_BW.png b/resources/img/logos/Bsd_daemon_BW.png new file mode 100644 index 0000000000..c7c1955497 Binary files /dev/null and b/resources/img/logos/Bsd_daemon_BW.png differ diff --git a/resources/img/logos/Cygwin_logo.png b/resources/img/logos/Cygwin_logo.png new file mode 100644 index 0000000000..3e9a446a98 Binary files /dev/null and b/resources/img/logos/Cygwin_logo.png differ diff --git a/resources/img/logos/Cygwin_logo.svg b/resources/img/logos/Cygwin_logo.svg new file mode 100644 index 0000000000..f3a56c33b2 --- /dev/null +++ b/resources/img/logos/Cygwin_logo.svg @@ -0,0 +1,69 @@ + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/resources/img/logos/Tux.png b/resources/img/logos/Tux.png new file mode 100644 index 0000000000..da471acdea Binary files /dev/null and b/resources/img/logos/Tux.png differ diff --git a/resources/img/logos/Tux.svg b/resources/img/logos/Tux.svg new file mode 100644 index 0000000000..eec66c5868 --- /dev/null +++ b/resources/img/logos/Tux.svg @@ -0,0 +1,4221 @@ + + + + + + image/svg+xml + + Tux + + + + + + Tux + For more information see: http://commons.wikimedia.org/wiki/Image:Tux.svg + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/img/logos/Tux_BW.png b/resources/img/logos/Tux_BW.png new file mode 100644 index 0000000000..c6bac2c88e Binary files /dev/null and b/resources/img/logos/Tux_BW.png differ diff --git a/resources/img/logos/Windows_logo.png b/resources/img/logos/Windows_logo.png new file mode 100644 index 0000000000..9cef608605 Binary files /dev/null and b/resources/img/logos/Windows_logo.png differ diff --git a/resources/img/logos/Windows_logo.svg b/resources/img/logos/Windows_logo.svg new file mode 100644 index 0000000000..ad5171a793 --- /dev/null +++ b/resources/img/logos/Windows_logo.svg @@ -0,0 +1,363 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/img/logos/Windows_logo_BW.png b/resources/img/logos/Windows_logo_BW.png new file mode 100644 index 0000000000..98e3766992 Binary files /dev/null and b/resources/img/logos/Windows_logo_BW.png differ diff --git a/resources/img/nescala-logo.png b/resources/img/nescala-logo.png new file mode 100644 index 0000000000..84e7bff7e7 Binary files /dev/null and b/resources/img/nescala-logo.png differ diff --git a/resources/img/nescala.png b/resources/img/nescala.png new file mode 100644 index 0000000000..e3dbe8e02f Binary files /dev/null and b/resources/img/nescala.png differ diff --git a/resources/img/nescala@2x.png b/resources/img/nescala@2x.png new file mode 100644 index 0000000000..bb4c3b5099 Binary files /dev/null and b/resources/img/nescala@2x.png differ diff --git a/resources/img/patmat-code.png b/resources/img/patmat-code.png new file mode 100644 index 0000000000..39b8bd150c Binary files /dev/null and b/resources/img/patmat-code.png differ diff --git a/resources/img/pdf-red.png b/resources/img/pdf-red.png new file mode 100644 index 0000000000..aa8ac9e69e Binary files /dev/null and b/resources/img/pdf-red.png differ diff --git a/resources/img/pdf-red@2x.png b/resources/img/pdf-red@2x.png new file mode 100644 index 0000000000..b306c54a3e Binary files /dev/null and b/resources/img/pdf-red@2x.png differ diff --git a/resources/img/pdf.png b/resources/img/pdf.png new file mode 100644 index 0000000000..dfb16e7f55 Binary files /dev/null and b/resources/img/pdf.png differ diff --git a/resources/img/pdf@2x.png b/resources/img/pdf@2x.png new file mode 100644 index 0000000000..1e69243f24 Binary files /dev/null and b/resources/img/pdf@2x.png differ diff --git a/resources/img/phillyete2014.png b/resources/img/phillyete2014.png new file mode 100644 index 0000000000..88cf0dc94b Binary files /dev/null and b/resources/img/phillyete2014.png differ diff --git a/resources/img/phillyete2014@2x.png b/resources/img/phillyete2014@2x.png new file mode 100644 index 0000000000..0e6af854ef Binary files /dev/null and b/resources/img/phillyete2014@2x.png differ diff --git a/resources/img/pnwscala.png b/resources/img/pnwscala.png new file mode 100644 index 0000000000..2005838cc1 Binary files /dev/null and b/resources/img/pnwscala.png differ diff --git a/resources/img/pnwscala@2x.png b/resources/img/pnwscala@2x.png new file mode 100644 index 0000000000..696b23d9e1 Binary files /dev/null and b/resources/img/pnwscala@2x.png differ diff --git a/resources/img/react.png b/resources/img/react.png new file mode 100644 index 0000000000..354898b2a3 Binary files /dev/null and b/resources/img/react.png differ diff --git a/resources/img/react@2x.png b/resources/img/react@2x.png new file mode 100644 index 0000000000..1025ea1369 Binary files /dev/null and b/resources/img/react@2x.png differ diff --git a/resources/img/reactivesummit2016.png b/resources/img/reactivesummit2016.png new file mode 100644 index 0000000000..51a832bf26 Binary files /dev/null and b/resources/img/reactivesummit2016.png differ diff --git a/resources/img/reactivesummit2016@x2.png b/resources/img/reactivesummit2016@x2.png new file mode 100644 index 0000000000..88f6263dab Binary files /dev/null and b/resources/img/reactivesummit2016@x2.png differ diff --git a/resources/img/recent-date-icon.png b/resources/img/recent-date-icon.png new file mode 100644 index 0000000000..c539c1f332 Binary files /dev/null and b/resources/img/recent-date-icon.png differ diff --git a/resources/img/recent-date-icon@2x.png b/resources/img/recent-date-icon@2x.png new file mode 100644 index 0000000000..70f9f93eb8 Binary files /dev/null and b/resources/img/recent-date-icon@2x.png differ diff --git a/resources/img/rss-icon.png b/resources/img/rss-icon.png new file mode 100644 index 0000000000..aad98ea6db Binary files /dev/null and b/resources/img/rss-icon.png differ diff --git a/resources/img/rss-icon@2x.png b/resources/img/rss-icon@2x.png new file mode 100644 index 0000000000..ed916522cf Binary files /dev/null and b/resources/img/rss-icon@2x.png differ diff --git a/resources/img/scala-downunder.png b/resources/img/scala-downunder.png new file mode 100644 index 0000000000..b6b76f988e Binary files /dev/null and b/resources/img/scala-downunder.png differ diff --git a/resources/img/scala-downunder@2x.png b/resources/img/scala-downunder@2x.png new file mode 100644 index 0000000000..d86b126471 Binary files /dev/null and b/resources/img/scala-downunder@2x.png differ diff --git a/resources/img/scala-italy.jpeg b/resources/img/scala-italy.jpeg new file mode 100644 index 0000000000..81d5e81fa3 Binary files /dev/null and b/resources/img/scala-italy.jpeg differ diff --git a/resources/img/scala-italy@2x.jpeg b/resources/img/scala-italy@2x.jpeg new file mode 100644 index 0000000000..7b34c482e2 Binary files /dev/null and b/resources/img/scala-italy@2x.jpeg differ diff --git a/resources/img/scala-logo-red-dark.png b/resources/img/scala-logo-red-dark.png new file mode 100644 index 0000000000..9b357cc93c Binary files /dev/null and b/resources/img/scala-logo-red-dark.png differ diff --git a/resources/img/scala-logo-red-dark@2x.png b/resources/img/scala-logo-red-dark@2x.png new file mode 100644 index 0000000000..7e5b081943 Binary files /dev/null and b/resources/img/scala-logo-red-dark@2x.png differ diff --git a/resources/img/scala-logo-red-footer.png b/resources/img/scala-logo-red-footer.png new file mode 100644 index 0000000000..73dd454862 Binary files /dev/null and b/resources/img/scala-logo-red-footer.png differ diff --git a/resources/img/scala-logo-red-footer@2x.png b/resources/img/scala-logo-red-footer@2x.png new file mode 100644 index 0000000000..7185deba58 Binary files /dev/null and b/resources/img/scala-logo-red-footer@2x.png differ diff --git a/resources/img/scala-logo-red-sm.png b/resources/img/scala-logo-red-sm.png new file mode 100644 index 0000000000..ab08b4c11a Binary files /dev/null and b/resources/img/scala-logo-red-sm.png differ diff --git a/resources/img/scala-logo-red-sm@2x.png b/resources/img/scala-logo-red-sm@2x.png new file mode 100644 index 0000000000..c6c5589dd5 Binary files /dev/null and b/resources/img/scala-logo-red-sm@2x.png differ diff --git a/resources/img/scala-logo-red-spiral-dark.png b/resources/img/scala-logo-red-spiral-dark.png new file mode 100644 index 0000000000..b3fbe10dc8 Binary files /dev/null and b/resources/img/scala-logo-red-spiral-dark.png differ diff --git a/resources/img/scala-logo-red-spiral.png b/resources/img/scala-logo-red-spiral.png new file mode 100644 index 0000000000..0d99955bc3 Binary files /dev/null and b/resources/img/scala-logo-red-spiral.png differ diff --git a/resources/img/scala-logo-red.png b/resources/img/scala-logo-red.png new file mode 100644 index 0000000000..66a0c0d4f6 Binary files /dev/null and b/resources/img/scala-logo-red.png differ diff --git a/resources/img/scala-logo-red@2x.png b/resources/img/scala-logo-red@2x.png new file mode 100644 index 0000000000..2a7de89e14 Binary files /dev/null and b/resources/img/scala-logo-red@2x.png differ diff --git a/resources/img/scala-logo-white-footer.png b/resources/img/scala-logo-white-footer.png new file mode 100644 index 0000000000..93ee327947 Binary files /dev/null and b/resources/img/scala-logo-white-footer.png differ diff --git a/resources/img/scala-logo-white-footer@2x.png b/resources/img/scala-logo-white-footer@2x.png new file mode 100644 index 0000000000..1b53c53aa5 Binary files /dev/null and b/resources/img/scala-logo-white-footer@2x.png differ diff --git a/resources/img/scala-logo-white-sm.png b/resources/img/scala-logo-white-sm.png new file mode 100644 index 0000000000..8b7c4c4b3a Binary files /dev/null and b/resources/img/scala-logo-white-sm.png differ diff --git a/resources/img/scala-logo-white-sm@2x.png b/resources/img/scala-logo-white-sm@2x.png new file mode 100644 index 0000000000..d546164452 Binary files /dev/null and b/resources/img/scala-logo-white-sm@2x.png differ diff --git a/resources/img/scala-logo-white-spiral.png b/resources/img/scala-logo-white-spiral.png new file mode 100644 index 0000000000..81b3ef0a59 Binary files /dev/null and b/resources/img/scala-logo-white-spiral.png differ diff --git a/resources/img/scala-logo-white.png b/resources/img/scala-logo-white.png new file mode 100644 index 0000000000..172b2984bc Binary files /dev/null and b/resources/img/scala-logo-white.png differ diff --git a/resources/img/scala-logo-white@2x.png b/resources/img/scala-logo-white@2x.png new file mode 100644 index 0000000000..fb70ad6c9e Binary files /dev/null and b/resources/img/scala-logo-white@2x.png differ diff --git a/resources/img/scala-logo.png b/resources/img/scala-logo.png new file mode 100644 index 0000000000..6217409748 Binary files /dev/null and b/resources/img/scala-logo.png differ diff --git a/resources/img/scala-matsuri.png b/resources/img/scala-matsuri.png new file mode 100644 index 0000000000..8c924442c5 Binary files /dev/null and b/resources/img/scala-matsuri.png differ diff --git a/resources/img/scala-matsuri@2x.png b/resources/img/scala-matsuri@2x.png new file mode 100644 index 0000000000..18560cd0f0 Binary files /dev/null and b/resources/img/scala-matsuri@2x.png differ diff --git a/resources/img/scala-small-logo.png b/resources/img/scala-small-logo.png new file mode 100644 index 0000000000..6f2875050a Binary files /dev/null and b/resources/img/scala-small-logo.png differ diff --git a/resources/img/scala-spiral-3d-2-noise.png b/resources/img/scala-spiral-3d-2-noise.png new file mode 100644 index 0000000000..d731964422 Binary files /dev/null and b/resources/img/scala-spiral-3d-2-noise.png differ diff --git a/resources/img/scala-spiral-3d-2-toned-down.png b/resources/img/scala-spiral-3d-2-toned-down.png new file mode 100644 index 0000000000..23e30025e4 Binary files /dev/null and b/resources/img/scala-spiral-3d-2-toned-down.png differ diff --git a/resources/img/scala-spiral-3d-2.png b/resources/img/scala-spiral-3d-2.png new file mode 100644 index 0000000000..a7ea2e7bf6 Binary files /dev/null and b/resources/img/scala-spiral-3d-2.png differ diff --git a/resources/img/scala-spiral-3d.png b/resources/img/scala-spiral-3d.png new file mode 100644 index 0000000000..4c14d23818 Binary files /dev/null and b/resources/img/scala-spiral-3d.png differ diff --git a/resources/img/scala-spiral-navbar.png b/resources/img/scala-spiral-navbar.png new file mode 100644 index 0000000000..66badac439 Binary files /dev/null and b/resources/img/scala-spiral-navbar.png differ diff --git a/resources/img/scala-spiral-noise-sm.png b/resources/img/scala-spiral-noise-sm.png new file mode 100644 index 0000000000..3846184b32 Binary files /dev/null and b/resources/img/scala-spiral-noise-sm.png differ diff --git a/resources/img/scala-spiral-noise-sm2.png b/resources/img/scala-spiral-noise-sm2.png new file mode 100644 index 0000000000..c452af546e Binary files /dev/null and b/resources/img/scala-spiral-noise-sm2.png differ diff --git a/resources/img/scala-spiral-noise-sm2.png.png b/resources/img/scala-spiral-noise-sm2.png.png new file mode 100644 index 0000000000..b29e1a0cb6 Binary files /dev/null and b/resources/img/scala-spiral-noise-sm2.png.png differ diff --git a/resources/img/scala-spiral-noise-sm@2x.png b/resources/img/scala-spiral-noise-sm@2x.png new file mode 100644 index 0000000000..fb8e9c0048 Binary files /dev/null and b/resources/img/scala-spiral-noise-sm@2x.png differ diff --git a/resources/img/scala-spiral-white.png b/resources/img/scala-spiral-white.png new file mode 100644 index 0000000000..46aaf80824 Binary files /dev/null and b/resources/img/scala-spiral-white.png differ diff --git a/resources/img/scala-spiral-white.svg b/resources/img/scala-spiral-white.svg new file mode 100644 index 0000000000..67809df24a --- /dev/null +++ b/resources/img/scala-spiral-white.svg @@ -0,0 +1,62 @@ + + + Slice 1 + Created with Sketch (http://www.bohemiancoding.com/sketch) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/resources/img/scala-spiral-white@2x.png b/resources/img/scala-spiral-white@2x.png new file mode 100644 index 0000000000..37a43e964e Binary files /dev/null and b/resources/img/scala-spiral-white@2x.png differ diff --git a/resources/img/scala-spiral.png b/resources/img/scala-spiral.png new file mode 100644 index 0000000000..0067ef440b Binary files /dev/null and b/resources/img/scala-spiral.png differ diff --git a/resources/img/scala-spiral.svg b/resources/img/scala-spiral.svg new file mode 100644 index 0000000000..51c3ca2c34 --- /dev/null +++ b/resources/img/scala-spiral.svg @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/img/scala-spiral2.png b/resources/img/scala-spiral2.png new file mode 100644 index 0000000000..b1fa0f7f89 Binary files /dev/null and b/resources/img/scala-spiral2.png differ diff --git a/resources/img/scala-swarm.png b/resources/img/scala-swarm.png new file mode 100644 index 0000000000..c1674d277f Binary files /dev/null and b/resources/img/scala-swarm.png differ diff --git a/resources/img/scala-swarm@2x.png b/resources/img/scala-swarm@2x.png new file mode 100644 index 0000000000..5923c0af26 Binary files /dev/null and b/resources/img/scala-swarm@2x.png differ diff --git a/resources/img/scala-world.png b/resources/img/scala-world.png new file mode 100644 index 0000000000..b2f783ad21 Binary files /dev/null and b/resources/img/scala-world.png differ diff --git a/resources/img/scala-world@2x.png b/resources/img/scala-world@2x.png new file mode 100644 index 0000000000..c46d320f26 Binary files /dev/null and b/resources/img/scala-world@2x.png differ diff --git a/resources/img/scala2013.png b/resources/img/scala2013.png new file mode 100644 index 0000000000..a568b0f627 Binary files /dev/null and b/resources/img/scala2013.png differ diff --git a/resources/img/scala2013@2x.png b/resources/img/scala2013@2x.png new file mode 100644 index 0000000000..e2b2f220a8 Binary files /dev/null and b/resources/img/scala2013@2x.png differ diff --git a/resources/img/scala2014.png b/resources/img/scala2014.png new file mode 100644 index 0000000000..58acb2042f Binary files /dev/null and b/resources/img/scala2014.png differ diff --git a/resources/img/scala2014@2x.png b/resources/img/scala2014@2x.png new file mode 100644 index 0000000000..6f2d43afe8 Binary files /dev/null and b/resources/img/scala2014@2x.png differ diff --git a/resources/img/scala2016.png b/resources/img/scala2016.png new file mode 100644 index 0000000000..22849b12c3 Binary files /dev/null and b/resources/img/scala2016.png differ diff --git a/resources/img/scala2016@x2.png b/resources/img/scala2016@x2.png new file mode 100644 index 0000000000..05c924364e Binary files /dev/null and b/resources/img/scala2016@x2.png differ diff --git a/resources/img/scalabythebay.png b/resources/img/scalabythebay.png new file mode 100644 index 0000000000..83a3d01224 Binary files /dev/null and b/resources/img/scalabythebay.png differ diff --git a/resources/img/scalabythebay2016.png b/resources/img/scalabythebay2016.png new file mode 100644 index 0000000000..3f09d1fd8c Binary files /dev/null and b/resources/img/scalabythebay2016.png differ diff --git a/resources/img/scalabythebay2016@x2.png b/resources/img/scalabythebay2016@x2.png new file mode 100644 index 0000000000..e4a8b1a508 Binary files /dev/null and b/resources/img/scalabythebay2016@x2.png differ diff --git a/resources/img/scalabythebay@2x.png b/resources/img/scalabythebay@2x.png new file mode 100644 index 0000000000..5c941c3f7c Binary files /dev/null and b/resources/img/scalabythebay@2x.png differ diff --git a/resources/img/scalabytheschuylkill.png b/resources/img/scalabytheschuylkill.png new file mode 100644 index 0000000000..e009c638f9 Binary files /dev/null and b/resources/img/scalabytheschuylkill.png differ diff --git a/resources/img/scalabytheschuylkill@x2.png b/resources/img/scalabytheschuylkill@x2.png new file mode 100644 index 0000000000..3ad84c1adc Binary files /dev/null and b/resources/img/scalabytheschuylkill@x2.png differ diff --git a/resources/img/scaladays-15.png b/resources/img/scaladays-15.png new file mode 100644 index 0000000000..16b400dee8 Binary files /dev/null and b/resources/img/scaladays-15.png differ diff --git a/resources/img/scaladays.png b/resources/img/scaladays.png new file mode 100644 index 0000000000..6e218a8c39 Binary files /dev/null and b/resources/img/scaladays.png differ diff --git a/resources/img/scaladays2.png b/resources/img/scaladays2.png new file mode 100644 index 0000000000..c22b544cbd Binary files /dev/null and b/resources/img/scaladays2.png differ diff --git a/resources/img/scaladays2014.png b/resources/img/scaladays2014.png new file mode 100644 index 0000000000..c8edee6fa7 Binary files /dev/null and b/resources/img/scaladays2014.png differ diff --git a/resources/img/scaladays2014@2x.png b/resources/img/scaladays2014@2x.png new file mode 100644 index 0000000000..c92c0bbaa0 Binary files /dev/null and b/resources/img/scaladays2014@2x.png differ diff --git a/resources/img/scaladays2@2x.png b/resources/img/scaladays2@2x.png new file mode 100644 index 0000000000..ecb13ca6cf Binary files /dev/null and b/resources/img/scaladays2@2x.png differ diff --git a/resources/img/scaladays@2x.png b/resources/img/scaladays@2x.png new file mode 100644 index 0000000000..7d83520317 Binary files /dev/null and b/resources/img/scaladays@2x.png differ diff --git a/resources/img/scalaexchange.png b/resources/img/scalaexchange.png new file mode 100644 index 0000000000..410839295a Binary files /dev/null and b/resources/img/scalaexchange.png differ diff --git a/resources/img/scalaexchange@2x.png b/resources/img/scalaexchange@2x.png new file mode 100644 index 0000000000..6538a53cad Binary files /dev/null and b/resources/img/scalaexchange@2x.png differ diff --git a/resources/img/scalaio.png b/resources/img/scalaio.png new file mode 100644 index 0000000000..708714cc31 Binary files /dev/null and b/resources/img/scalaio.png differ diff --git a/resources/img/scalaio@2x.png b/resources/img/scalaio@2x.png new file mode 100644 index 0000000000..6639398c59 Binary files /dev/null and b/resources/img/scalaio@2x.png differ diff --git a/resources/img/scalameta-sketch.jpg b/resources/img/scalameta-sketch.jpg new file mode 100644 index 0000000000..277b8dbec4 Binary files /dev/null and b/resources/img/scalameta-sketch.jpg differ diff --git a/resources/img/scalapeno2014-logo.png b/resources/img/scalapeno2014-logo.png new file mode 100644 index 0000000000..b705e2a5ff Binary files /dev/null and b/resources/img/scalapeno2014-logo.png differ diff --git a/resources/img/scalapolis.png b/resources/img/scalapolis.png new file mode 100644 index 0000000000..ccba77aecf Binary files /dev/null and b/resources/img/scalapolis.png differ diff --git a/resources/img/scalapolis@2x.png b/resources/img/scalapolis@2x.png new file mode 100644 index 0000000000..4715d7ac86 Binary files /dev/null and b/resources/img/scalapolis@2x.png differ diff --git a/resources/img/scalar.png b/resources/img/scalar.png new file mode 100644 index 0000000000..ba45f0167a Binary files /dev/null and b/resources/img/scalar.png differ diff --git a/resources/img/scalar@2x.png b/resources/img/scalar@2x.png new file mode 100644 index 0000000000..4fc9d115ad Binary files /dev/null and b/resources/img/scalar@2x.png differ diff --git a/resources/img/scalasphere.png b/resources/img/scalasphere.png new file mode 100644 index 0000000000..726015b586 Binary files /dev/null and b/resources/img/scalasphere.png differ diff --git a/resources/img/scalasphere@2x.png b/resources/img/scalasphere@2x.png new file mode 100644 index 0000000000..d0546f13a7 Binary files /dev/null and b/resources/img/scalasphere@2x.png differ diff --git a/resources/img/scalasummit2014.png b/resources/img/scalasummit2014.png new file mode 100644 index 0000000000..b3895ed586 Binary files /dev/null and b/resources/img/scalasummit2014.png differ diff --git a/resources/img/scalasummit2014@2x.png b/resources/img/scalasummit2014@2x.png new file mode 100644 index 0000000000..baef27cddc Binary files /dev/null and b/resources/img/scalasummit2014@2x.png differ diff --git a/resources/img/scalaua.png b/resources/img/scalaua.png new file mode 100644 index 0000000000..a65eab0670 Binary files /dev/null and b/resources/img/scalaua.png differ diff --git a/resources/img/scalaua@2x.png b/resources/img/scalaua@2x.png new file mode 100644 index 0000000000..2b4e2dc512 Binary files /dev/null and b/resources/img/scalaua@2x.png differ diff --git a/resources/img/scalaupnorth.png b/resources/img/scalaupnorth.png new file mode 100644 index 0000000000..b164d088b6 Binary files /dev/null and b/resources/img/scalaupnorth.png differ diff --git a/resources/img/scalaupnorth@2x.png b/resources/img/scalaupnorth@2x.png new file mode 100644 index 0000000000..82292075dd Binary files /dev/null and b/resources/img/scalaupnorth@2x.png differ diff --git a/resources/img/scalawave.png b/resources/img/scalawave.png new file mode 100644 index 0000000000..af829d8910 Binary files /dev/null and b/resources/img/scalawave.png differ diff --git a/resources/img/scalawave@2x.png b/resources/img/scalawave@2x.png new file mode 100644 index 0000000000..5eff3bb1e8 Binary files /dev/null and b/resources/img/scalawave@2x.png differ diff --git a/resources/img/sfscala.png b/resources/img/sfscala.png new file mode 100644 index 0000000000..e80f48a9f5 Binary files /dev/null and b/resources/img/sfscala.png differ diff --git a/resources/img/sfscala@2x.png b/resources/img/sfscala@2x.png new file mode 100644 index 0000000000..29b1c9cf6f Binary files /dev/null and b/resources/img/sfscala@2x.png differ diff --git a/resources/img/shadow.png b/resources/img/shadow.png new file mode 100644 index 0000000000..06c3d64dbf Binary files /dev/null and b/resources/img/shadow.png differ diff --git a/resources/img/smooth-spiral.png b/resources/img/smooth-spiral.png new file mode 100644 index 0000000000..39f5c606f2 Binary files /dev/null and b/resources/img/smooth-spiral.png differ diff --git a/resources/img/smooth-spiral@2x.png b/resources/img/smooth-spiral@2x.png new file mode 100644 index 0000000000..5591d59934 Binary files /dev/null and b/resources/img/smooth-spiral@2x.png differ diff --git a/resources/img/splash.png b/resources/img/splash.png new file mode 100644 index 0000000000..b9c52186e1 Binary files /dev/null and b/resources/img/splash.png differ diff --git a/resources/img/splash@2x.png b/resources/img/splash@2x.png new file mode 100644 index 0000000000..8f8f15202c Binary files /dev/null and b/resources/img/splash@2x.png differ diff --git a/resources/img/swiss-alps-sunset3.jpg b/resources/img/swiss-alps-sunset3.jpg new file mode 100644 index 0000000000..37b5b1346e Binary files /dev/null and b/resources/img/swiss-alps-sunset3.jpg differ diff --git a/resources/img/swiss-alps-sunset5-blurred.jpg b/resources/img/swiss-alps-sunset5-blurred.jpg new file mode 100644 index 0000000000..7b376a927b Binary files /dev/null and b/resources/img/swiss-alps-sunset5-blurred.jpg differ diff --git a/resources/img/swiss-alps-sunset5-dark-overlay.jpg b/resources/img/swiss-alps-sunset5-dark-overlay.jpg new file mode 100644 index 0000000000..8bcbc2ef67 Binary files /dev/null and b/resources/img/swiss-alps-sunset5-dark-overlay.jpg differ diff --git a/resources/img/swiss-alps-sunset5-short-blue.jpg b/resources/img/swiss-alps-sunset5-short-blue.jpg new file mode 100644 index 0000000000..04017532f6 Binary files /dev/null and b/resources/img/swiss-alps-sunset5-short-blue.jpg differ diff --git a/resources/img/swiss-alps-sunset5-short.jpg b/resources/img/swiss-alps-sunset5-short.jpg new file mode 100644 index 0000000000..c0145039e7 Binary files /dev/null and b/resources/img/swiss-alps-sunset5-short.jpg differ diff --git a/resources/img/swiss-alps-sunset5-sm.jpg b/resources/img/swiss-alps-sunset5-sm.jpg new file mode 100644 index 0000000000..fc4f47a1d0 Binary files /dev/null and b/resources/img/swiss-alps-sunset5-sm.jpg differ diff --git a/resources/img/swiss-alps-sunset5.jpg b/resources/img/swiss-alps-sunset5.jpg new file mode 100644 index 0000000000..0cdc4d31c2 Binary files /dev/null and b/resources/img/swiss-alps-sunset5.jpg differ diff --git a/resources/img/swiss-flag.png b/resources/img/swiss-flag.png new file mode 100644 index 0000000000..2c588156b4 Binary files /dev/null and b/resources/img/swiss-flag.png differ diff --git a/resources/img/swiss-flag@2x.png b/resources/img/swiss-flag@2x.png new file mode 100644 index 0000000000..dac6f97abf Binary files /dev/null and b/resources/img/swiss-flag@2x.png differ diff --git a/resources/img/test.png b/resources/img/test.png new file mode 100644 index 0000000000..747773c530 Binary files /dev/null and b/resources/img/test.png differ diff --git a/resources/img/transparent_noise.png b/resources/img/transparent_noise.png new file mode 100644 index 0000000000..52ff999920 Binary files /dev/null and b/resources/img/transparent_noise.png differ diff --git a/resources/img/twitter-logo-blue.png b/resources/img/twitter-logo-blue.png new file mode 100644 index 0000000000..0eab4fca72 Binary files /dev/null and b/resources/img/twitter-logo-blue.png differ diff --git a/resources/img/twitter-logo-blue@2x.png b/resources/img/twitter-logo-blue@2x.png new file mode 100644 index 0000000000..54f66adf66 Binary files /dev/null and b/resources/img/twitter-logo-blue@2x.png differ diff --git a/resources/img/twitter-logo-white-lg.png b/resources/img/twitter-logo-white-lg.png new file mode 100644 index 0000000000..003139b3a7 Binary files /dev/null and b/resources/img/twitter-logo-white-lg.png differ diff --git a/resources/img/twitter-logo-white-lg@2x.png b/resources/img/twitter-logo-white-lg@2x.png new file mode 100644 index 0000000000..9188cd795d Binary files /dev/null and b/resources/img/twitter-logo-white-lg@2x.png differ diff --git a/resources/img/twitter-logo-white.png b/resources/img/twitter-logo-white.png new file mode 100644 index 0000000000..7e92119764 Binary files /dev/null and b/resources/img/twitter-logo-white.png differ diff --git a/resources/img/twitter-logo-white@2x.png b/resources/img/twitter-logo-white@2x.png new file mode 100644 index 0000000000..2219685760 Binary files /dev/null and b/resources/img/twitter-logo-white@2x.png differ diff --git a/resources/img/twitter-logo.png b/resources/img/twitter-logo.png new file mode 100644 index 0000000000..675986895d Binary files /dev/null and b/resources/img/twitter-logo.png differ diff --git a/resources/img/type-inf-code.png b/resources/img/type-inf-code.png new file mode 100644 index 0000000000..c9b9a03c77 Binary files /dev/null and b/resources/img/type-inf-code.png differ diff --git a/resources/img/typelevel.png b/resources/img/typelevel.png new file mode 100644 index 0000000000..cee5c1f66c Binary files /dev/null and b/resources/img/typelevel.png differ diff --git a/resources/img/typelevel@2x.png b/resources/img/typelevel@2x.png new file mode 100644 index 0000000000..f0b29b4bf5 Binary files /dev/null and b/resources/img/typelevel@2x.png differ diff --git a/resources/img/uberconf2014.png b/resources/img/uberconf2014.png new file mode 100644 index 0000000000..a9d6564a4b Binary files /dev/null and b/resources/img/uberconf2014.png differ diff --git a/resources/img/uberconf2014@2x.png b/resources/img/uberconf2014@2x.png new file mode 100644 index 0000000000..289f61d909 Binary files /dev/null and b/resources/img/uberconf2014@2x.png differ diff --git a/resources/img/view-leman-blurred.jpg b/resources/img/view-leman-blurred.jpg new file mode 100644 index 0000000000..b87f38a572 Binary files /dev/null and b/resources/img/view-leman-blurred.jpg differ diff --git a/resources/img/view-leman-blurred2.jpg b/resources/img/view-leman-blurred2.jpg new file mode 100644 index 0000000000..c01b6afe72 Binary files /dev/null and b/resources/img/view-leman-blurred2.jpg differ diff --git a/resources/img/view-leman-gradient-map.jpg b/resources/img/view-leman-gradient-map.jpg new file mode 100644 index 0000000000..d952ac2906 Binary files /dev/null and b/resources/img/view-leman-gradient-map.jpg differ diff --git a/resources/img/view-leman-gradient-map2.jpg b/resources/img/view-leman-gradient-map2.jpg new file mode 100644 index 0000000000..56c978b0b4 Binary files /dev/null and b/resources/img/view-leman-gradient-map2.jpg differ diff --git a/resources/img/view-leman-grayscale.jpg b/resources/img/view-leman-grayscale.jpg new file mode 100644 index 0000000000..bf0cf68845 Binary files /dev/null and b/resources/img/view-leman-grayscale.jpg differ diff --git a/resources/img/view-leman-opt.jpg b/resources/img/view-leman-opt.jpg new file mode 100644 index 0000000000..865022e9e5 Binary files /dev/null and b/resources/img/view-leman-opt.jpg differ diff --git a/resources/img/view-leman-opt2.jpg b/resources/img/view-leman-opt2.jpg new file mode 100644 index 0000000000..f62bdf49b7 Binary files /dev/null and b/resources/img/view-leman-opt2.jpg differ diff --git a/resources/img/view-leman.jpg b/resources/img/view-leman.jpg new file mode 100644 index 0000000000..8a27cc03aa Binary files /dev/null and b/resources/img/view-leman.jpg differ diff --git a/resources/img/white-line.png b/resources/img/white-line.png new file mode 100644 index 0000000000..b19dad64fa Binary files /dev/null and b/resources/img/white-line.png differ diff --git a/resources/img/winterretreat2016.png b/resources/img/winterretreat2016.png new file mode 100644 index 0000000000..7d40cf3459 Binary files /dev/null and b/resources/img/winterretreat2016.png differ diff --git a/resources/img/winterretreat2016@2x.png b/resources/img/winterretreat2016@2x.png new file mode 100644 index 0000000000..673591beb7 Binary files /dev/null and b/resources/img/winterretreat2016@2x.png differ diff --git a/resources/javascript/coursera/bargraphs.js b/resources/javascript/coursera/bargraphs.js deleted file mode 100644 index 340d715c91..0000000000 --- a/resources/javascript/coursera/bargraphs.js +++ /dev/null @@ -1,363 +0,0 @@ -simpleBarGraphTiltedLabels("../resources/dat/degrees.csv","#degrees","Percentage",450,240,undefined,"#CF5300",{top: 20, right: 20, bottom: 60, left: 40}) -simpleBarGraphTiltedLabels("../resources/dat/worth-it.csv","#worthit","Percentage",250,250,70) -simpleBarGraphTiltedLabels("../resources/dat/followup.csv","#followup","Percentage",250,250,70,"#5E4175") - -groupedBarGraph("../resources/dat/languages-percentages.csv","#languages-percentages","Percentage") -groupedBarGraph("../resources/dat/difficulty-to-expertise.csv","#difficulty-to-expertise","Percentage",450,240) -groupedBarGraph("../resources/dat/difficulty-to-field.csv","#difficulty-to-field","Percentage",450,240) -groupedBarGraph("../resources/dat/editors.csv","#editors","Percentage",450,240) -groupedBarGraph("../resources/dat/difficulty-to-education.csv", '#difficulty-to-education', 'Percentage', '800', '320') - -verticalBarGraph("../resources/dat/fields-of-study.csv","#fields-of-study") - -function groupedBarGraph(pathToCsv,div,ylabel,w,h,colorArr,margin,yformat) { - div = typeof div !== 'undefined' ? div : "body"; - ylabel = typeof ylabel !== 'undefined' ? ylabel : ""; - w = typeof w !== 'undefined' ? w : 960; - h = typeof h !== 'undefined' ? h : 480; - colorArr = typeof colorArr !== 'undefined' ? colorArr : ["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]; - margin = typeof margin != 'undefined' ? margin : {top: 20, right: 20, bottom: 30, left: 40}; - yformat = typeof format !== 'undefined' ? format : d3.format(".2s"); // for example, for %ages d3.format(".0%"); - - var width = w - margin.left - margin.right, - height = h - margin.top - margin.bottom; - - var x0 = d3.scale.ordinal() - .rangeRoundBands([0, width], .1); - - var x1 = d3.scale.ordinal(); - - var y = d3.scale.linear() - .range([height, 0]); - - var color = d3.scale.ordinal() - .range(colorArr); - - var xAxis = d3.svg.axis() - .scale(x0) - .orient("bottom"); - - var yAxis = d3.svg.axis() - .scale(y) - .orient("left") - .tickFormat(yformat); - - var svg = d3.select(div).append("svg") - .attr("width", width + margin.left + margin.right) - .attr("height", height + margin.top + margin.bottom) - .append("g") - .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); - - d3.csv(pathToCsv, function(error, data) { - var expLevels = d3.keys(data[0]).filter(function(key) { return key !== "Key"; }); - - data.forEach(function(d) { - d.exp = expLevels.map(function(name) { return {name: name, value: +d[name]}; }); - }); - - x0.domain(data.map(function(d) { return d.Key; })); - x1.domain(expLevels).rangeRoundBands([0, x0.rangeBand()]); - y.domain([0, d3.max(data, function(d) { return d3.max(d.exp, function(d) { return d.value; }); })]); - - svg.append("g") - .attr("class", "x axis") - .attr("transform", "translate(0," + height + ")") - .call(xAxis); - - svg.append("g") - .attr("class", "y axis") - .call(yAxis) - .append("text") - .attr("transform", "rotate(-90)") - .attr("y", 6) - .attr("dy", ".71em") - .style("text-anchor", "end") - .text(ylabel) - .style("font-weight", "bold"); - - var language = svg.selectAll(".language") - .data(data) - .enter().append("g") - .attr("class", "g") - .attr("transform", function(d) { return "translate(" + x0(d.Key) + ",0)"; }) - .style("font-size", "10px"); - - language.selectAll("rect") - .data(function(d) { return d.exp; }) - .enter().append("rect") - .attr("width", x1.rangeBand()) - .attr("x", function(d) { return x1(d.name); }) - .attr("y", function(d) { return y(d.value); }) - .attr("height", function(d) { return height - y(d.value); }) - .style("fill", function(d) { return color(d.name); }) - .on ("mouseover",mover) - .on ("mouseout",mout); - - if (ylabel.toLowerCase().indexOf("percentage") != -1) { var suffix = "%"; } - else { var suffix = ""; } - - function mover(d) { - d3.select(this).transition().attr("height", function(d) { return height - y(d.value) + 5; }) - .attr("y", function(d) { return y(d.value) - 5;}) - .attr("width", x1.rangeBand() + 2) - .attr("x", function(d) { return x1(d.name) - 1; }) - .style("fill-opacity", .9); - $(this).attr("rel","twipsy") - .attr("data-original-title",d.value + suffix) - .twipsy('show'); - } - - function mout(d) { - d3.select(this).transition().attr("height", function(d) { return height - y(d.value); }) - .attr("y", function(d) { return y(d.value); }) - .attr("width", x1.rangeBand()) - .attr("x", function(d) { return x1(d.name); }) - .style("fill-opacity", 1); - $(this).twipsy('hide'); - } - - $("rect[rel=twipsy]").twipsy({ live: true, offset: 4 }); - - var legend = svg.selectAll(".legend") - .data(expLevels.slice()) - .enter().append("g") - .attr("class", "legend") - .attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; }); - - legend.append("rect") - .attr("x", width - 18) - .attr("width", 18) - .attr("height", 18) - .style("fill", color); - - legend.append("text") - .attr("x", width - 24) - .attr("y", 9) - .attr("dy", ".35em") - .style("text-anchor", "end") - .text(function(d) { return d; }); - - }); -} - -function simpleBarGraphTiltedLabels(pathToCsv,div,ylabel,w,h,maxy,color,margin,yformat) { - return simpleBarGraph(pathToCsv,div,ylabel,w,h,maxy,color,margin,yformat,true); -} - -function simpleBarGraph(pathToCsv,div,ylabel,w,h,maxy,color,margin,yformat,tilted) { - div = typeof div !== 'undefined' ? div : "body"; - w = typeof w !== 'undefined' ? w : 960; - h = typeof h !== 'undefined' ? w : 480; - ylabel = typeof ylabel !== 'undefined' ? ylabel : ""; - //maxy's default is set below - color = typeof color !== 'undefined' ? color : "steelblue"; - margin = typeof margin != 'undefined' ? margin : {top: 20, right: 20, bottom: 80, left: 40}; - yformat = typeof format !== 'undefined' ? format : d3.format(".2s"); // for example, for %ages d3.format(".0%"); - tilted = typeof tilted !== 'undefined' ? tilted : false; - - var width = w - margin.left - margin.right, - height = h - margin.top - margin.bottom; - - var x = d3.scale.ordinal() - .rangeRoundBands([0, width], .1); - - var y = d3.scale.linear() - .range([height, 0]); - - var xAxis = d3.svg.axis() - .scale(x) - .orient("bottom"); - - var yAxis = d3.svg.axis() - .scale(y) - .orient("left") - .tickFormat(yformat); - - var svg = d3.select(div).append("svg") - .attr("width", width + margin.left + margin.right) - .attr("height", height + margin.top + margin.bottom) - .append("g") - .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); - - var bargraph = d3.csv(pathToCsv, function(error, data) { - data.forEach(function(d) { - d.el2 = +d.el2; - }); - - x.domain(data.map(function(d) { return d.el1; })); - if (typeof maxy !== 'undefined') { - y.domain([0, maxy]); - } else { - y.domain([0, d3.max(data, function(d) { return d.el2; })]); - } - - var xax = svg.append("g") - .attr("class", "x axis") - .attr("transform", "translate(0," + height + ")") - .call(xAxis); - - if(tilted) { - xax.selectAll("text") - .attr("text-anchor", function(d) { return "end" }) - .attr("transform", function(d) { return "translate(-20,20)rotate(-45)"; }) - .style("font-size","11px") - .style("font-weight","bold"); - } - - svg.append("g") - .attr("class", "y axis") - .call(yAxis) - .append("text") - .attr("transform", "rotate(-90)") - .attr("y", 6) - .attr("dy", ".71em") - .style("text-anchor", "end") - .text("Percentage") - .style("font-weight","bold"); - - svg.selectAll(".bar") - .data(data) - .enter().append("rect") - .attr("class", "bar") - .attr("x", function(d) { return x(d.el1); }) - .attr("width", x.rangeBand()) - .attr("y", function(d) { return y(d.el2); }) - .attr("height", function(d) { return height - y(d.el2); }) - .style("fill", color) - .on ("mouseover",mover) - .on ("mouseout",mout); - - if (ylabel.toLowerCase().indexOf("percentage") != -1) { var suffix = "%"; } - else { var suffix = ""; } - - function mover(d) { - d3.select(this).transition().attr("height", function(d) { return height - y(d.el2) + 5; }) - .attr("y", function(d) { return y(d.el2) - 5;}) - .attr("width", x.rangeBand() + 2) - .attr("x", function(d) { return x(d.el1) - 1; }) - .style("fill-opacity", .8); - $(this).attr("rel","twipsy") - .attr("data-original-title",d.el2 + suffix) - .twipsy('show'); - } - - function mout(d) { - d3.select(this).transition().attr("height", function(d) { return height - y(d.el2); }) - .attr("y", function(d) { return y(d.el2); }) - .attr("width", x.rangeBand()) - .attr("x", function(d) { return x(d.el1); }) - .style("fill-opacity", 1); - $(this).twipsy('hide'); - } - - $("rect[rel=twipsy]").twipsy({ live: true, offset: 4 }); - - }); - return bargraph; -} - -function verticalBarGraph(pathToCsv,div,w,color,barHeight,barLabelWidth) { - div = typeof div !== 'undefined' ? div : "body"; - w = typeof w !== 'undefined' ? w : 260; - color = typeof color !== 'undefined' ? color : "steelblue"; - barHeight = typeof barHeight !== 'undefined' ? barHeight : 18; // height of one bar - barLabelWidth = typeof barLabelWidth !== 'undefined' ? barLabelWidth : 200; // space reserved for bar labels - - d3.csv(pathToCsv, function(error, data) { - data.forEach(function(d) { - d.value = +d.value; - }); - - var valueLabelWidth = 40; // space reserved for value labels (right) - var barLabelPadding = 5; // padding between bar and bar labels (left) - var gridLabelHeight = 18; // space reserved for gridline labels - var gridChartOffset = 3; // space between start of grid and first bar - var maxBarWidth = w; // width of the bar with the max value - - // accessor functions - var barLabel = function(d) { return d['name']; }; - var barValue = function(d) { return parseFloat(d['value']); }; - - // sorting - var sortedData = data.sort(function(a, b) { - return d3.descending(barValue(a), barValue(b)); - }); - - // scales - var yScale = d3.scale.ordinal().domain(d3.range(0, sortedData.length)).rangeBands([0, sortedData.length * barHeight]); - var y = function(d, i) { return yScale(i); }; - var yText = function(d, i) { return y(d, i) + yScale.rangeBand() / 2; }; - var x = d3.scale.linear().domain([0, d3.max(sortedData, barValue)]).range([0, maxBarWidth]); - // svg container element - var chart = d3.select(div).append("svg") - .attr('width', maxBarWidth + barLabelWidth + valueLabelWidth) - .attr('height', gridLabelHeight + gridChartOffset + sortedData.length * barHeight); - // grid line labels - var gridContainer = chart.append('g') - .attr('transform', 'translate(' + barLabelWidth + ',' + gridLabelHeight + ')'); - gridContainer.selectAll("text").data(x.ticks(10)).enter().append("text") - .attr("x", x) - .attr("dy", -3) - .attr("text-anchor", "middle") - .text(String); - // vertical grid lines - gridContainer.selectAll("line").data(x.ticks(10)).enter().append("line") - .attr("x1", x) - .attr("x2", x) - .attr("y1", 0) - .attr("y2", yScale.rangeExtent()[1] + gridChartOffset) - .style("stroke", "#ccc"); - // bar labels - var labelsContainer = chart.append('g') - .attr('transform', 'translate(' + (barLabelWidth - barLabelPadding) + ',' + (gridLabelHeight + gridChartOffset) + ')'); - labelsContainer.selectAll('text').data(sortedData).enter().append('text') - .attr('y', yText) - .attr('stroke', 'none') - .attr('fill', 'black') - .attr("dy", ".35em") // vertical-align: middle - .attr('text-anchor', 'end') - .text(barLabel) - .style("font-size","13px"); - // bars - var barsContainer = chart.append('g') - .attr('transform', 'translate(' + barLabelWidth + ',' + (gridLabelHeight + gridChartOffset) + ')'); - barsContainer.selectAll("rect").data(sortedData).enter().append("rect") - .attr("y", y) - .attr("height", yScale.rangeBand()) - .attr("width", function(d) { return x(barValue(d)); }) - .attr("stroke", 'white') - .attr("fill", color) - .on ("mouseover",mover) - .on ("mouseout",mout); - - // .on("mouseover", function(d) { d3.select(this).style("fill-opacity", 0.8); }) - // .on("mouseout", function(d) { d3.select(this).style("fill-opacity", 1); }); - - function mover(d) { - d3.select(this).transition().attr("width", function(d) { return x(barValue(d)) + 3; }) - .style("fill-opacity", .8); - } - - function mout(d) { - d3.select(this).transition().attr("width", function(d) { return x(barValue(d)); }) - .style("fill-opacity", 1); - } - - // bar value labels - barsContainer.selectAll("text").data(sortedData).enter().append("text") - .attr("x", function(d) { return x(barValue(d)); }) - .attr("y", yText) - .attr("dx", 3) // padding-left - .attr("dy", ".35em") // vertical-align: middle - .attr("text-anchor", "start") // text-align: right - .attr("fill", "black") - .attr("stroke", "none") - .text(function(d) { return d3.round(barValue(d), 2); }) - .style("font-size","13px"); - // start line - barsContainer.append("line") - .attr("y1", -gridChartOffset) - .attr("y2", yScale.rangeExtent()[1] + gridChartOffset) - .style("stroke", "#000"); - }); -} \ No newline at end of file diff --git a/resources/javascript/coursera/degrees.js b/resources/javascript/coursera/degrees.js deleted file mode 100644 index 1efd5ae658..0000000000 --- a/resources/javascript/coursera/degrees.js +++ /dev/null @@ -1,66 +0,0 @@ -var margin5 = {top: 20, right: 20, bottom: 60, left: 40}, - width5 = 450 - margin5.left - margin5.right, - height5 = 240 - margin5.top - margin5.bottom; - -// var formatPercent = d3.format(".0%"); - -var x5 = d3.scale.ordinal() - .rangeRoundBands([0, width5], .1); - -var y5 = d3.scale.linear() - .range([height5, 0]); - -var xAxis5 = d3.svg.axis() - .scale(x5) - .orient("bottom"); - -var yAxis5 = d3.svg.axis() - .scale(y5) - .orient("left") - .tickFormat(d3.format(".2s")); - -var svg5 = d3.select("#degrees").append("svg") - .attr("width", width5 + margin5.left + margin5.right) - .attr("height", height5 + margin5.top + margin5.bottom) - .append("g") - .attr("transform", "translate(" + margin5.left + "," + margin5.top + ")"); - -d3.csv("../resources/dat/degrees.csv", function(error, data) { - - data.forEach(function(d) { - d.count = +d.count; - }); - - x5.domain(data.map(function(d) { return d.degree; })); - y5.domain([0, d3.max(data, function(d) { return d.count; })]); - - var xax = svg5.append("g") - .attr("class", "x axis") - .attr("transform", "translate(0," + height5 + ")") - .call(xAxis5); - - xax.selectAll("text") - .attr("text-anchor", function(d) { return "end" }) - .attr("transform", function(d) { return "translate(-20,20)rotate(-45)"; }); - - svg5.append("g") - .attr("class", "y axis") - .call(yAxis5) - .append("text") - .attr("transform", "rotate(-90)") - .attr("y", 6) - .attr("dy", ".71em") - .style("text-anchor", "end") - .text("Percentage") - .style("font-weight","bold"); - - svg5.selectAll(".bar") - .data(data) - .enter().append("rect") - .attr("class", "bar") - .attr("x", function(d) { return x5(d.degree); }) - .attr("width", x5.rangeBand()) - .attr("y", function(d) { return y5(d.count); }) - .attr("height", function(d) { return height5 - y5(d.count); }); - -}); diff --git a/resources/javascript/coursera/difficulty-by-expertise.png b/resources/javascript/coursera/difficulty-by-expertise.png deleted file mode 100644 index ed99127ec6..0000000000 Binary files a/resources/javascript/coursera/difficulty-by-expertise.png and /dev/null differ diff --git a/resources/javascript/coursera/difficulty-to-expertise.js b/resources/javascript/coursera/difficulty-to-expertise.js deleted file mode 100644 index 2b0d7c002e..0000000000 --- a/resources/javascript/coursera/difficulty-to-expertise.js +++ /dev/null @@ -1,93 +0,0 @@ -var margin1 = {top: 20, right: 20, bottom: 30, left: 40}, - width1 = 450 - margin1.left - margin1.right, - height1 = 240 - margin1.top - margin1.bottom; - -var x01 = d3.scale.ordinal() - .rangeRoundBands([0, width1], .1); - -var x1 = d3.scale.ordinal(); - -var y1 = d3.scale.linear() - .range([height1, 0]); - -var color1 = d3.scale.ordinal() - .range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]); - -var xAxis1 = d3.svg.axis() - .scale(x01) - .orient("bottom"); - -var yAxis1 = d3.svg.axis() - .scale(y1) - .orient("left") - .tickFormat(d3.format(".2s")); - -var svg1 = d3.select("#difficulty-to-expertise").append("svg") - .attr("width", width1 + margin1.left + margin1.right) - .attr("height", height1 + margin1.top + margin1.bottom) - .append("g") - .attr("transform", "translate(" + margin1.left + "," + margin1.top + ")"); - -d3.csv("../resources/dat/difficulty-to-expertise.csv", function(error, data) { - var expLevels = d3.keys(data[0]).filter(function(key) { return key !== "Experience"; }); - - data.forEach(function(d) { - d.exp = expLevels.map(function(name) { return {name: name, value: +d[name]}; }); - }); - - x01.domain(data.map(function(d) { return d.Experience; })); - x1.domain(expLevels).rangeRoundBands([0, x01.rangeBand()]); - y1.domain([0, d3.max(data, function(d) { return d3.max(d.exp, function(d) { return d.value; }); })]); - - svg1.append("g") - .attr("class", "x axis") - .attr("transform", "translate(0," + height1 + ")") - .call(xAxis1); - - svg1.append("g") - .attr("class", "y axis") - .call(yAxis1) - .append("text") - .attr("transform", "rotate(-90)") - .attr("y", 6) - .attr("dy", ".71em") - .style("text-anchor", "end") - .text("Percentage") - .style("font-weight", "bold"); - - var language = svg1.selectAll(".language") - .data(data) - .enter().append("g") - .attr("class", "g") - .attr("transform", function(d) { return "translate(" + x01(d.Experience) + ",0)"; }) - .style("font-size", "10px"); - - language.selectAll("rect") - .data(function(d) { return d.exp; }) - .enter().append("rect") - .attr("width", x1.rangeBand()) - .attr("x", function(d) { return x1(d.name); }) - .attr("y", function(d) { return y1(d.value); }) - .attr("height", function(d) { return height1 - y1(d.value); }) - .style("fill", function(d) { return color1(d.name); }); - - var legend = svg1.selectAll(".legend") - .data(expLevels.slice()) - .enter().append("g") - .attr("class", "legend") - .attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; }); - - legend.append("rect") - .attr("x", width1 - 18) - .attr("width", 18) - .attr("height", 18) - .style("fill", color1); - - legend.append("text") - .attr("x", width1 - 24) - .attr("y", 9) - .attr("dy", ".35em") - .style("text-anchor", "end") - .text(function(d) { return d; }); - -}); \ No newline at end of file diff --git a/resources/javascript/coursera/difficulty-to-field.js b/resources/javascript/coursera/difficulty-to-field.js deleted file mode 100644 index a9762a39cd..0000000000 --- a/resources/javascript/coursera/difficulty-to-field.js +++ /dev/null @@ -1,93 +0,0 @@ -var margin4 = {top: 20, right: 20, bottom: 30, left: 40}, - width4 = 450 - margin4.left - margin4.right, - height4 = 240 - margin4.top - margin4.bottom; - -var x04 = d3.scale.ordinal() - .rangeRoundBands([0, width4], .1); - -var x4 = d3.scale.ordinal(); - -var y4 = d3.scale.linear() - .range([height4, 0]); - -var color4 = d3.scale.ordinal() - .range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]); - -var xAxis4 = d3.svg.axis() - .scale(x04) - .orient("bottom"); - -var yAxis4 = d3.svg.axis() - .scale(y4) - .orient("left") - .tickFormat(d3.format(".2s")); - -var svg4 = d3.select("#difficulty-to-field").append("svg") - .attr("width", width4 + margin4.left + margin4.right) - .attr("height", height4 + margin4.top + margin4.bottom) - .append("g") - .attr("transform", "translate(" + margin4.left + "," + margin4.top + ")"); - -d3.csv("../resources/dat/difficulty-to-field.csv", function(error, data) { - var expLevels = d3.keys(data[0]).filter(function(key) { return key !== "Field"; }); - - data.forEach(function(d) { - d.exp = expLevels.map(function(name) { return {name: name, value: +d[name]}; }); - }); - - x04.domain(data.map(function(d) { return d.Field; })); - x4.domain(expLevels).rangeRoundBands([0, x04.rangeBand()]); - y4.domain([0, d3.max(data, function(d) { return d3.max(d.exp, function(d) { return d.value; }); })]); - - svg4.append("g") - .attr("class", "x axis") - .attr("transform", "translate(0," + height4 + ")") - .call(xAxis4); - - svg4.append("g") - .attr("class", "y axis") - .call(yAxis4) - .append("text") - .attr("transform", "rotate(-90)") - .attr("y", 6) - .attr("dy", ".71em") - .style("text-anchor", "end") - .text("Percentage") - .style("font-weight", "bold"); - - var field = svg4.selectAll(".field") - .data(data) - .enter().append("g") - .attr("class", "g") - .attr("transform", function(d) { return "translate(" + x04(d.Field) + ",0)"; }) - .style("font-size", "10px"); - - field.selectAll("rect") - .data(function(d) { return d.exp; }) - .enter().append("rect") - .attr("width", x4.rangeBand()) - .attr("x", function(d) { return x4(d.name); }) - .attr("y", function(d) { return y4(d.value); }) - .attr("height", function(d) { return height4 - y4(d.value); }) - .style("fill", function(d) { return color4(d.name); }); - - var legend = svg4.selectAll(".legend") - .data(expLevels.slice()) - .enter().append("g") - .attr("class", "legend") - .attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; }); - - legend.append("rect") - .attr("x", width4 - 18) - .attr("width", 18) - .attr("height", 18) - .style("fill", color4); - - legend.append("text") - .attr("x", width4 - 24) - .attr("y", 9) - .attr("dy", ".35em") - .style("text-anchor", "end") - .text(function(d) { return d; }); - -}); \ No newline at end of file diff --git a/resources/javascript/coursera/difficulty.png b/resources/javascript/coursera/difficulty.png deleted file mode 100644 index 3c4a607c68..0000000000 Binary files a/resources/javascript/coursera/difficulty.png and /dev/null differ diff --git a/resources/javascript/coursera/editors.js b/resources/javascript/coursera/editors.js deleted file mode 100644 index d1de070e15..0000000000 --- a/resources/javascript/coursera/editors.js +++ /dev/null @@ -1,93 +0,0 @@ -var margin6 = {top: 20, right: 20, bottom: 30, left: 40}, - width6 = 450 - margin6.left - margin6.right, - height6 = 240 - margin6.top - margin6.bottom; - -var x06 = d3.scale.ordinal() - .rangeRoundBands([0, width6], .1); - -var x6 = d3.scale.ordinal(); - -var y6 = d3.scale.linear() - .range([height6, 0]); - -var color6 = d3.scale.ordinal() - .range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]); - -var xAxis6 = d3.svg.axis() - .scale(x06) - .orient("bottom"); - -var yAxis6 = d3.svg.axis() - .scale(y6) - .orient("left") - .tickFormat(d3.format(".2s")); - -var svg6 = d3.select("#editors").append("svg") - .attr("width", width6 + margin6.left + margin6.right) - .attr("height", height6 + margin6.top + margin6.bottom) - .append("g") - .attr("transform", "translate(" + margin6.left + "," + margin6.top + ")"); - -d3.csv("../resources/dat/editors.csv", function(error, data) { - var expLevels = d3.keys(data[0]).filter(function(key) { return key !== "Editor"; }); - - data.forEach(function(d) { - d.exp = expLevels.map(function(name) { return {name: name, value: +d[name]}; }); - }); - - x06.domain(data.map(function(d) { return d.Editor; })); - x6.domain(expLevels).rangeRoundBands([0, x06.rangeBand()]); - y6.domain([0, d3.max(data, function(d) { return d3.max(d.exp, function(d) { return d.value; }); })]); - - svg6.append("g") - .attr("class", "x axis") - .attr("transform", "translate(0," + height6 + ")") - .call(xAxis6); - - svg6.append("g") - .attr("class", "y axis") - .call(yAxis6) - .append("text") - .attr("transform", "rotate(-90)") - .attr("y", 6) - .attr("dy", ".71em") - .style("text-anchor", "end") - .text("Percentage") - .style("font-weight", "bold"); - - var field = svg6.selectAll(".editor") - .data(data) - .enter().append("g") - .attr("class", "g") - .attr("transform", function(d) { return "translate(" + x06(d.Editor) + ",0)"; }) - .style("font-size", "10px"); - - field.selectAll("rect") - .data(function(d) { return d.exp; }) - .enter().append("rect") - .attr("width", x6.rangeBand()) - .attr("x", function(d) { return x6(d.name); }) - .attr("y", function(d) { return y6(d.value); }) - .attr("height", function(d) { return height6 - y6(d.value); }) - .style("fill", function(d) { return color6(d.name); }); - - var legend = svg6.selectAll(".legend") - .data(expLevels.slice()) - .enter().append("g") - .attr("class", "legend") - .attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; }); - - legend.append("rect") - .attr("x", width6 - 18) - .attr("width", 18) - .attr("height", 18) - .style("fill", color6); - - legend.append("text") - .attr("x", width6 - 24) - .attr("y", 9) - .attr("dy", ".35em") - .style("text-anchor", "end") - .text(function(d) { return d; }); - -}); \ No newline at end of file diff --git a/resources/javascript/coursera/editors.png b/resources/javascript/coursera/editors.png deleted file mode 100644 index 301df1d9fb..0000000000 Binary files a/resources/javascript/coursera/editors.png and /dev/null differ diff --git a/resources/javascript/coursera/fields-of-study.js b/resources/javascript/coursera/fields-of-study.js deleted file mode 100644 index 82339dcceb..0000000000 --- a/resources/javascript/coursera/fields-of-study.js +++ /dev/null @@ -1,85 +0,0 @@ -d3.csv("../resources/dat/fields-of-study.csv", function(error, data) { - data.forEach(function(d) { - d.count = +d.count; - }); - -var valueLabelWidth = 40; // space reserved for value labels (right) -var barHeight = 18; // height of one bar -var barLabelWidth = 200; // space reserved for bar labels -var barLabelPadding = 5; // padding between bar and bar labels (left) -var gridLabelHeight = 18; // space reserved for gridline labels -var gridChartOffset = 3; // space between start of grid and first bar -var maxBarWidth = 260; // width of the bar with the max value - -// accessor functions -var barLabel = function(d) { return d['fieldofstudy']; }; -var barValue = function(d) { return parseFloat(d['count']); }; - -// sorting -var sortedData = data.sort(function(a, b) { - return d3.descending(barValue(a), barValue(b)); -}); - -// scales -var yScale = d3.scale.ordinal().domain(d3.range(0, sortedData.length)).rangeBands([0, sortedData.length * barHeight]); -var y = function(d, i) { return yScale(i); }; -var yText = function(d, i) { return y(d, i) + yScale.rangeBand() / 2; }; -var x = d3.scale.linear().domain([0, d3.max(sortedData, barValue)]).range([0, maxBarWidth]); -// svg container element -var chart = d3.select('#fields-of-study').append("svg") - .attr('width', maxBarWidth + barLabelWidth + valueLabelWidth) - .attr('height', gridLabelHeight + gridChartOffset + sortedData.length * barHeight); -// grid line labels -var gridContainer = chart.append('g') - .attr('transform', 'translate(' + barLabelWidth + ',' + gridLabelHeight + ')'); -gridContainer.selectAll("text").data(x.ticks(10)).enter().append("text") - .attr("x", x) - .attr("dy", -3) - .attr("text-anchor", "middle") - .text(String); -// vertical grid lines -gridContainer.selectAll("line").data(x.ticks(10)).enter().append("line") - .attr("x1", x) - .attr("x2", x) - .attr("y1", 0) - .attr("y2", yScale.rangeExtent()[1] + gridChartOffset) - .style("stroke", "#ccc"); -// bar labels -var labelsContainer = chart.append('g') - .attr('transform', 'translate(' + (barLabelWidth - barLabelPadding) + ',' + (gridLabelHeight + gridChartOffset) + ')'); -labelsContainer.selectAll('text').data(sortedData).enter().append('text') - .attr('y', yText) - .attr('stroke', 'none') - .attr('fill', 'black') - .attr("dy", ".35em") // vertical-align: middle - .attr('text-anchor', 'end') - .text(barLabel) - .style("font-size","13px"); -// bars -var barsContainer = chart.append('g') - .attr('transform', 'translate(' + barLabelWidth + ',' + (gridLabelHeight + gridChartOffset) + ')'); -barsContainer.selectAll("rect").data(sortedData).enter().append("rect") - .attr('y', y) - .attr('height', yScale.rangeBand()) - .attr('width', function(d) { return x(barValue(d)); }) - .attr('stroke', 'white') - .attr('fill', 'steelblue') - .on("mouseover", function(d) { d3.select(this).style("fill", "#3A6991"); }) - .on("mouseout", function(d) { d3.select(this).style("fill", "steelblue"); }); -// bar value labels -barsContainer.selectAll("text").data(sortedData).enter().append("text") - .attr("x", function(d) { return x(barValue(d)); }) - .attr("y", yText) - .attr("dx", 3) // padding-left - .attr("dy", ".35em") // vertical-align: middle - .attr("text-anchor", "start") // text-align: right - .attr("fill", "black") - .attr("stroke", "none") - .text(function(d) { return d3.round(barValue(d), 2); }) - .style("font-size","13px"); -// start line -barsContainer.append("line") - .attr("y1", -gridChartOffset) - .attr("y2", yScale.rangeExtent()[1] + gridChartOffset) - .style("stroke", "#000"); -}); \ No newline at end of file diff --git a/resources/javascript/coursera/g.pie.js b/resources/javascript/coursera/g.pie.js deleted file mode 100644 index 624adeb66b..0000000000 --- a/resources/javascript/coursera/g.pie.js +++ /dev/null @@ -1 +0,0 @@ -(function(){function b(n,h,g,t,e,o){o=o||{};var c=this,q=[],k=n.set(),s=n.set(),m=n.set(),x=[],z=e.length,A=0,D=0,C=0,f=9,B=true;function w(I,H,i,K,G,P){var M=Math.PI/180,E=I+i*Math.cos(-K*M),p=I+i*Math.cos(-G*M),J=I+i/2*Math.cos(-(K+(G-K)/2)*M),O=H+i*Math.sin(-K*M),N=H+i*Math.sin(-G*M),F=H+i/2*Math.sin(-(K+(G-K)/2)*M),L=["M",I,H,"L",E,O,"A",i,i,0,+(Math.abs(G-K)>180),1,p,N,"z"];L.middle={x:J,y:F};return L}s.covers=k;if(z==1){m.push(n.circle(h,g,t).attr({fill:c.colors[0],stroke:o.stroke||"#fff","stroke-width":o.strokewidth==null?1:o.strokewidth}));k.push(n.circle(h,g,t).attr(c.shim));D=e[0];e[0]={value:e[0],order:0,valueOf:function(){return this.value}};m[0].middle={x:h,y:g};m[0].mangle=180}else{for(var y=0;yf){B=false;e[f].value+=e[y];e[f].others=true;C=e[f].value}}z=Math.min(f+1,e.length);C&&e.splice(z)&&(e[f].others=true);for(y=0;y")}for(y=0;y=b*2){c.attr({path:["M",l,k+b,"a",b,b,0,1,1,0,-b*2,b,b,0,1,1,0,b*2,"m",0,-b*2-i,"a",b+i,b+i,0,1,0,0,(b+i)*2,"L",l+b+i,k+j.height/2+i,"l",j.width+2*i,0,0,-j.height-2*i,-j.width-2*i,0,"L",l,k-b-i].join(",")})}else{m=Math.sqrt(Math.pow(b+i,2)-Math.pow(j.height/2+i,2));c.attr({path:["M",l,k+b,"c",-h,0,-b,h-b,-b,-b,0,-h,b-h,-b,b,-b,h,0,b,b-h,b,b,0,h,h-b,b,-b,b,"M",l+m,k-j.height/2-i,"a",b+i,b+i,0,1,0,0,j.height+2*i,"l",b+i-m+j.width+2*i,0,0,-j.height-2*i,"L",l+m,k-j.height/2-i].join(",")})}f=360-f;c.rotate(f,l,k);if(this.attrs){this.attr(this.attrs.x?"x":"cx",l+b+i+(!a?this.type=="text"?j.width:0:j.width/2)).attr("y",a?k:k-j.height/2);this.rotate(f,l,k);f>90&&f<270&&this.attr(this.attrs.x?"x":"cx",l-b-i-(!a?j.width:j.width/2)).rotate(180,l,k)}else{if(f>90&&f<270){this.translate(l-j.x-j.width-b-i,k-j.y-j.height/2);this.rotate(f-180,j.x+j.width+b+i,j.y+j.height/2)}else{this.translate(l-j.x+b+i,k-j.y-j.height/2);this.rotate(f,j.x-b-i,j.y+j.height/2)}}return c.insertBefore(this.node?this:this[0])};Raphael.el.drop=function(d,g,f){var e=this.getBBox(),c=this.paper||this[0].paper,a,j,b,i,h;if(!c){return}switch(this.type){case"text":case"circle":case"ellipse":a=true;break;default:a=false}d=d||0;g=typeof g=="number"?g:(a?e.x+e.width/2:e.x);f=typeof f=="number"?f:(a?e.y+e.height/2:e.y);j=Math.max(e.width,e.height)+Math.min(e.width,e.height);b=c.path(["M",g,f,"l",j,0,"A",j*0.4,j*0.4,0,1,0,g+j*0.7,f-j*0.7,"z"]).attr({fill:"#000",stroke:"none"}).rotate(22.5-d,g,f);d=(d+90)*Math.PI/180;i=(g+j*Math.sin(d))-(a?0:e.width/2);h=(f+j*Math.cos(d))-(a?0:e.height/2);this.attrs?this.attr(this.attrs.x?"x":"cx",i).attr(this.attrs.y?"y":"cy",h):this.translate(i-e.x,h-e.y);return b.insertBefore(this.node?this:this[0])};Raphael.el.flag=function(e,k,j){var g=3,c=this.paper||this[0].paper;if(!c){return}var b=c.path().attr({fill:"#000",stroke:"#000"}),i=this.getBBox(),f=i.height/2,a;switch(this.type){case"text":case"circle":case"ellipse":a=true;break;default:a=false}e=e||0;k=typeof k=="number"?k:(a?i.x+i.width/2:i.x);j=typeof j=="number"?j:(a?i.y+i.height/2:i.y);b.attr({path:["M",k,j,"l",f+g,-f-g,i.width+2*g,0,0,i.height+2*g,-i.width-2*g,0,"z"].join(",")});e=360-e;b.rotate(e,k,j);if(this.attrs){this.attr(this.attrs.x?"x":"cx",k+f+g+(!a?this.type=="text"?i.width:0:i.width/2)).attr("y",a?j:j-i.height/2);this.rotate(e,k,j);e>90&&e<270&&this.attr(this.attrs.x?"x":"cx",k-f-g-(!a?i.width:i.width/2)).rotate(180,k,j)}else{if(e>90&&e<270){this.translate(k-i.x-i.width-f-g,j-i.y-i.height/2);this.rotate(e-180,i.x+i.width+f+g,i.y+i.height/2)}else{this.translate(k-i.x+f+g,j-i.y-i.height/2);this.rotate(e,i.x-f-g,i.y+i.height/2)}}return b.insertBefore(this.node?this:this[0])};Raphael.el.label=function(){var c=this.getBBox(),b=this.paper||this[0].paper,a=Math.min(20,c.width+10,c.height+10)/2;if(!b){return}return b.rect(c.x-a/2,c.y-a/2,c.width+a,c.height+a,a).attr({stroke:"none",fill:"#000"}).insertBefore(this.node?this:this[0])};Raphael.el.blob=function(z,j,i){var g=this.getBBox(),B=Math.PI/180,n=this.paper||this[0].paper,r,A,q;if(!n){return}switch(this.type){case"text":case"circle":case"ellipse":A=true;break;default:A=false}r=n.path().attr({fill:"#000",stroke:"none"});z=(+z+1?z:45)+90;q=Math.min(g.height,g.width);j=typeof j=="number"?j:(A?g.x+g.width/2:g.x);i=typeof i=="number"?i:(A?g.y+g.height/2:g.y);var m=Math.max(g.width+q,q*25/12),t=Math.max(g.height+q,q*25/12),u=j+q*Math.sin((z-22.5)*B),b=i+q*Math.cos((z-22.5)*B),v=j+q*Math.sin((z+22.5)*B),d=i+q*Math.cos((z+22.5)*B),o=(v-u)/2,l=(d-b)/2,f=m/2,e=t/2,s=-Math.sqrt(Math.abs(f*f*e*e-f*f*l*l-e*e*o*o)/(f*f*l*l+e*e*o*o)),c=s*f*l/e+(v+u)/2,a=s*-e*o/f+(d+b)/2;r.attr({x:c,y:a,path:["M",j,i,"L",v,d,"A",f,e,0,1,1,u,b,"z"].join(",")});this.translate(c-g.x-g.width/2,a-g.y-g.height/2);return r.insertBefore(this.node?this:this[0])};Raphael.fn.label=function(a,d,b){var c=this.set();b=this.text(a,d,b).attr(Raphael.g.txtattr);return c.push(b.label(),b)};Raphael.fn.popup=function(a,f,d,b,c){var e=this.set();d=this.text(a,f,d).attr(Raphael.g.txtattr);return e.push(d.popup(b,c),d)};Raphael.fn.tag=function(a,f,d,c,b){var e=this.set();d=this.text(a,f,d).attr(Raphael.g.txtattr);return e.push(d.tag(c,b),d)};Raphael.fn.flag=function(a,e,c,b){var d=this.set();c=this.text(a,e,c).attr(Raphael.g.txtattr);return d.push(c.flag(b),c)};Raphael.fn.drop=function(a,e,c,b){var d=this.set();c=this.text(a,e,c).attr(Raphael.g.txtattr);return d.push(c.drop(b),c)};Raphael.fn.blob=function(a,e,c,b){var d=this.set();c=this.text(a,e,c).attr(Raphael.g.txtattr);return d.push(c.blob(b),c)};Raphael.el.lighter=function(b){b=b||2;var a=[this.attrs.fill,this.attrs.stroke];this.fs=this.fs||[a[0],a[1]];a[0]=Raphael.rgb2hsb(Raphael.getRGB(a[0]).hex);a[1]=Raphael.rgb2hsb(Raphael.getRGB(a[1]).hex);a[0].b=Math.min(a[0].b*b,1);a[0].s=a[0].s/b;a[1].b=Math.min(a[1].b*b,1);a[1].s=a[1].s/b;this.attr({fill:"hsb("+[a[0].h,a[0].s,a[0].b]+")",stroke:"hsb("+[a[1].h,a[1].s,a[1].b]+")"});return this};Raphael.el.darker=function(b){b=b||2;var a=[this.attrs.fill,this.attrs.stroke];this.fs=this.fs||[a[0],a[1]];a[0]=Raphael.rgb2hsb(Raphael.getRGB(a[0]).hex);a[1]=Raphael.rgb2hsb(Raphael.getRGB(a[1]).hex);a[0].s=Math.min(a[0].s*b,1);a[0].b=a[0].b/b;a[1].s=Math.min(a[1].s*b,1);a[1].b=a[1].b/b;this.attr({fill:"hsb("+[a[0].h,a[0].s,a[0].b]+")",stroke:"hsb("+[a[1].h,a[1].s,a[1].b]+")"});return this};Raphael.el.resetBrightness=function(){if(this.fs){this.attr({fill:this.fs[0],stroke:this.fs[1]});delete this.fs}return this};(function(){var c=["lighter","darker","resetBrightness"],a=["popup","tag","flag","label","drop","blob"];for(var b in a){(function(d){Raphael.st[d]=function(){return Raphael.el[d].apply(this,arguments)}})(a[b])}for(var b in c){(function(d){Raphael.st[d]=function(){for(var e=0;e0?0:0.5))*Math.pow(10,b))/Math.pow(10,b);return{from:e,to:l,power:b}},axis:function(p,o,k,D,e,G,g,J,h,a,q){a=a==null?2:a;h=h||"t";G=G||10;q=arguments[arguments.length-1];var C=h=="|"||h==" "?["M",p+0.5,o,"l",0,0.001]:g==1||g==3?["M",p+0.5,o,"l",0,-k]:["M",p,o+0.5,"l",k,0],s=this.snapEnds(D,e,G),H=s.from,z=s.to,F=s.power,E=0,w={font:"11px 'Fontin Sans', Fontin-Sans, sans-serif"},v=q.set(),I;I=(z-H)/G;var n=H,m=F>0?F:0;r=k/G;if(+g==1||+g==3){var b=o,u=(g-1?1:-1)*(a+3+!!(g-1));while(b>=o-k){h!="-"&&h!=" "&&(C=C.concat(["M",p-(h=="+"||h=="|"?a:!(g-1)*a*2),b+0.5,"l",a*2+1,0]));v.push(q.text(p+u,b,(J&&J[E++])||(Math.round(n)==n?n:+n.toFixed(m))).attr(w).attr({"text-anchor":g-1?"start":"end"}));n+=I;b-=r}if(Math.round(b+r-(o-k))){h!="-"&&h!=" "&&(C=C.concat(["M",p-(h=="+"||h=="|"?a:!(g-1)*a*2),o-k+0.5,"l",a*2+1,0]));v.push(q.text(p+u,o-k,(J&&J[E])||(Math.round(n)==n?n:+n.toFixed(m))).attr(w).attr({"text-anchor":g-1?"start":"end"}))}}else{n=H;m=(F>0)*F;u=(g?-1:1)*(a+9+!g);var c=p,r=k/G,A=0,B=0;while(c<=p+k){h!="-"&&h!=" "&&(C=C.concat(["M",c+0.5,o-(h=="+"?a:!!g*a*2),"l",0,a*2+1]));v.push(A=q.text(c,o+u,(J&&J[E++])||(Math.round(n)==n?n:+n.toFixed(m))).attr(w));var l=A.getBBox();if(B>=l.x-5){v.pop(v.length-1).remove()}else{B=l.x+l.width}n+=I;c+=r}if(Math.round(c-r-p-k)){h!="-"&&h!=" "&&(C=C.concat(["M",p+k+0.5,o-(h=="+"?a:!!g*a*2),"l",0,a*2+1]));v.push(q.text(p+k,o+u,(J&&J[E])||(Math.round(n)==n?n:+n.toFixed(m))).attr(w))}}var K=q.path(C);K.text=v;K.all=q.set([K,v]);K.remove=function(){this.text.remove();this.constructor.prototype.remove.call(this)};return K},labelise:function(a,c,b){if(a){return(a+"").replace(/(##+(?:\.#+)?)|(%%+(?:\.%+)?)/g,function(d,f,e){if(f){return(+c).toFixed(f.replace(/^#+\.?/g,"").length)}if(e){return(c*100/b).toFixed(e.replace(/^%+\.?/g,"").length)+"%"}})}else{return(+c).toFixed(0)}}}; \ No newline at end of file diff --git a/resources/javascript/coursera/grades-breakdown.js b/resources/javascript/coursera/grades-breakdown.js deleted file mode 100644 index 7dde14cb80..0000000000 --- a/resources/javascript/coursera/grades-breakdown.js +++ /dev/null @@ -1,88 +0,0 @@ -var margin2 = {top: 20, right: 20, bottom: 30, left: 40}, - width2 = 960 - margin2.left - margin2.right, - height2 = 480 - margin2.top - margin2.bottom; - -var x2 = d3.scale.ordinal() - .rangeRoundBands([0, width2], .1); - -var y2 = d3.scale.linear() - .range([height2, 0]); - -var xAxis2 = d3.svg.axis() - .scale(x2) - .orient("bottom"); - -var yAxis2 = d3.svg.axis() - .scale(y2) - .orient("left"); - -var svg2 = d3.select("#grades-breakdown").append("svg") - .attr("width", width2 + margin2.left + margin2.right) - .attr("height", height2 + margin2.top + margin2.bottom) - .append("g") - .attr("transform", "translate(" + margin2.left + "," + margin2.top + ")"); - -d3.csv("../resources/dat/grades-breakdown.csv", function(error, data) { - - data.forEach(function(d) { - d.grade = +d.grade; - d.count = +d.count; - }); - - x2.domain(data.map(function(d) { return d.grade; })); - y2.domain([0, d3.max(data, function(d) { return d.count; })]); - - svg2.append("g") - .attr("class", "x axis") - .attr("transform", "translate(0," + height2 + ")") - .style("font-size", "9px") - .call(xAxis2) - .append("text") - .attr("y", 30) - .attr("x", width2 - margin2.left) - .style("font-weight", "bold") - .style("font-size", "13px") - .text("Score"); - - svg2.append("g") - .attr("class", "y axis") - .call(yAxis2) - .append("text") - .attr("transform", "rotate(-90)") - .attr("y", 6) - .attr("dy", ".71em") - .style("text-anchor", "end") - .style("font-weight", "bold") - .text("Number of Students"); - - svg2.selectAll(".bar") - .data(data) - .enter().append("rect") - .attr("class", "bar") - .attr("x", function(d) { return x2(d.grade); }) - .attr("width", x2.rangeBand()) - .attr("y", function(d) { return y2(d.count); }) - .attr("height", function(d) { return height2 - y2(d.count); }) - .on ("mouseover",mover) - .on ("mouseout",mout); - - function mover(d) { - $("#pop-up").fadeOut(0,function () { - // Popup content - $("#pop-up-title").html(d.count); - - // Popup position - var popLeft = x2(d.grade) + margin2.left;//lE.cL[0] + 20; - var popTop = y2(d.count);//lE.cL[1] + 70; - $("#pop-up").css({"left":popLeft,"top":popTop}); - $("#pop-up").fadeIn(0); - }); - d3.select(this).style("fill", "#3A6991"); - } - - function mout(d) { - $("#pop-up").fadeOut(50); - d3.select(this).attr("fill","url(#ten1)"); - d3.select(this).style("fill", "steelblue"); - } -}); \ No newline at end of file diff --git a/resources/javascript/coursera/jquery-jvectormap-1.1.1.min.js b/resources/javascript/coursera/jquery-jvectormap-1.1.1.min.js deleted file mode 100644 index 17450a0983..0000000000 --- a/resources/javascript/coursera/jquery-jvectormap-1.1.1.min.js +++ /dev/null @@ -1,7 +0,0 @@ -/** - * jVectorMap version 1.1 - * - * Copyright 2011-2012, Kirill Lebedev - * Licensed under the MIT license. - * - */(function(e){var t={set:{colors:1,values:1,backgroundColor:1,scaleColors:1,normalizeFunction:1,focus:1},get:{selectedRegions:1,selectedMarkers:1,mapObject:1,regionName:1}};e.fn.vectorMap=function(e){var n,r,i,n=this.children(".jvectormap-container").data("mapObject");if(e==="addMap")jvm.WorldMap.maps[arguments[1]]=arguments[2];else{if(!(e!=="set"&&e!=="get"||!t[e][arguments[1]]))return r=arguments[1].charAt(0).toUpperCase()+arguments[1].substr(1),n[e+r].apply(n,Array.prototype.slice.call(arguments,2));e=e||{},e.container=this,n=new jvm.WorldMap(e)}return this}})(jQuery),function(e){function r(t){var n=t||window.event,r=[].slice.call(arguments,1),i=0,s=!0,o=0,u=0;return t=e.event.fix(n),t.type="mousewheel",n.wheelDelta&&(i=n.wheelDelta/120),n.detail&&(i=-n.detail/3),u=i,n.axis!==undefined&&n.axis===n.HORIZONTAL_AXIS&&(u=0,o=-1*i),n.wheelDeltaY!==undefined&&(u=n.wheelDeltaY/120),n.wheelDeltaX!==undefined&&(o=-1*n.wheelDeltaX/120),r.unshift(t,i,o,u),(e.event.dispatch||e.event.handle).apply(this,r)}var t=["DOMMouseScroll","mousewheel"];if(e.event.fixHooks)for(var n=t.length;n;)e.event.fixHooks[t[--n]]=e.event.mouseHooks;e.event.special.mousewheel={setup:function(){if(this.addEventListener)for(var e=t.length;e;)this.addEventListener(t[--e],r,!1);else this.onmousewheel=r},teardown:function(){if(this.removeEventListener)for(var e=t.length;e;)this.removeEventListener(t[--e],r,!1);else this.onmousewheel=null}},e.fn.extend({mousewheel:function(e){return e?this.bind("mousewheel",e):this.trigger("mousewheel")},unmousewheel:function(e){return this.unbind("mousewheel",e)}})}(jQuery);var jvm={inherits:function(e,t){function n(){}n.prototype=t.prototype,e.prototype=new n,e.prototype.constructor=e,e.parentClass=t},mixin:function(e,t){var n;for(n in t.prototype)t.prototype.hasOwnProperty(n)&&(e.prototype[n]=t.prototype[n])},min:function(e){var t=Number.MAX_VALUE,n;if(e instanceof Array)for(n=0;nt&&(t=e[n]);else for(n in e)e[n]>t&&(t=e[n]);return t},keys:function(e){var t=[],n;for(n in e)t.push(n);return t},values:function(e){var t=[],n,r;for(r=0;r')}}catch(e){jvm.VMLElement.prototype.createElement=function(e){return document.createElement("<"+e+' xmlns="urn:schemas-microsoft.com:vml" class="rvml">')}}document.createStyleSheet().addRule(".rvml","behavior:url(#default#VML)"),jvm.VMLElement.VMLInitialized=!0},jvm.VMLElement.prototype.getElementCtr=function(e){return jvm["VML"+e]},jvm.VMLElement.prototype.addClass=function(e){jvm.$(this.node).addClass(e)},jvm.VMLElement.prototype.applyAttr=function(e,t){this.node[e]=t},jvm.VMLElement.prototype.getBBox=function(){var e=jvm.$(this.node);return{x:e.position().left/this.canvas.scale,y:e.position().top/this.canvas.scale,width:e.width()/this.canvas.scale,height:e.height()/this.canvas.scale}},jvm.VMLGroupElement=function(){jvm.VMLGroupElement.parentClass.call(this,"group"),this.node.style.left="0px",this.node.style.top="0px",this.node.coordorigin="0 0"},jvm.inherits(jvm.VMLGroupElement,jvm.VMLElement),jvm.VMLGroupElement.prototype.add=function(e){this.node.appendChild(e.node)},jvm.VMLCanvasElement=function(e,t,n){this.classPrefix="VML",jvm.VMLCanvasElement.parentClass.call(this,"group"),jvm.AbstractCanvasElement.apply(this,arguments),this.node.style.position="absolute"},jvm.inherits(jvm.VMLCanvasElement,jvm.VMLElement),jvm.mixin(jvm.VMLCanvasElement,jvm.AbstractCanvasElement),jvm.VMLCanvasElement.prototype.setSize=function(e,t){var n,r,i,s;this.width=e,this.height=t,this.node.style.width=e+"px",this.node.style.height=t+"px",this.node.coordsize=e+" "+t,this.node.coordorigin="0 0";if(this.rootElement){n=this.rootElement.node.getElementsByTagName("shape");for(i=0,s=n.length;i=0)e-=t[i],i++;return i==this.scale.length-1?e=this.vectorToNum(this.scale[i]):e=this.vectorToNum(this.vectorAdd(this.scale[i],this.vectorMult(this.vectorSubtract(this.scale[i+1],this.scale[i]),e/t[i]))),e},vectorToNum:function(e){var t=0,n;for(n=0;nt&&(t=e[i]),r").css({width:"100%",height:"100%"}).addClass("jvectormap-container"),this.params.container.append(this.container),this.container.data("mapObject",this),this.container.css({position:"relative",overflow:"hidden"}),this.defaultWidth=this.mapData.width,this.defaultHeight=this.mapData.height,this.setBackgroundColor(this.params.backgroundColor),this.onResize=function(){t.setSize()},jvm.$(window).resize(this.onResize);for(n in jvm.WorldMap.apiEvents)this.params[n]&&this.container.bind(jvm.WorldMap.apiEvents[n]+".jvectormap",this.params[n]);this.canvas=new jvm.VectorCanvas(this.container[0],this.width,this.height),"ontouchstart"in window||window.DocumentTouch&&document instanceof DocumentTouch?this.params.bindTouchEvents&&this.bindContainerTouchEvents():this.bindContainerEvents(),this.bindElementEvents(),this.createLabel(),this.bindZoomButtons(),this.createRegions(),this.createMarkers(this.params.markers||{}),this.setSize(),this.params.focusOn&&(typeof this.params.focusOn=="object"?this.setFocus.call(this,this.params.focusOn.scale,this.params.focusOn.x,this.params.focusOn.y):this.setFocus.call(this,this.params.focusOn)),this.params.selectedRegions&&this.setSelectedRegions(this.params.selectedRegions),this.params.selectedMarkers&&this.setSelectedMarkers(this.params.selectedMarkers),this.params.series&&this.createSeries()},jvm.WorldMap.prototype={transX:0,transY:0,scale:1,baseTransX:0,baseTransY:0,baseScale:1,width:0,height:0,setBackgroundColor:function(e){this.container.css("background-color",e)},resize:function(){var e=this.baseScale;this.width/this.height>this.defaultWidth/this.defaultHeight?(this.baseScale=this.height/this.defaultHeight,this.baseTransX=Math.abs(this.width-this.defaultWidth*this.baseScale)/(2*this.baseScale)):(this.baseScale=this.width/this.defaultWidth,this.baseTransY=Math.abs(this.height-this.defaultHeight*this.baseScale)/(2*this.baseScale)),this.scale*=this.baseScale/e,this.transX*=this.baseScale/e,this.transY*=this.baseScale/e},setSize:function(){this.width=this.container.width(),this.height=this.container.height(),this.resize(),this.canvas.setSize(this.width,this.height),this.applyTransform()},reset:function(){var e,t;for(e in this.series)for(t=0;tt?this.transY=t:this.transYe?this.transX=e:this.transXt[1].pageX?o=t[1].pageX+(t[0].pageX-t[1].pageX)/2:o=t[0].pageX+(t[1].pageX-t[0].pageX)/2,t[0].pageY>t[1].pageY?u=t[1].pageY+(t[0].pageY-t[1].pageY)/2:u=t[0].pageY+(t[1].pageY-t[0].pageY)/2),i=e.originalEvent.touches[0].pageX,s=e.originalEvent.touches[0].pageY}),jvm.$(this.container).bind("touchmove",function(e){var t;if(r.scale!=r.baseScale)return e.originalEvent.touches.length==1&&i&&s?(t=e.originalEvent.touches[0],r.transX-=(i-t.pageX)/r.scale,r.transY-=(s-t.pageY)/r.scale,r.applyTransform(),r.label.hide(),i=t.pageX,s=t.pageY):(i=!1,s=!1),!1})},bindElementEvents:function(){var e=this,t;this.container.mousemove(function(){t=!0}),this.container.delegate("[class~='jvectormap-element']","mouseover mouseout",function(t){var n=this,r=jvm.$(this).attr("class").indexOf("jvectormap-region")===-1?"marker":"region",i=r=="region"?jvm.$(this).attr("data-code"):jvm.$(this).attr("data-index"),s=r=="region"?e.regions[i].element:e.markers[i].element,o=r=="region"?e.mapData.paths[i].name:e.markers[i].config.name||"",u=jvm.$.Event(r+"LabelShow.jvectormap"),a=jvm.$.Event(r+"Over.jvectormap");t.type=="mouseover"?(e.container.trigger(a,[i]),a.isDefaultPrevented()||s.setHovered(!0),e.label.text(o),e.container.trigger(u,[e.label,i]),u.isDefaultPrevented()||(e.label.show(),e.labelWidth=e.label.width(),e.labelHeight=e.label.height())):(s.setHovered(!1),e.label.hide(),e.container.trigger(r+"Out.jvectormap",[i]))}),this.container.delegate("[class~='jvectormap-element']","mousedown",function(e){t=!1}),this.container.delegate("[class~='jvectormap-element']","mouseup",function(n){var r=this,i=jvm.$(this).attr("class").indexOf("jvectormap-region")===-1?"marker":"region",s=i=="region"?jvm.$(this).attr("data-code"):jvm.$(this).attr("data-index"),o=jvm.$.Event(i+"Click.jvectormap"),u=i=="region"?e.regions[s].element:e.markers[s].element;if(!t){e.container.trigger(o,[s]);if(i==="region"&&e.params.regionsSelectable||i==="marker"&&e.params.markersSelectable)o.isDefaultPrevented()||(e.params[i+"sSelectableOne"]&&e.clearSelected(i+"s"),u.setSelected(!u.isSelected))}})},bindZoomButtons:function(){var e=this;jvm.$("
        ").addClass("jvectormap-zoomin").text("+").appendTo(this.container),jvm.$("
        ").addClass("jvectormap-zoomout").html("−").appendTo(this.container),this.container.find(".jvectormap-zoomin").click(function(){e.setScale(e.scale*e.params.zoomStep,e.width/2,e.height/2)}),this.container.find(".jvectormap-zoomout").click(function(){e.setScale(e.scale/e.params.zoomStep,e.width/2,e.height/2)})},createLabel:function(){var e=this;this.label=jvm.$("
        ").addClass("jvectormap-label").appendTo(jvm.$("body")),this.container.mousemove(function(t){var n=t.pageX-15-e.labelWidth,r=t.pageY-15-e.labelHeight;n<5&&(n=t.pageX+15),r<5&&(r=t.pageY+15),e.label.is(":visible")&&e.label.css({left:n,top:r})})},setScale:function(e,t,n,r){var i,s=jvm.$.Event("zoom.jvectormap");e>this.params.zoomMax*this.baseScale?e=this.params.zoomMax*this.baseScale:ei[0].x&&ei[0].y&&tf*b.top){e=b.percents[y],p=b.percents[y-1]||0,t=t/b.top*(e-p),o=b.percents[y+1],j=b.anim[e];break}f&&d.attr(b.anim[b.percents[y]])}if(!!j){if(!k){for(var A in j)if(j[g](A))if(U[g](A)||d.paper.customAttributes[g](A)){u[A]=d.attr(A),u[A]==null&&(u[A]=T[A]),v[A]=j[A];switch(U[A]){case C:w[A]=(v[A]-u[A])/t;break;case"colour":u[A]=a.getRGB(u[A]);var B=a.getRGB(v[A]);w[A]={r:(B.r-u[A].r)/t,g:(B.g-u[A].g)/t,b:(B.b-u[A].b)/t};break;case"path":var D=bR(u[A],v[A]),E=D[1];u[A]=D[0],w[A]=[];for(y=0,z=u[A].length;yd)return d;while(cf?c=e:d=e,e=(d-c)/2+c}return e}function n(a,b){var c=o(a,b);return((l*c+k)*c+j)*c}function m(a){return((i*a+h)*a+g)*a}var g=3*b,h=3*(d-b)-g,i=1-g-h,j=3*c,k=3*(e-c)-j,l=1-j-k;return n(a,1/(200*f))}function cq(){return this.x+q+this.y+q+this.width+" × "+this.height}function cp(){return this.x+q+this.y}function cb(a,b,c,d,e,f){a!=null?(this.a=+a,this.b=+b,this.c=+c,this.d=+d,this.e=+e,this.f=+f):(this.a=1,this.b=0,this.c=0,this.d=1,this.e=0,this.f=0)}function bH(b,c,d){b=a._path2curve(b),c=a._path2curve(c);var e,f,g,h,i,j,k,l,m,n,o=d?0:[];for(var p=0,q=b.length;p=0&&y<=1&&A>=0&&A<=1&&(d?n++:n.push({x:x.x,y:x.y,t1:y,t2:A}))}}return n}function bF(a,b){return bG(a,b,1)}function bE(a,b){return bG(a,b)}function bD(a,b,c,d,e,f,g,h){if(!(x(a,c)x(e,g)||x(b,d)x(f,h))){var i=(a*d-b*c)*(e-g)-(a-c)*(e*h-f*g),j=(a*d-b*c)*(f-h)-(b-d)*(e*h-f*g),k=(a-c)*(f-h)-(b-d)*(e-g);if(!k)return;var l=i/k,m=j/k,n=+l.toFixed(2),o=+m.toFixed(2);if(n<+y(a,c).toFixed(2)||n>+x(a,c).toFixed(2)||n<+y(e,g).toFixed(2)||n>+x(e,g).toFixed(2)||o<+y(b,d).toFixed(2)||o>+x(b,d).toFixed(2)||o<+y(f,h).toFixed(2)||o>+x(f,h).toFixed(2))return;return{x:l,y:m}}}function bC(a,b,c,d,e,f,g,h,i){if(!(i<0||bB(a,b,c,d,e,f,g,h)n)k/=2,l+=(m1?1:i<0?0:i;var j=i/2,k=12,l=[-0.1252,.1252,-0.3678,.3678,-0.5873,.5873,-0.7699,.7699,-0.9041,.9041,-0.9816,.9816],m=[.2491,.2491,.2335,.2335,.2032,.2032,.1601,.1601,.1069,.1069,.0472,.0472],n=0;for(var o=0;od;d+=2){var f=[{x:+a[d-2],y:+a[d-1]},{x:+a[d],y:+a[d+1]},{x:+a[d+2],y:+a[d+3]},{x:+a[d+4],y:+a[d+5]}];b?d?e-4==d?f[3]={x:+a[0],y:+a[1]}:e-2==d&&(f[2]={x:+a[0],y:+a[1]},f[3]={x:+a[2],y:+a[3]}):f[0]={x:+a[e-2],y:+a[e-1]}:e-4==d?f[3]=f[2]:d||(f[0]={x:+a[d],y:+a[d+1]}),c.push(["C",(-f[0].x+6*f[1].x+f[2].x)/6,(-f[0].y+6*f[1].y+f[2].y)/6,(f[1].x+6*f[2].x-f[3].x)/6,(f[1].y+6*f[2].y-f[3].y)/6,f[2].x,f[2].y])}return c}function bx(){return this.hex}function bv(a,b,c){function d(){var e=Array.prototype.slice.call(arguments,0),f=e.join("␀"),h=d.cache=d.cache||{},i=d.count=d.count||[];if(h[g](f)){bu(i,f);return c?c(h[f]):h[f]}i.length>=1e3&&delete h[i.shift()],i.push(f),h[f]=a[m](b,e);return c?c(h[f]):h[f]}return d}function bu(a,b){for(var c=0,d=a.length;c',bl=bk.firstChild,bl.style.behavior="url(#default#VML)";if(!bl||typeof bl.adj!="object")return a.type=p;bk=null}a.svg=!(a.vml=a.type=="VML"),a._Paper=j,a.fn=k=j.prototype=a.prototype,a._id=0,a._oid=0,a.is=function(a,b){b=v.call(b);if(b=="finite")return!M[g](+a);if(b=="array")return a instanceof Array;return b=="null"&&a===null||b==typeof a&&a!==null||b=="object"&&a===Object(a)||b=="array"&&Array.isArray&&Array.isArray(a)||H.call(a).slice(8,-1).toLowerCase()==b},a.angle=function(b,c,d,e,f,g){if(f==null){var h=b-d,i=c-e;if(!h&&!i)return 0;return(180+w.atan2(-i,-h)*180/B+360)%360}return a.angle(b,c,f,g)-a.angle(d,e,f,g)},a.rad=function(a){return a%360*B/180},a.deg=function(a){return a*180/B%360},a.snapTo=function(b,c,d){d=a.is(d,"finite")?d:10;if(a.is(b,E)){var e=b.length;while(e--)if(z(b[e]-c)<=d)return b[e]}else{b=+b;var f=c%b;if(fb-d)return c-f+b}return c};var bn=a.createUUID=function(a,b){return function(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(a,b).toUpperCase()}}(/[xy]/g,function(a){var b=w.random()*16|0,c=a=="x"?b:b&3|8;return c.toString(16)});a.setWindow=function(b){eve("raphael.setWindow",a,h.win,b),h.win=b,h.doc=h.win.document,a._engine.initWin&&a._engine.initWin(h.win)};var bo=function(b){if(a.vml){var c=/^\s+|\s+$/g,d;try{var e=new ActiveXObject("htmlfile");e.write(""),e.close(),d=e.body}catch(f){d=createPopup().document.body}var g=d.createTextRange();bo=bv(function(a){try{d.style.color=r(a).replace(c,p);var b=g.queryCommandValue("ForeColor");b=(b&255)<<16|b&65280|(b&16711680)>>>16;return"#"+("000000"+b.toString(16)).slice(-6)}catch(e){return"none"}})}else{var i=h.doc.createElement("i");i.title="Raphaël Colour Picker",i.style.display="none",h.doc.body.appendChild(i),bo=bv(function(a){i.style.color=a;return h.doc.defaultView.getComputedStyle(i,p).getPropertyValue("color")})}return bo(b)},bp=function(){return"hsb("+[this.h,this.s,this.b]+")"},bq=function(){return"hsl("+[this.h,this.s,this.l]+")"},br=function(){return this.hex},bs=function(b,c,d){c==null&&a.is(b,"object")&&"r"in b&&"g"in b&&"b"in b&&(d=b.b,c=b.g,b=b.r);if(c==null&&a.is(b,D)){var e=a.getRGB(b);b=e.r,c=e.g,d=e.b}if(b>1||c>1||d>1)b/=255,c/=255,d/=255;return[b,c,d]},bt=function(b,c,d,e){b*=255,c*=255,d*=255;var f={r:b,g:c,b:d,hex:a.rgb(b,c,d),toString:br};a.is(e,"finite")&&(f.opacity=e);return f};a.color=function(b){var c;a.is(b,"object")&&"h"in b&&"s"in b&&"b"in b?(c=a.hsb2rgb(b),b.r=c.r,b.g=c.g,b.b=c.b,b.hex=c.hex):a.is(b,"object")&&"h"in b&&"s"in b&&"l"in b?(c=a.hsl2rgb(b),b.r=c.r,b.g=c.g,b.b=c.b,b.hex=c.hex):(a.is(b,"string")&&(b=a.getRGB(b)),a.is(b,"object")&&"r"in b&&"g"in b&&"b"in b?(c=a.rgb2hsl(b),b.h=c.h,b.s=c.s,b.l=c.l,c=a.rgb2hsb(b),b.v=c.b):(b={hex:"none"},b.r=b.g=b.b=b.h=b.s=b.v=b.l=-1)),b.toString=br;return b},a.hsb2rgb=function(a,b,c,d){this.is(a,"object")&&"h"in a&&"s"in a&&"b"in a&&(c=a.b,b=a.s,a=a.h,d=a.o),a*=360;var e,f,g,h,i;a=a%360/60,i=c*b,h=i*(1-z(a%2-1)),e=f=g=c-i,a=~~a,e+=[i,h,0,0,h,i][a],f+=[h,i,i,h,0,0][a],g+=[0,0,h,i,i,h][a];return bt(e,f,g,d)},a.hsl2rgb=function(a,b,c,d){this.is(a,"object")&&"h"in a&&"s"in a&&"l"in a&&(c=a.l,b=a.s,a=a.h);if(a>1||b>1||c>1)a/=360,b/=100,c/=100;a*=360;var e,f,g,h,i;a=a%360/60,i=2*b*(c<.5?c:1-c),h=i*(1-z(a%2-1)),e=f=g=c-i/2,a=~~a,e+=[i,h,0,0,h,i][a],f+=[h,i,i,h,0,0][a],g+=[0,0,h,i,i,h][a];return bt(e,f,g,d)},a.rgb2hsb=function(a,b,c){c=bs(a,b,c),a=c[0],b=c[1],c=c[2];var d,e,f,g;f=x(a,b,c),g=f-y(a,b,c),d=g==0?null:f==a?(b-c)/g:f==b?(c-a)/g+2:(a-b)/g+4,d=(d+360)%6*60/360,e=g==0?0:g/f;return{h:d,s:e,b:f,toString:bp}},a.rgb2hsl=function(a,b,c){c=bs(a,b,c),a=c[0],b=c[1],c=c[2];var d,e,f,g,h,i;g=x(a,b,c),h=y(a,b,c),i=g-h,d=i==0?null:g==a?(b-c)/i:g==b?(c-a)/i+2:(a-b)/i+4,d=(d+360)%6*60/360,f=(g+h)/2,e=i==0?0:f<.5?i/(2*f):i/(2-2*f);return{h:d,s:e,l:f,toString:bq}},a._path2string=function(){return this.join(",").replace(Y,"$1")};var bw=a._preload=function(a,b){var c=h.doc.createElement("img");c.style.cssText="position:absolute;left:-9999em;top:-9999em",c.onload=function(){b.call(this),this.onload=null,h.doc.body.removeChild(this)},c.onerror=function(){h.doc.body.removeChild(this)},h.doc.body.appendChild(c),c.src=a};a.getRGB=bv(function(b){if(!b||!!((b=r(b)).indexOf("-")+1))return{r:-1,g:-1,b:-1,hex:"none",error:1,toString:bx};if(b=="none")return{r:-1,g:-1,b:-1,hex:"none",toString:bx};!X[g](b.toLowerCase().substring(0,2))&&b.charAt()!="#"&&(b=bo(b));var c,d,e,f,h,i,j,k=b.match(L);if(k){k[2]&&(f=R(k[2].substring(5),16),e=R(k[2].substring(3,5),16),d=R(k[2].substring(1,3),16)),k[3]&&(f=R((i=k[3].charAt(3))+i,16),e=R((i=k[3].charAt(2))+i,16),d=R((i=k[3].charAt(1))+i,16)),k[4]&&(j=k[4][s](W),d=Q(j[0]),j[0].slice(-1)=="%"&&(d*=2.55),e=Q(j[1]),j[1].slice(-1)=="%"&&(e*=2.55),f=Q(j[2]),j[2].slice(-1)=="%"&&(f*=2.55),k[1].toLowerCase().slice(0,4)=="rgba"&&(h=Q(j[3])),j[3]&&j[3].slice(-1)=="%"&&(h/=100));if(k[5]){j=k[5][s](W),d=Q(j[0]),j[0].slice(-1)=="%"&&(d*=2.55),e=Q(j[1]),j[1].slice(-1)=="%"&&(e*=2.55),f=Q(j[2]),j[2].slice(-1)=="%"&&(f*=2.55),(j[0].slice(-3)=="deg"||j[0].slice(-1)=="°")&&(d/=360),k[1].toLowerCase().slice(0,4)=="hsba"&&(h=Q(j[3])),j[3]&&j[3].slice(-1)=="%"&&(h/=100);return a.hsb2rgb(d,e,f,h)}if(k[6]){j=k[6][s](W),d=Q(j[0]),j[0].slice(-1)=="%"&&(d*=2.55),e=Q(j[1]),j[1].slice(-1)=="%"&&(e*=2.55),f=Q(j[2]),j[2].slice(-1)=="%"&&(f*=2.55),(j[0].slice(-3)=="deg"||j[0].slice(-1)=="°")&&(d/=360),k[1].toLowerCase().slice(0,4)=="hsla"&&(h=Q(j[3])),j[3]&&j[3].slice(-1)=="%"&&(h/=100);return a.hsl2rgb(d,e,f,h)}k={r:d,g:e,b:f,toString:bx},k.hex="#"+(16777216|f|e<<8|d<<16).toString(16).slice(1),a.is(h,"finite")&&(k.opacity=h);return k}return{r:-1,g:-1,b:-1,hex:"none",error:1,toString:bx}},a),a.hsb=bv(function(b,c,d){return a.hsb2rgb(b,c,d).hex}),a.hsl=bv(function(b,c,d){return a.hsl2rgb(b,c,d).hex}),a.rgb=bv(function(a,b,c){return"#"+(16777216|c|b<<8|a<<16).toString(16).slice(1)}),a.getColor=function(a){var b=this.getColor.start=this.getColor.start||{h:0,s:1,b:a||.75},c=this.hsb2rgb(b.h,b.s,b.b);b.h+=.075,b.h>1&&(b.h=0,b.s-=.2,b.s<=0&&(this.getColor.start={h:0,s:1,b:b.b}));return c.hex},a.getColor.reset=function(){delete this.start},a.parsePathString=function(b){if(!b)return null;var c=bz(b);if(c.arr)return bJ(c.arr);var d={a:7,c:6,h:1,l:2,m:2,r:4,q:4,s:4,t:2,v:1,z:0},e=[];a.is(b,E)&&a.is(b[0],E)&&(e=bJ(b)),e.length||r(b).replace(Z,function(a,b,c){var f=[],g=b.toLowerCase();c.replace(_,function(a,b){b&&f.push(+b)}),g=="m"&&f.length>2&&(e.push([b][n](f.splice(0,2))),g="l",b=b=="m"?"l":"L");if(g=="r")e.push([b][n](f));else while(f.length>=d[g]){e.push([b][n](f.splice(0,d[g])));if(!d[g])break}}),e.toString=a._path2string,c.arr=bJ(e);return e},a.parseTransformString=bv(function(b){if(!b)return null;var c={r:3,s:4,t:2,m:6},d=[];a.is(b,E)&&a.is(b[0],E)&&(d=bJ(b)),d.length||r(b).replace($,function(a,b,c){var e=[],f=v.call(b);c.replace(_,function(a,b){b&&e.push(+b)}),d.push([b][n](e))}),d.toString=a._path2string;return d});var bz=function(a){var b=bz.ps=bz.ps||{};b[a]?b[a].sleep=100:b[a]={sleep:100},setTimeout(function(){for(var c in b)b[g](c)&&c!=a&&(b[c].sleep--,!b[c].sleep&&delete b[c])});return b[a]};a.findDotsAtSegment=function(a,b,c,d,e,f,g,h,i){var j=1-i,k=A(j,3),l=A(j,2),m=i*i,n=m*i,o=k*a+l*3*i*c+j*3*i*i*e+n*g,p=k*b+l*3*i*d+j*3*i*i*f+n*h,q=a+2*i*(c-a)+m*(e-2*c+a),r=b+2*i*(d-b)+m*(f-2*d+b),s=c+2*i*(e-c)+m*(g-2*e+c),t=d+2*i*(f-d)+m*(h-2*f+d),u=j*a+i*c,v=j*b+i*d,x=j*e+i*g,y=j*f+i*h,z=90-w.atan2(q-s,r-t)*180/B;(q>s||r=a.x&&b<=a.x2&&c>=a.y&&c<=a.y2},a.isBBoxIntersect=function(b,c){var d=a.isPointInsideBBox;return d(c,b.x,b.y)||d(c,b.x2,b.y)||d(c,b.x,b.y2)||d(c,b.x2,b.y2)||d(b,c.x,c.y)||d(b,c.x2,c.y)||d(b,c.x,c.y2)||d(b,c.x2,c.y2)||(b.xc.x||c.xb.x)&&(b.yc.y||c.yb.y)},a.pathIntersection=function(a,b){return bH(a,b)},a.pathIntersectionNumber=function(a,b){return bH(a,b,1)},a.isPointInsidePath=function(b,c,d){var e=a.pathBBox(b);return a.isPointInsideBBox(e,c,d)&&bH(b,[["M",c,d],["H",e.x2+10]],1)%2==1},a._removedFactory=function(a){return function(){eve("raphael.log",null,"Raphaël: you are calling to method “"+a+"” of removed object",a)}};var bI=a.pathBBox=function(a){var b=bz(a);if(b.bbox)return bm(b.bbox);if(!a)return{x:0,y:0,width:0,height:0,x2:0,y2:0};a=bR(a);var c=0,d=0,e=[],f=[],g;for(var h=0,i=a.length;h1&&(v=w.sqrt(v),c=v*c,d=v*d);var x=c*c,y=d*d,A=(f==g?-1:1)*w.sqrt(z((x*y-x*u*u-y*t*t)/(x*u*u+y*t*t))),C=A*c*u/d+(a+h)/2,D=A*-d*t/c+(b+i)/2,E=w.asin(((b-D)/d).toFixed(9)),F=w.asin(((i-D)/d).toFixed(9));E=aF&&(E=E-B*2),!g&&F>E&&(F=F-B*2)}else E=j[0],F=j[1],C=j[2],D=j[3];var G=F-E;if(z(G)>k){var H=F,I=h,J=i;F=E+k*(g&&F>E?1:-1),h=C+c*w.cos(F),i=D+d*w.sin(F),m=bO(h,i,c,d,e,0,g,I,J,[F,H,C,D])}G=F-E;var K=w.cos(E),L=w.sin(E),M=w.cos(F),N=w.sin(F),O=w.tan(G/4),P=4/3*c*O,Q=4/3*d*O,R=[a,b],S=[a+P*L,b-Q*K],T=[h+P*N,i-Q*M],U=[h,i];S[0]=2*R[0]-S[0],S[1]=2*R[1]-S[1];if(j)return[S,T,U][n](m);m=[S,T,U][n](m).join()[s](",");var V=[];for(var W=0,X=m.length;W"1e12"&&(l=.5),z(n)>"1e12"&&(n=.5),l>0&&l<1&&(q=bP(a,b,c,d,e,f,g,h,l),p.push(q.x),o.push(q.y)),n>0&&n<1&&(q=bP(a,b,c,d,e,f,g,h,n),p.push(q.x),o.push(q.y)),i=f-2*d+b-(h-2*f+d),j=2*(d-b)-2*(f-d),k=b-d,l=(-j+w.sqrt(j*j-4*i*k))/2/i,n=(-j-w.sqrt(j*j-4*i*k))/2/i,z(l)>"1e12"&&(l=.5),z(n)>"1e12"&&(n=.5),l>0&&l<1&&(q=bP(a,b,c,d,e,f,g,h,l),p.push(q.x),o.push(q.y)),n>0&&n<1&&(q=bP(a,b,c,d,e,f,g,h,n),p.push(q.x),o.push(q.y));return{min:{x:y[m](0,p),y:y[m](0,o)},max:{x:x[m](0,p),y:x[m](0,o)}}}),bR=a._path2curve=bv(function(a,b){var c=!b&&bz(a);if(!b&&c.curve)return bJ(c.curve);var d=bL(a),e=b&&bL(b),f={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},g={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},h=function(a,b){var c,d;if(!a)return["C",b.x,b.y,b.x,b.y,b.x,b.y];!(a[0]in{T:1,Q:1})&&(b.qx=b.qy=null);switch(a[0]){case"M":b.X=a[1],b.Y=a[2];break;case"A":a=["C"][n](bO[m](0,[b.x,b.y][n](a.slice(1))));break;case"S":c=b.x+(b.x-(b.bx||b.x)),d=b.y+(b.y-(b.by||b.y)),a=["C",c,d][n](a.slice(1));break;case"T":b.qx=b.x+(b.x-(b.qx||b.x)),b.qy=b.y+(b.y-(b.qy||b.y)),a=["C"][n](bN(b.x,b.y,b.qx,b.qy,a[1],a[2]));break;case"Q":b.qx=a[1],b.qy=a[2],a=["C"][n](bN(b.x,b.y,a[1],a[2],a[3],a[4]));break;case"L":a=["C"][n](bM(b.x,b.y,a[1],a[2]));break;case"H":a=["C"][n](bM(b.x,b.y,a[1],b.y));break;case"V":a=["C"][n](bM(b.x,b.y,b.x,a[1]));break;case"Z":a=["C"][n](bM(b.x,b.y,b.X,b.Y))}return a},i=function(a,b){if(a[b].length>7){a[b].shift();var c=a[b];while(c.length)a.splice(b++,0,["C"][n](c.splice(0,6)));a.splice(b,1),l=x(d.length,e&&e.length||0)}},j=function(a,b,c,f,g){a&&b&&a[g][0]=="M"&&b[g][0]!="M"&&(b.splice(g,0,["M",f.x,f.y]),c.bx=0,c.by=0,c.x=a[g][1],c.y=a[g][2],l=x(d.length,e&&e.length||0))};for(var k=0,l=x(d.length,e&&e.length||0);ke){if(c&&!l.start){m=cs(g,h,i[1],i[2],i[3],i[4],i[5],i[6],e-n),k+=["C"+m.start.x,m.start.y,m.m.x,m.m.y,m.x,m.y];if(f)return k;l.start=k,k=["M"+m.x,m.y+"C"+m.n.x,m.n.y,m.end.x,m.end.y,i[5],i[6]].join(),n+=j,g=+i[5],h=+i[6];continue}if(!b&&!c){m=cs(g,h,i[1],i[2],i[3],i[4],i[5],i[6],e-n);return{x:m.x,y:m.y,alpha:m.alpha}}}n+=j,g=+i[5],h=+i[6]}k+=i.shift()+i}l.end=k,m=b?n:c?l:a.findDotsAtSegment(g,h,i[0],i[1],i[2],i[3],i[4],i[5],1),m.alpha&&(m={x:m.x,y:m.y,alpha:m.alpha});return m}},cu=ct(1),cv=ct(),cw=ct(0,1);a.getTotalLength=cu,a.getPointAtLength=cv,a.getSubpath=function(a,b,c){if(this.getTotalLength(a)-c<1e-6)return cw(a,b).end;var d=cw(a,c,1);return b?cw(d,b).end:d},cl.getTotalLength=function(){if(this.type=="path"){if(this.node.getTotalLength)return this.node.getTotalLength();return cu(this.attrs.path)}},cl.getPointAtLength=function(a){if(this.type=="path")return cv(this.attrs.path,a)},cl.getSubpath=function(b,c){if(this.type=="path")return a.getSubpath(this.attrs.path,b,c)};var cx=a.easing_formulas={linear:function(a){return a},"<":function(a){return A(a,1.7)},">":function(a){return A(a,.48)},"<>":function(a){var b=.48-a/1.04,c=w.sqrt(.1734+b*b),d=c-b,e=A(z(d),1/3)*(d<0?-1:1),f=-c-b,g=A(z(f),1/3)*(f<0?-1:1),h=e+g+.5;return(1-h)*3*h*h+h*h*h},backIn:function(a){var b=1.70158;return a*a*((b+1)*a-b)},backOut:function(a){a=a-1;var b=1.70158;return a*a*((b+1)*a+b)+1},elastic:function(a){if(a==!!a)return a;return A(2,-10*a)*w.sin((a-.075)*2*B/.3)+1},bounce:function(a){var b=7.5625,c=2.75,d;a<1/c?d=b*a*a:a<2/c?(a-=1.5/c,d=b*a*a+.75):a<2.5/c?(a-=2.25/c,d=b*a*a+.9375):(a-=2.625/c,d=b*a*a+.984375);return d}};cx.easeIn=cx["ease-in"]=cx["<"],cx.easeOut=cx["ease-out"]=cx[">"],cx.easeInOut=cx["ease-in-out"]=cx["<>"],cx["back-in"]=cx.backIn,cx["back-out"]=cx.backOut;var cy=[],cz=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(a){setTimeout(a,16)},cA=function(){var b=+(new Date),c=0;for(;c1&&!d.next){for(s in k)k[g](s)&&(r[s]=d.totalOrigin[s]);d.el.attr(r),cE(d.anim,d.el,d.anim.percents[0],null,d.totalOrigin,d.repeat-1)}d.next&&!d.stop&&cE(d.anim,d.el,d.next,null,d.totalOrigin,d.repeat)}}a.svg&&m&&m.paper&&m.paper.safari(),cy.length&&cz(cA)},cB=function(a){return a>255?255:a<0?0:a};cl.animateWith=function(b,c,d,e,f,g){var h=this;if(h.removed){g&&g.call(h);return h}var i=d instanceof cD?d:a.animation(d,e,f,g),j,k;cE(i,h,i.percents[0],null,h.attr());for(var l=0,m=cy.length;l.5)*2-1;i(m-.5,2)+i(n-.5,2)>.25&&(n=f.sqrt(.25-i(m-.5,2))*e+.5)&&n!=.5&&(n=n.toFixed(5)-1e-5*e)}return l}),e=e.split(/\s*\-\s*/);if(j=="linear"){var t=e.shift();t=-d(t);if(isNaN(t))return null;var u=[0,0,f.cos(a.rad(t)),f.sin(a.rad(t))],v=1/(g(h(u[2]),h(u[3]))||1);u[2]*=v,u[3]*=v,u[2]<0&&(u[0]=-u[2],u[2]=0),u[3]<0&&(u[1]=-u[3],u[3]=0)}var w=a._parseDots(e);if(!w)return null;k=k.replace(/[\(\)\s,\xb0#]/g,"_"),b.gradient&&k!=b.gradient.id&&(p.defs.removeChild(b.gradient),delete b.gradient);if(!b.gradient){s=q(j+"Gradient",{id:k}),b.gradient=s,q(s,j=="radial"?{fx:m,fy:n}:{x1:u[0],y1:u[1],x2:u[2],y2:u[3],gradientTransform:b.matrix.invert()}),p.defs.appendChild(s);for(var x=0,y=w.length;x1?H.opacity/100:H.opacity});case"stroke":H=a.getRGB(u),i.setAttribute(p,H.hex),p=="stroke"&&H[b]("opacity")&&q(i,{"stroke-opacity":H.opacity>1?H.opacity/100:H.opacity}),p=="stroke"&&d._.arrows&&("startString"in d._.arrows&&t(d,d._.arrows.startString),"endString"in d._.arrows&&t(d,d._.arrows.endString,1));break;case"gradient":(d.type=="circle"||d.type=="ellipse"||c(u).charAt()!="r")&&r(d,u);break;case"opacity":m.gradient&&!m[b]("stroke-opacity")&&q(i,{"stroke-opacity":u>1?u/100:u});case"fill-opacity":if(m.gradient){I=a._g.doc.getElementById(i.getAttribute("fill").replace(/^url\(#|\)$/g,l)),I&&(J=I.getElementsByTagName("stop"),q(J[J.length-1],{"stop-opacity":u}));break};default:p=="font-size"&&(u=e(u,10)+"px");var K=p.replace(/(\-.)/g,function(a){return a.substring(1).toUpperCase()});i.style[K]=u,d._.dirty=1,i.setAttribute(p,u)}}}y(d,f),i.style.visibility=o},x=1.2,y=function(d,f){if(d.type=="text"&&!!(f[b]("text")||f[b]("font")||f[b]("font-size")||f[b]("x")||f[b]("y"))){var g=d.attrs,h=d.node,i=h.firstChild?e(a._g.doc.defaultView.getComputedStyle(h.firstChild,l).getPropertyValue("font-size"),10):10;if(f[b]("text")){g.text=f.text;while(h.firstChild)h.removeChild(h.firstChild);var j=c(f.text).split("\n"),k=[],m;for(var n=0,o=j.length;n"));var $=X.getBoundingClientRect();t.W=m.w=($.right-$.left)/Y,t.H=m.h=($.bottom-$.top)/Y,t.X=m.x,t.Y=m.y+t.H/2,("x"in i||"y"in i)&&(t.path.v=a.format("m{0},{1}l{2},{1}",f(m.x*u),f(m.y*u),f(m.x*u)+1));var _=["x","y","text","font","font-family","font-weight","font-style","font-size"];for(var ba=0,bb=_.length;ba.25&&(c=e.sqrt(.25-i(b-.5,2))*((c>.5)*2-1)+.5),m=b+n+c);return o}),f=f.split(/\s*\-\s*/);if(l=="linear"){var p=f.shift();p=-d(p);if(isNaN(p))return null}var q=a._parseDots(f);if(!q)return null;b=b.shape||b.node;if(q.length){b.removeChild(g),g.on=!0,g.method="none",g.color=q[0].color,g.color2=q[q.length-1].color;var r=[];for(var s=0,t=q.length;s')}}catch(c){F=function(a){return b.createElement("<"+a+' xmlns="urn:schemas-microsoft.com:vml" class="rvml">')}}},a._engine.initWin(a._g.win),a._engine.create=function(){var b=a._getContainer.apply(0,arguments),c=b.container,d=b.height,e,f=b.width,g=b.x,h=b.y;if(!c)throw new Error("VML container not found.");var i=new a._Paper,j=i.canvas=a._g.doc.createElement("div"),k=j.style;g=g||0,h=h||0,f=f||512,d=d||342,i.width=f,i.height=d,f==+f&&(f+="px"),d==+d&&(d+="px"),i.coordsize=u*1e3+n+u*1e3,i.coordorigin="0 0",i.span=a._g.doc.createElement("span"),i.span.style.cssText="position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;",j.appendChild(i.span),k.cssText=a.format("top:0;left:0;width:{0};height:{1};display:inline-block;position:relative;clip:rect(0 {0} {1} 0);overflow:hidden",f,d),c==1?(a._g.doc.body.appendChild(j),k.left=g+"px",k.top=h+"px",k.position="absolute"):c.firstChild?c.insertBefore(j,c.firstChild):c.appendChild(j),i.renderfix=function(){};return i},a.prototype.clear=function(){a.eve("raphael.clear",this),this.canvas.innerHTML=o,this.span=a._g.doc.createElement("span"),this.span.style.cssText="position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;display:inline;",this.canvas.appendChild(this.span),this.bottom=this.top=null},a.prototype.remove=function(){a.eve("raphael.remove",this),this.canvas.parentNode.removeChild(this.canvas);for(var b in this)this[b]=typeof this[b]=="function"?a._removedFactory(b):null;return!0};var G=a.st;for(var H in E)E[b](H)&&!G[b](H)&&(G[H]=function(a){return function(){var b=arguments;return this.forEach(function(c){c[a].apply(c,b)})}}(H))}(window.Raphael) \ No newline at end of file diff --git a/resources/javascript/coursera/timespent.png b/resources/javascript/coursera/timespent.png deleted file mode 100644 index a5262799bd..0000000000 Binary files a/resources/javascript/coursera/timespent.png and /dev/null differ diff --git a/resources/javascript/coursera/what-interested-you.js b/resources/javascript/coursera/what-interested-you.js deleted file mode 100644 index da0d5ade3b..0000000000 --- a/resources/javascript/coursera/what-interested-you.js +++ /dev/null @@ -1,20 +0,0 @@ -var r2 = Raphael("what-interested-you",420, 240); -var pc2 = r2.piechart(310, 120, 100, [4810, 194, 2488],{ legend: ["%%.%% Personal Interest/Curiosity", "%%.%% University Studies", "%%.%% Helps With Profession"], legendpos: "west" }); - -pc2.hover(function () { - this.sector.stop(); - this.sector.scale(1.1, 1.1, this.cx, this.cy); - - if (this.label) { - this.label[0].stop(); - this.label[0].attr({ r: 7.5 }); - this.label[1].attr({ "font-weight": 800 }); - } -}, function () { - this.sector.animate({ transform: 's1 1 ' + this.cx + ' ' + this.cy }, 500, "bounce"); - - if (this.label) { - this.label[0].animate({ r: 5 }, 500, "bounce"); - this.label[1].attr({ "font-weight": 400 }); - } -}); \ No newline at end of file diff --git a/resources/javascript/coursera/where-apply.js b/resources/javascript/coursera/where-apply.js deleted file mode 100644 index d120e9ba93..0000000000 --- a/resources/javascript/coursera/where-apply.js +++ /dev/null @@ -1,20 +0,0 @@ -var r1 = Raphael("where-apply",500, 240); -var pc1 = r1.piechart(110, 120, 100, [2533, 1186, 1837, 302, 1634],{ legend: ["%%.%% Personal Projects", "%%.%% Individual project at work", "%%.%% Team project at work", "%%.%% University projects", "%%.%% No application plans, general interest"], legendpos: "east" }); - -pc1.hover(function () { - this.sector.stop(); - this.sector.scale(1.1, 1.1, this.cx, this.cy); - - if (this.label) { - this.label[0].stop(); - this.label[0].attr({ r: 7.5 }); - this.label[1].attr({ "font-weight": 800 }); - } -}, function () { - this.sector.animate({ transform: 's1 1 ' + this.cx + ' ' + this.cy }, 500, "bounce"); - - if (this.label) { - this.label[0].animate({ r: 5 }, 500, "bounce"); - this.label[1].attr({ "font-weight": 400 }); - } -}); diff --git a/resources/javascript/coursera/whereapply.png b/resources/javascript/coursera/whereapply.png deleted file mode 100644 index 748caff0be..0000000000 Binary files a/resources/javascript/coursera/whereapply.png and /dev/null differ diff --git a/resources/javascript/coursera/worldmap-density.js b/resources/javascript/coursera/worldmap-density.js deleted file mode 100644 index 08bf460e0a..0000000000 --- a/resources/javascript/coursera/worldmap-density.js +++ /dev/null @@ -1,18 +0,0 @@ - $('#map-density').vectorMap({ - map: 'world_mill_en', - regionStyle: { - hover: { - "fill-opacity": 0.6 - } - }, - series: { - regions: [{ - values: density, - scale: ['#ffffff', '#330066'], - normalizeFunction: 'polynomial', - }] - }, - onRegionLabelShow: function(e, el, code){ - el.html(""+el.html()+"
        Number of Students: "+count[code]+"
        Population: "+population[code]+""); - } -}); \ No newline at end of file diff --git a/resources/javascript/coursera/worldmap-population.js b/resources/javascript/coursera/worldmap-population.js deleted file mode 100644 index 2747c7cef2..0000000000 --- a/resources/javascript/coursera/worldmap-population.js +++ /dev/null @@ -1,18 +0,0 @@ - $('#map-population').vectorMap({ - map: 'world_mill_en', - regionStyle: { - hover: { - "fill-opacity": 0.6 - } - }, - series: { - regions: [{ - values: studentData, - scale: ['#ffffff', '#005f8a'], - normalizeFunction: 'polynomial', - }] - }, - onRegionLabelShow: function(e, el, code){ - el.html(""+el.html()+"
        Number of Students: "+studentData[code]+"
        Percentage: "+((studentData[code]/tot)*100).toPrecision(3)+"%"); - } -}); \ No newline at end of file diff --git a/resources/javascript/coursera/worldmap.js b/resources/javascript/coursera/worldmap.js deleted file mode 100644 index 89f778398c..0000000000 --- a/resources/javascript/coursera/worldmap.js +++ /dev/null @@ -1 +0,0 @@ -$.fn.vectorMap('addMap', 'world_mill_en',{"insets": [{"width": 900.0, "top": 0, "height": 440.7063107441331, "bbox": [{"y": -12671671.123330014, "x": -20004297.151525836}, {"y": 6930392.02513512, "x": 20026572.394749384}], "left": 0}], "paths": {"BD": {"path": "M652.71,228.85l-0.04,1.38l-0.46,-0.21l-0.42,0.3l0.05,0.65l-0.17,-1.37l-0.48,-1.26l-1.08,-1.6l-0.23,-0.13l-2.31,-0.11l-0.31,0.36l0.21,0.98l-0.6,1.11l-0.8,-0.4l-0.37,0.09l-0.23,0.3l-0.54,-0.21l-0.78,-0.19l-0.38,-2.04l-0.83,-1.89l0.4,-1.5l-0.16,-0.35l-1.24,-0.57l0.36,-0.62l1.5,-0.95l0.02,-0.49l-1.62,-1.26l0.64,-1.31l1.7,1.0l0.12,0.04l0.96,0.11l0.19,1.62l0.25,0.26l2.38,0.37l2.32,-0.04l1.06,0.33l-0.92,1.79l-0.97,0.13l-0.23,0.16l-0.77,1.51l0.05,0.35l1.37,1.37l0.5,-0.14l0.35,-1.46l0.24,-0.0l1.24,3.92Z", "name": "Bangladesh"}, "BE": {"path": "M429.28,143.95l1.76,0.25l0.13,-0.01l2.16,-0.64l1.46,1.34l1.26,0.71l-0.23,1.8l-0.44,0.08l-0.24,0.25l-0.2,1.36l-1.8,-1.22l-0.23,-0.05l-1.14,0.23l-1.62,-1.43l-1.15,-1.31l-0.21,-0.1l-0.95,-0.04l-0.21,-0.68l1.66,-0.54Z", "name": "Belgium"}, "BF": {"path": "M413.48,260.21l-1.22,-0.46l-0.13,-0.02l-1.17,0.1l-0.15,0.06l-0.73,0.53l-0.87,-0.41l-0.39,-0.75l-0.13,-0.13l-0.98,-0.48l-0.14,-1.2l0.63,-0.99l0.05,-0.18l-0.05,-0.73l1.9,-2.01l0.08,-0.14l0.35,-1.65l0.49,-0.44l1.05,0.3l0.21,-0.02l1.05,-0.52l0.13,-0.13l0.3,-0.58l1.87,-1.1l0.11,-0.1l0.43,-0.72l2.23,-1.01l1.21,-0.32l0.51,0.4l0.19,0.06l1.25,-0.01l-0.14,0.89l0.01,0.13l0.34,1.16l0.06,0.11l1.35,1.59l0.07,1.13l0.24,0.28l2.64,0.53l-0.05,1.39l-0.42,0.59l-1.11,0.21l-0.22,0.17l-0.46,0.99l-0.69,0.23l-2.12,-0.05l-1.14,-0.2l-0.19,0.03l-0.72,0.36l-1.07,-0.17l-4.35,0.12l-0.29,0.29l-0.06,1.44l0.25,1.45Z", "name": "Burkina Faso"}, "BG": {"path": "M477.63,166.84l0.51,0.9l0.33,0.14l0.9,-0.21l1.91,0.47l3.68,0.16l0.17,-0.05l1.2,-0.75l2.78,-0.67l1.72,1.05l1.02,0.24l-0.97,0.97l-0.91,2.17l0.0,0.24l0.56,1.19l-1.58,-0.3l-0.16,0.01l-2.55,0.95l-0.2,0.28l-0.02,1.23l-1.92,0.24l-1.68,-0.99l-0.27,-0.02l-1.94,0.8l-1.52,-0.07l-0.15,-1.72l-0.12,-0.21l-0.99,-0.76l0.18,-0.18l0.02,-0.39l-0.17,-0.22l0.33,-0.75l0.91,-0.91l0.01,-0.42l-1.16,-1.25l-0.18,-0.89l0.24,-0.27Z", "name": "Bulgaria"}, "BA": {"path": "M468.39,164.66l0.16,0.04l0.43,-0.0l-0.43,0.93l0.06,0.34l1.08,1.06l-0.28,1.09l-0.5,0.13l-0.47,0.28l-0.86,0.74l-0.1,0.16l-0.28,1.29l-1.81,-0.94l-0.9,-1.22l-1.0,-0.73l-1.1,-1.1l-0.55,-0.96l-1.11,-1.3l0.3,-0.75l0.59,0.46l0.42,-0.04l0.46,-0.54l1.0,-0.06l2.11,0.5l1.72,-0.03l1.06,0.64Z", "name": "Bosnia and Herzegovina"}, "BN": {"path": "M707.34,273.57l0.76,-0.72l1.59,-1.03l-0.18,1.93l-0.9,-0.06l-0.28,0.14l-0.31,0.51l-0.68,-0.78Z", "name": "Brunei"}, "BO": {"path": "M263.83,340.79l-0.23,-0.12l-2.86,-0.11l-0.28,0.17l-0.77,1.67l-1.17,-1.51l-0.18,-0.11l-3.28,-0.64l-0.28,0.1l-2.02,2.3l-1.43,0.29l-0.91,-3.35l-1.31,-2.88l0.75,-2.41l-0.09,-0.32l-1.23,-1.03l-0.31,-1.76l-0.05,-0.12l-1.12,-1.6l1.49,-2.62l0.01,-0.28l-1.0,-2.0l0.48,-0.72l0.02,-0.29l-0.37,-0.78l0.87,-1.13l0.06,-0.18l0.05,-2.17l0.12,-1.71l0.5,-0.8l0.01,-0.3l-1.9,-3.58l1.3,0.15l1.34,-0.05l0.23,-0.12l0.51,-0.7l2.12,-0.99l1.31,-0.93l2.81,-0.37l-0.21,1.51l0.01,0.13l0.29,0.91l-0.19,1.64l0.11,0.27l2.72,2.27l0.15,0.07l2.71,0.41l0.92,0.88l0.12,0.07l1.64,0.49l1.0,0.71l0.18,0.06l1.5,-0.02l1.24,0.64l0.1,1.31l0.05,0.14l0.44,0.68l0.02,0.73l-0.44,0.03l-0.27,0.39l0.96,2.99l0.28,0.21l4.43,0.1l-0.28,1.12l0.0,0.15l0.27,1.02l0.15,0.19l1.27,0.67l0.52,1.42l-0.42,1.91l-0.66,1.1l-0.04,0.2l0.21,1.3l-0.19,0.13l-0.01,-0.27l-0.15,-0.24l-2.33,-1.33l-0.14,-0.04l-2.38,-0.03l-4.36,0.76l-0.21,0.16l-1.2,2.29l-0.03,0.13l-0.06,1.37l-0.79,2.53l-0.05,-0.08Z", "name": "Bolivia"}, "JP": {"path": "M781.17,166.78l1.8,0.67l0.28,-0.04l1.38,-1.01l0.43,2.67l-3.44,0.77l-0.18,0.12l-2.04,2.79l-3.71,-1.94l-0.42,0.15l-1.29,3.11l-2.32,0.04l-0.3,-2.63l1.12,-2.1l2.51,-0.16l0.28,-0.25l0.73,-4.22l0.58,-1.9l2.59,2.84l2.0,1.1ZM773.66,187.36l-0.92,2.24l-0.01,0.2l0.4,1.3l-1.18,1.81l-3.06,1.28l-4.35,0.17l-0.19,0.08l-3.4,3.06l-1.36,-0.87l-0.1,-1.95l-0.34,-0.28l-4.35,0.62l-2.99,1.33l-2.87,0.05l-0.28,0.2l0.09,0.33l2.37,1.93l-1.57,4.44l-1.35,0.97l-0.9,-0.79l0.57,-2.32l-0.15,-0.34l-1.5,-0.77l-0.81,-1.53l2.04,-0.75l0.14,-0.1l1.28,-1.72l2.47,-1.43l1.84,-1.92l4.83,-0.82l2.62,0.57l0.33,-0.16l2.45,-4.77l1.38,1.14l0.38,0.0l5.1,-4.02l0.09,-0.11l1.57,-3.57l0.02,-0.16l-0.42,-3.22l0.94,-1.67l2.27,-0.47l1.26,3.82l-0.07,2.23l-2.26,2.86l-0.06,0.19l0.04,2.93ZM757.85,196.18l0.22,0.66l-1.11,1.33l-0.8,-0.7l-0.33,-0.04l-1.28,0.65l-0.14,0.15l-0.54,1.34l-1.17,-0.57l0.02,-1.03l1.2,-1.45l1.24,0.28l0.29,-0.1l0.9,-1.03l1.51,0.5Z", "name": "Japan"}, "BI": {"path": "M494.7,295.83l-0.14,-2.71l-0.04,-0.13l-0.34,-0.62l0.93,0.12l0.3,-0.16l0.67,-1.25l0.9,0.11l0.11,0.76l0.08,0.16l0.46,0.48l0.02,0.56l-0.55,0.48l-0.96,1.29l-0.82,0.82l-0.61,0.07Z", "name": "Burundi"}, "BJ": {"path": "M427.4,268.94l-1.58,0.22l-0.52,-1.45l0.11,-5.73l-0.08,-0.21l-0.43,-0.44l-0.09,-1.13l-0.09,-0.19l-1.52,-1.52l0.24,-1.01l0.7,-0.23l0.18,-0.16l0.45,-0.97l1.07,-0.21l0.19,-0.12l0.53,-0.73l0.73,-0.65l0.68,-0.0l1.69,1.3l-0.08,0.67l0.02,0.14l0.52,1.38l-0.44,0.9l-0.01,0.24l0.2,0.52l-1.1,1.42l-0.76,0.76l-0.08,0.13l-0.47,1.59l0.05,1.69l-0.13,3.79Z", "name": "Benin"}, "BT": {"path": "M650.38,213.78l0.88,0.75l-0.13,1.24l-1.77,0.07l-2.1,-0.18l-1.57,0.4l-2.02,-0.91l-0.02,-0.24l1.54,-1.87l1.18,-0.6l1.67,0.59l1.32,0.08l1.01,0.67Z", "name": "Bhutan"}, "JM": {"path": "M226.67,238.37l1.64,0.23l1.2,0.56l0.11,0.19l-1.25,0.03l-0.14,0.04l-0.65,0.37l-1.24,-0.37l-1.17,-0.77l0.11,-0.22l0.86,-0.15l0.52,0.08Z", "name": "Jamaica"}, "BW": {"path": "M484.91,331.96l0.53,0.52l0.82,1.53l2.83,2.86l0.14,0.08l0.85,0.22l0.03,0.81l0.74,1.66l0.21,0.17l1.87,0.39l1.17,0.87l-3.13,1.71l-2.3,2.01l-0.07,0.1l-0.82,1.74l-0.66,0.88l-1.24,0.19l-0.24,0.2l-0.65,1.98l-1.4,0.55l-1.9,-0.12l-1.2,-0.74l-1.06,-0.32l-0.22,0.02l-1.22,0.62l-0.14,0.14l-0.58,1.21l-1.16,0.79l-1.18,1.13l-1.5,0.23l-0.4,-0.68l0.22,-1.53l-0.04,-0.19l-1.48,-2.54l-0.11,-0.11l-0.53,-0.31l-0.0,-7.25l2.18,-0.08l0.29,-0.3l0.07,-9.0l1.63,-0.08l3.69,-0.86l0.84,0.93l0.38,0.05l1.53,-0.97l0.79,-0.03l1.3,-0.53l0.23,0.1l0.92,1.96Z", "name": "Botswana"}, "BR": {"path": "M259.49,274.87l1.42,0.25l1.97,0.62l0.28,-0.05l0.67,-0.55l1.76,-0.38l2.8,-0.94l0.12,-0.08l0.92,-0.96l0.05,-0.33l-0.15,-0.32l0.73,-0.06l0.36,0.35l-0.27,0.93l0.17,0.36l0.76,0.34l0.44,0.9l-0.58,0.73l-0.06,0.13l-0.4,2.13l0.03,0.19l0.62,1.22l0.17,1.11l0.11,0.19l1.54,1.18l0.15,0.06l1.23,0.12l0.29,-0.15l0.2,-0.36l0.71,-0.11l1.13,-0.44l0.79,-0.63l1.25,0.19l0.65,-0.08l1.32,0.2l0.32,-0.18l0.23,-0.51l-0.05,-0.31l-0.31,-0.37l0.11,-0.31l0.75,0.17l0.13,0.0l1.1,-0.24l1.34,0.5l1.08,0.51l0.33,-0.05l0.67,-0.58l0.27,0.05l0.28,0.57l0.31,0.17l1.2,-0.18l0.17,-0.08l1.03,-1.05l0.76,-1.82l1.39,-2.16l0.49,-0.07l0.52,1.17l1.4,4.37l0.2,0.2l1.14,0.35l0.05,1.39l-1.8,1.97l0.01,0.42l0.78,0.75l0.18,0.08l4.16,0.37l0.08,2.25l0.5,0.22l1.78,-1.54l2.98,0.85l4.07,1.5l1.07,1.28l-0.37,1.23l0.36,0.38l2.83,-0.75l4.8,1.3l3.75,-0.09l3.6,2.02l3.27,2.84l1.93,0.72l2.13,0.11l0.76,0.66l1.22,4.56l-0.96,4.03l-1.22,1.58l-3.52,3.51l-1.63,2.91l-1.75,2.09l-0.5,0.04l-0.26,0.19l-0.72,1.99l0.18,4.76l-0.95,5.56l-0.74,0.96l-0.06,0.15l-0.43,3.39l-2.49,3.34l-0.06,0.13l-0.4,2.56l-1.9,1.07l-0.13,0.16l-0.51,1.38l-2.59,0.0l-3.94,1.01l-1.82,1.19l-2.85,0.81l-3.01,2.17l-2.12,2.65l-0.06,0.13l-0.36,2.0l0.01,0.13l0.4,1.42l-0.45,2.63l-0.53,1.23l-1.76,1.53l-2.76,4.79l-2.16,2.15l-1.69,1.29l-0.09,0.12l-1.12,2.6l-1.3,1.26l-0.45,-1.02l0.99,-1.18l0.01,-0.37l-1.5,-1.95l-1.98,-1.54l-2.58,-1.77l-0.2,-0.05l-0.81,0.07l-2.42,-2.05l-0.25,-0.07l-0.77,0.14l2.75,-3.07l2.8,-2.61l1.67,-1.09l2.11,-1.49l0.13,-0.24l0.05,-2.15l-0.07,-0.2l-1.26,-1.54l-0.35,-0.09l-0.64,0.27l0.3,-0.95l0.34,-1.57l0.01,-1.52l-0.16,-0.26l-0.9,-0.48l-0.27,-0.01l-0.86,0.39l-0.65,-0.08l-0.23,-0.8l-0.23,-2.39l-0.04,-0.12l-0.47,-0.79l-0.14,-0.12l-1.69,-0.71l-0.25,0.01l-0.93,0.47l-2.29,-0.44l0.15,-3.3l-0.03,-0.15l-0.62,-1.22l0.57,-0.39l0.13,-0.3l-0.22,-1.37l0.67,-1.13l0.44,-2.04l-0.01,-0.17l-0.59,-1.61l-0.14,-0.16l-1.25,-0.66l-0.22,-0.82l0.35,-1.41l-0.28,-0.37l-4.59,-0.1l-0.78,-2.41l0.34,-0.02l0.28,-0.31l-0.03,-1.1l-0.05,-0.16l-0.45,-0.68l-0.1,-1.4l-0.16,-0.24l-1.45,-0.76l-0.14,-0.03l-1.48,0.02l-1.04,-0.73l-1.62,-0.48l-0.93,-0.9l-0.16,-0.08l-2.72,-0.41l-2.53,-2.12l0.18,-1.54l-0.01,-0.13l-0.29,-0.91l0.26,-1.83l-0.34,-0.34l-3.28,0.43l-0.14,0.05l-1.3,0.93l-2.16,1.01l-0.12,0.09l-0.47,0.65l-1.12,0.05l-1.84,-0.21l-0.12,0.01l-1.33,0.41l-0.82,-0.21l0.16,-3.6l-0.48,-0.26l-1.97,1.43l-1.96,-0.06l-0.86,-1.23l-0.22,-0.13l-1.23,-0.11l0.34,-0.69l-0.05,-0.33l-1.36,-1.5l-0.92,-2.0l0.45,-0.32l0.13,-0.25l-0.0,-0.87l1.34,-0.64l0.17,-0.32l-0.23,-1.23l0.56,-0.77l0.05,-0.13l0.16,-1.03l2.7,-1.61l2.01,-0.47l0.16,-0.09l0.24,-0.27l2.11,0.11l0.31,-0.25l1.13,-6.87l0.06,-1.12l-0.4,-1.53l-0.1,-0.15l-1.0,-0.82l0.01,-1.45l1.08,-0.32l0.39,0.2l0.44,-0.24l0.08,-0.96l-0.25,-0.32l-1.22,-0.22l-0.02,-1.01l4.57,0.05l0.22,-0.09l0.6,-0.63l0.44,0.5l0.47,1.42l0.45,0.16l0.27,-0.18l1.21,1.16l0.23,0.08l1.95,-0.16l0.23,-0.14l0.43,-0.67l1.76,-0.55l1.05,-0.42l0.18,-0.2l0.25,-0.92l1.65,-0.66l0.18,-0.35l-0.14,-0.53l-0.26,-0.22l-1.91,-0.19l-0.29,-1.33l0.1,-1.64l-0.15,-0.28l-0.44,-0.25Z", "name": "Brazil"}, "BS": {"path": "M227.51,216.69l0.3,0.18l-0.24,1.07l0.03,-1.04l-0.09,-0.21ZM226.5,224.03l-0.13,0.03l-0.54,-1.3l-0.09,-0.12l-0.78,-0.64l0.4,-1.26l0.33,0.05l0.79,2.0l0.01,1.24ZM225.76,216.5l-2.16,0.34l-0.07,-0.41l0.85,-0.16l1.36,0.07l0.02,0.16Z", "name": "The Bahamas"}, "BY": {"path": "M480.08,135.28l2.09,0.02l0.13,-0.03l2.72,-1.3l0.16,-0.19l0.55,-1.83l1.94,-1.06l0.15,-0.31l-0.2,-1.33l1.33,-0.52l2.58,-1.3l2.39,0.8l0.3,0.75l0.37,0.17l1.22,-0.39l2.18,0.75l0.2,1.36l-0.48,0.85l0.01,0.32l1.57,2.26l0.92,0.6l-0.1,0.41l0.19,0.35l1.61,0.57l0.48,0.6l-0.64,0.49l-1.91,-0.11l-0.18,0.05l-0.48,0.32l-0.1,0.39l0.57,1.1l0.51,1.78l-1.79,0.17l-0.18,0.08l-0.77,0.73l-0.09,0.19l-0.13,1.31l-0.75,-0.22l-2.11,0.15l-0.56,-0.66l-0.39,-0.06l-0.8,0.49l-0.79,-0.4l-0.13,-0.03l-1.94,-0.07l-2.76,-0.79l-2.58,-0.27l-1.98,0.07l-0.15,0.05l-1.31,0.86l-0.8,0.09l-0.04,-1.16l-0.03,-0.12l-0.63,-1.28l1.22,-0.56l0.17,-0.27l0.01,-1.35l-0.04,-0.15l-0.66,-1.24l-0.08,-1.12Z", "name": "Belarus"}, "BZ": {"path": "M198.03,239.7l0.28,0.19l0.43,-0.1l0.82,-1.42l0.0,0.07l0.29,0.29l0.16,0.0l-0.02,0.35l-0.39,1.08l0.02,0.25l0.16,0.29l-0.23,0.8l0.04,0.24l0.09,0.14l-0.25,1.12l-0.38,0.53l-0.33,0.06l-0.21,0.15l-0.41,0.74l-0.25,0.0l0.17,-2.58l0.01,-2.2Z", "name": "Belize"}, "RU": {"path": "M688.57,38.85l0.63,2.39l0.44,0.19l2.22,-1.23l7.18,0.07l5.54,2.49l1.85,1.77l-0.55,2.34l-2.64,1.42l-6.57,2.76l-1.95,1.5l0.12,0.53l3.09,0.68l3.69,1.23l0.21,-0.01l1.98,-0.81l1.16,2.84l0.5,0.08l1.03,-1.18l3.86,-0.74l7.79,0.78l0.56,2.05l0.27,0.22l10.47,0.71l0.32,-0.29l0.13,-3.34l4.98,0.8l3.96,-0.02l3.88,2.43l1.06,2.79l-1.38,1.83l0.01,0.38l3.15,3.64l0.1,0.08l3.94,1.86l0.4,-0.14l2.28,-4.56l3.75,1.94l0.22,0.02l4.18,-1.22l4.76,1.4l0.26,-0.04l1.74,-1.23l3.98,0.63l0.32,-0.41l-1.71,-4.1l3.0,-1.86l22.39,3.04l2.06,2.67l0.1,0.08l6.55,3.51l0.17,0.03l10.08,-0.86l4.86,0.73l1.91,1.72l-0.29,3.13l0.18,0.31l3.08,1.26l0.19,0.01l3.32,-0.9l4.37,-0.11l4.78,0.87l4.61,-0.48l4.26,3.82l0.32,0.05l3.1,-1.4l0.12,-0.45l-1.91,-2.67l0.92,-1.64l7.78,1.22l5.22,-0.26l7.12,2.1l9.6,5.22l6.4,4.15l-0.2,2.44l0.14,0.28l1.69,1.04l0.45,-0.31l-0.51,-2.66l6.31,0.58l4.52,3.61l-2.1,1.52l-4.02,0.42l-0.27,0.29l-0.06,3.83l-0.81,0.67l-2.14,-0.11l-1.91,-1.39l-3.19,-1.13l-0.51,-1.63l-0.21,-0.2l-2.54,-0.67l-0.13,-0.0l-2.69,0.5l-1.12,-1.19l0.48,-1.36l-0.38,-0.39l-3.0,0.98l-0.17,0.44l1.02,1.76l-1.27,1.55l-3.09,1.71l-3.15,-0.29l-0.3,0.18l0.07,0.34l2.22,2.1l1.47,3.22l1.15,1.09l0.25,1.41l-0.48,0.76l-4.47,-0.81l-0.17,0.02l-6.97,2.9l-2.2,0.44l-0.11,0.05l-3.83,2.68l-3.63,2.32l-0.1,0.11l-0.76,1.4l-3.3,-2.4l-0.3,-0.03l-6.31,2.85l-0.99,-1.21l-0.4,-0.06l-2.32,1.54l-3.23,-0.49l-0.33,0.2l-0.79,2.39l-2.97,3.51l-0.07,0.21l0.09,1.47l0.22,0.27l2.62,0.74l-0.3,4.7l-2.06,0.12l-0.26,0.2l-1.07,2.94l0.04,0.27l0.83,1.19l-4.03,1.63l-0.18,0.21l-0.83,3.72l-3.55,0.79l-0.23,0.23l-0.73,3.32l-3.22,2.76l-0.76,-1.88l-1.07,-4.88l-1.39,-7.59l1.17,-4.76l2.05,-2.08l0.09,-0.19l0.11,-1.46l3.67,-0.77l0.15,-0.08l4.47,-4.61l4.29,-3.82l4.48,-3.01l0.11,-0.14l2.01,-5.43l-0.31,-0.4l-3.04,0.33l-0.24,0.17l-1.47,3.11l-5.98,3.94l-1.91,-4.36l-0.33,-0.17l-6.46,1.3l-0.15,0.08l-6.27,6.33l-0.01,0.41l1.7,1.87l-5.04,0.87l-3.51,0.34l0.16,-2.32l-0.26,-0.32l-3.89,-0.56l-0.19,0.04l-3.02,1.77l-7.63,-0.63l-8.24,1.1l-0.16,0.07l-8.11,7.09l-9.6,8.31l0.16,0.52l3.79,0.42l1.16,2.03l0.17,0.14l2.43,0.76l0.31,-0.08l1.5,-1.61l2.49,0.2l3.46,3.6l0.08,2.67l-1.91,3.26l-0.04,0.14l-0.21,3.91l-1.11,5.09l-3.73,4.55l-0.87,2.21l-6.73,7.14l-1.59,1.77l-3.23,1.72l-1.38,0.03l-1.48,-1.39l-0.37,-0.03l-3.36,2.22l-0.11,0.14l-0.16,0.42l-0.01,-1.09l1.0,-0.06l0.28,-0.27l0.36,-3.6l-0.61,-2.51l1.85,-0.94l2.94,0.53l0.32,-0.15l1.71,-3.1l0.84,-3.38l0.97,-1.18l1.32,-2.88l-0.34,-0.42l-4.14,0.95l-2.18,1.25l-3.51,-0.0l-0.95,-2.81l-0.1,-0.14l-2.97,-2.3l-0.11,-0.05l-4.19,-1.0l-0.89,-3.08l-0.87,-2.03l-0.95,-1.46l-1.54,-3.37l-0.12,-0.14l-2.27,-1.28l-3.83,-1.02l-3.37,0.1l-3.11,0.61l-0.13,0.06l-2.07,1.69l0.04,0.49l1.23,0.72l0.03,1.53l-1.34,1.05l-2.26,3.51l-0.05,0.17l0.02,1.27l-3.25,1.9l-2.87,-1.17l-0.14,-0.02l-2.86,0.26l-1.22,-1.02l-0.12,-0.06l-1.5,-0.35l-0.23,0.04l-3.62,2.27l-3.24,0.53l-2.28,0.79l-3.08,-0.51l-2.24,0.03l-1.49,-1.61l-2.45,-1.57l-0.11,-0.04l-2.6,-0.43l-3.17,0.43l-2.31,0.59l-3.31,-1.28l-0.45,-2.31l-0.21,-0.23l-2.94,-0.85l-2.26,-0.39l-2.77,-1.36l-0.37,0.09l-2.59,3.45l-0.03,0.32l0.91,1.74l-2.15,2.01l-3.47,-0.79l-2.44,-0.12l-1.59,-1.46l-0.2,-0.08l-2.55,-0.05l-2.12,-0.98l-0.24,-0.01l-3.85,1.57l-4.74,2.79l-2.59,0.55l-0.79,0.21l-1.21,-1.81l-0.29,-0.13l-3.05,0.41l-0.96,-1.25l-0.14,-0.1l-1.65,-0.6l-1.15,-1.82l-0.13,-0.12l-1.38,-0.6l-0.19,-0.02l-3.49,0.82l-3.35,-1.85l-0.38,0.08l-1.08,1.4l-5.36,-8.17l-3.02,-2.52l0.72,-0.85l0.01,-0.38l-0.37,-0.08l-6.22,3.21l-1.98,0.16l0.17,-1.51l-0.2,-0.31l-3.22,-1.17l-0.19,-0.0l-2.3,0.74l-0.72,-3.27l-0.24,-0.23l-4.5,-0.75l-0.21,0.04l-2.2,1.42l-6.21,1.27l-0.11,0.05l-1.16,0.81l-9.3,1.19l-0.18,0.09l-1.15,1.17l-0.02,0.39l1.56,2.01l-2.02,0.74l-0.16,0.42l0.35,0.68l-2.18,1.49l0.02,0.51l3.83,2.16l-0.45,1.13l-3.31,-0.13l-0.25,0.12l-0.57,0.77l-2.97,-1.59l-0.15,-0.04l-3.97,0.07l-0.13,0.03l-2.53,1.32l-2.84,-1.28l-5.52,-2.3l-0.12,-0.02l-3.91,0.09l-0.16,0.05l-5.17,3.6l-0.13,0.21l-0.25,1.89l-2.17,-1.6l-0.44,0.1l-2.0,3.59l0.06,0.37l0.55,0.5l-1.32,2.23l0.04,0.36l2.13,2.17l0.23,0.09l1.7,-0.08l1.42,1.89l-0.23,1.5l0.19,0.32l0.94,0.38l-0.89,1.44l-2.3,0.49l-0.17,0.11l-2.49,3.2l0.0,0.37l2.2,2.81l-0.23,1.93l0.06,0.22l2.56,3.32l-1.27,1.02l-0.4,0.66l-0.8,-0.15l-1.65,-1.75l-0.18,-0.09l-0.66,-0.09l-1.45,-0.64l-0.72,-1.16l-0.18,-0.13l-2.34,-0.63l-0.17,0.0l-1.32,0.41l-0.31,-0.4l-0.12,-0.09l-3.49,-1.48l-3.67,-0.49l-2.1,-0.52l-0.3,0.1l-0.12,0.14l-2.96,-2.4l-2.89,-1.19l-1.69,-1.42l1.27,-0.35l0.16,-0.1l2.08,-2.61l-0.04,-0.41l-1.02,-0.9l3.21,-1.12l0.2,-0.31l-0.07,-0.69l-0.37,-0.26l-1.86,0.42l0.05,-0.86l1.11,-0.76l2.35,-0.23l0.25,-0.19l0.39,-1.07l0.0,-0.19l-0.51,-1.64l0.95,-1.58l0.04,-0.16l-0.03,-0.95l-0.22,-0.28l-3.69,-1.06l-1.43,0.02l-1.45,-1.44l-0.29,-0.08l-1.83,0.49l-2.88,-1.04l0.04,-0.42l-0.04,-0.18l-0.89,-1.43l-0.23,-0.14l-1.77,-0.14l-0.13,-0.66l0.52,-0.56l0.01,-0.4l-1.6,-1.9l-0.27,-0.1l-2.55,0.32l-0.71,-0.16l-0.3,0.1l-0.53,0.63l-0.58,-0.08l-0.56,-1.97l-0.48,-0.94l0.17,-0.11l1.92,0.11l0.2,-0.06l0.97,-0.74l0.05,-0.42l-0.72,-0.91l-0.13,-0.1l-1.43,-0.51l0.09,-0.36l-0.13,-0.33l-0.97,-0.59l-1.43,-2.06l0.44,-0.77l0.04,-0.19l-0.25,-1.64l-0.2,-0.24l-2.45,-0.84l-0.19,-0.0l-1.05,0.34l-0.25,-0.62l-0.18,-0.17l-2.5,-0.84l-0.74,-1.93l-0.21,-1.7l-0.13,-0.21l-0.92,-0.63l0.83,-0.89l0.07,-0.27l-0.71,-3.26l1.69,-2.01l0.03,-0.34l-0.24,-0.41l2.63,-1.9l-0.01,-0.49l-2.31,-1.57l5.08,-4.61l2.33,-2.24l1.01,-2.08l-0.09,-0.37l-3.52,-2.56l0.94,-2.38l-0.04,-0.29l-2.14,-2.86l1.61,-3.35l-0.01,-0.29l-2.81,-4.58l2.19,-3.04l-0.06,-0.42l-3.7,-2.76l0.32,-2.67l1.87,-0.38l4.26,-1.77l2.46,-1.47l3.96,2.58l0.12,0.05l6.81,1.04l9.37,4.87l1.81,1.92l0.15,2.55l-2.61,2.06l-3.95,1.07l-11.1,-3.15l-0.17,0.0l-1.84,0.53l-0.1,0.53l3.97,2.97l0.15,1.77l0.16,4.14l0.19,0.27l3.21,1.22l1.94,1.03l0.44,-0.22l0.32,-1.94l-0.07,-0.25l-1.32,-1.52l1.25,-1.2l5.87,2.45l0.24,-0.01l2.11,-0.98l0.13,-0.42l-1.55,-2.75l5.52,-3.84l2.13,0.22l2.28,1.42l0.43,-0.12l1.46,-2.87l-0.04,-0.33l-1.97,-2.37l1.14,-2.38l-0.02,-0.3l-1.42,-2.07l6.15,1.22l1.14,1.92l-2.74,0.46l-0.25,0.3l0.02,2.36l0.12,0.24l1.97,1.44l0.25,0.05l3.87,-0.91l0.22,-0.23l0.58,-2.55l5.09,-1.98l8.67,-3.69l1.22,0.14l-2.06,2.2l0.18,0.5l3.11,0.45l0.23,-0.07l1.71,-1.41l4.59,-0.12l0.12,-0.03l3.53,-1.72l2.7,2.48l0.42,-0.01l2.85,-2.88l-0.0,-0.43l-2.42,-2.35l1.0,-1.13l7.2,1.31l3.42,1.36l9.06,4.97l0.39,-0.08l1.67,-2.27l-0.04,-0.4l-2.46,-2.23l-0.06,-0.82l-0.26,-0.27l-2.64,-0.38l0.69,-1.76l0.0,-0.22l-1.32,-3.47l-0.07,-1.27l4.52,-4.09l0.08,-0.11l1.6,-4.18l1.67,-0.84l6.33,1.2l0.46,2.31l-2.31,3.67l0.05,0.38l1.49,1.41l0.77,3.04l-0.56,6.05l0.09,0.24l2.62,2.54l-0.99,2.65l-4.87,5.96l0.17,0.48l2.86,0.61l0.31,-0.13l0.94,-1.42l2.67,-1.04l0.18,-0.19l0.64,-2.01l2.11,-1.98l0.05,-0.37l-1.38,-2.32l1.11,-2.74l-0.24,-0.41l-2.53,-0.33l-0.53,-2.16l1.96,-4.42l-0.05,-0.32l-3.03,-3.48l4.21,-2.94l0.12,-0.3l-0.52,-3.04l0.72,-0.06l1.18,2.35l-0.97,4.39l0.2,0.35l2.68,0.84l0.37,-0.38l-1.05,-3.07l3.89,-1.71l5.05,-0.24l4.55,2.62l0.36,-0.05l0.05,-0.36l-2.19,-3.84l-0.23,-4.78l4.07,-0.92l5.98,0.21l5.47,-0.64l0.2,-0.48l-1.88,-2.37l2.65,-2.99l2.75,-0.13l0.12,-0.03l4.82,-2.48l6.56,-0.67l0.23,-0.14l0.76,-1.27l6.33,-0.46l1.97,1.11l0.28,0.01l5.55,-2.71l4.53,0.08l0.29,-0.21l0.67,-2.18l2.29,-2.15l5.75,-2.13l3.48,1.4l-2.7,1.03l-0.19,0.31l0.26,0.26l5.47,0.78ZM871.83,65.73l0.25,-0.15l1.99,0.01l3.3,1.2l-0.08,0.22l-2.41,1.03l-5.73,0.49l-0.31,-1.0l2.99,-1.8ZM797.64,48.44l-2.22,1.51l-3.85,-0.43l-4.35,-1.85l0.42,-1.13l4.42,0.72l5.59,1.17ZM783.82,46.06l-1.71,3.25l-9.05,-0.14l-4.11,1.15l-4.64,-3.04l1.21,-3.13l3.11,-0.91l6.53,0.22l8.66,2.59ZM780.37,145.71l2.28,5.23l-3.09,-0.89l-0.37,0.19l-1.54,4.65l0.04,0.27l2.38,3.17l-0.05,1.4l-1.41,-1.41l-0.46,0.04l-1.23,1.81l-0.33,-1.86l0.28,-3.1l-0.28,-3.41l0.58,-2.46l0.11,-4.39l-0.03,-0.13l-1.44,-3.2l0.21,-4.39l2.19,-1.49l0.09,-0.41l-0.81,-1.3l0.48,-0.21l0.56,1.94l0.86,3.23l-0.05,3.36l1.03,3.35ZM780.16,57.18l-3.4,0.03l-5.06,-0.53l1.97,-1.59l2.95,-0.42l3.35,1.75l0.18,0.77ZM683.84,31.18l-13.29,1.97l4.16,-6.56l1.88,-0.58l1.77,0.34l6.08,3.02l-0.6,1.8ZM670.94,28.02l-5.18,0.65l-6.89,-1.58l-4.03,-2.07l-1.88,-3.98l-0.18,-0.16l-2.8,-0.93l5.91,-3.62l5.25,-1.29l4.73,2.88l5.63,5.44l-0.57,4.66ZM564.37,68.98l-0.85,0.23l-7.93,-0.57l-0.6,-1.84l-0.21,-0.2l-4.34,-1.18l-0.3,-2.08l2.34,-0.92l0.19,-0.29l-0.08,-2.43l4.85,-4.0l-0.12,-0.52l-1.68,-0.43l5.47,-3.94l0.11,-0.33l-0.6,-2.02l5.36,-2.55l8.22,-3.27l8.29,-0.96l4.34,-1.94l4.67,-0.65l1.45,1.72l-1.43,1.37l-8.8,2.52l-7.65,2.42l-7.92,4.84l-3.73,4.75l-3.92,4.58l-0.07,0.23l0.51,3.88l0.11,0.2l4.32,3.39ZM548.86,18.57l-3.28,0.75l-2.25,0.44l-0.22,0.19l-0.3,0.81l-2.67,0.86l-2.27,-1.14l1.2,-1.51l-0.23,-0.49l-3.14,-0.1l2.48,-0.54l3.55,-0.07l0.44,1.36l0.49,0.12l1.4,-1.35l2.2,-0.9l3.13,1.08l-0.54,0.49ZM477.5,133.25l-4.21,0.05l-2.69,-0.34l0.39,-1.03l3.24,-1.06l2.51,0.58l0.85,0.43l-0.2,0.71l-0.0,0.15l0.12,0.52Z", "name": "Russia"}, "RW": {"path": "M497.03,288.12l0.78,1.11l-0.12,1.19l-0.49,0.21l-1.25,-0.15l-0.3,0.16l-0.67,1.24l-1.01,-0.13l0.16,-0.92l0.22,-0.12l0.15,-0.24l0.09,-1.37l0.49,-0.48l0.42,0.18l0.25,-0.01l1.26,-0.65Z", "name": "Rwanda"}, "RS": {"path": "M469.75,168.65l0.21,-0.21l0.36,-1.44l-0.08,-0.29l-1.06,-1.03l0.54,-1.16l-0.28,-0.43l-0.26,0.0l0.55,-0.67l-0.01,-0.39l-0.77,-0.86l-0.45,-0.89l1.56,-0.67l1.39,0.12l1.22,1.1l0.26,0.91l0.16,0.19l1.38,0.66l0.17,1.12l0.14,0.21l1.46,0.9l0.35,-0.03l0.62,-0.54l0.09,0.06l-0.28,0.25l-0.03,0.42l0.29,0.34l-0.44,0.5l-0.07,0.26l0.22,1.12l0.07,0.14l1.02,1.1l-0.81,0.84l-0.42,0.96l0.04,0.3l0.12,0.15l-0.15,0.16l-1.04,0.04l-0.39,0.08l0.33,-0.81l-0.29,-0.41l-0.21,0.01l-0.39,-0.45l-0.13,-0.09l-0.32,-0.11l-0.27,-0.4l-0.14,-0.11l-0.4,-0.16l-0.31,-0.37l-0.34,-0.09l-0.45,0.17l-0.18,0.18l-0.29,0.84l-0.96,-0.65l-0.81,-0.33l-0.32,-0.37l-0.22,-0.18Z", "name": "Republic of Serbia"}, "LT": {"path": "M478.13,133.31l-0.14,-0.63l0.25,-0.88l-0.15,-0.35l-1.17,-0.58l-2.43,-0.57l-0.45,-2.51l2.58,-0.97l4.14,0.22l2.3,-0.32l0.26,0.54l0.22,0.17l1.26,0.22l2.25,1.6l0.19,1.23l-1.87,1.01l-0.14,0.18l-0.54,1.83l-2.54,1.21l-2.18,-0.02l-0.52,-0.91l-0.18,-0.14l-1.11,-0.32Z", "name": "Lithuania"}, "LU": {"path": "M435.95,147.99l0.33,0.49l-0.11,1.07l-0.39,0.04l-0.29,-0.15l0.21,-1.4l0.25,-0.05Z", "name": "Luxembourg"}, "LR": {"path": "M401.37,273.67l-0.32,0.01l-2.48,-1.15l-2.24,-1.89l-2.14,-1.38l-1.47,-1.42l0.44,-0.59l0.05,-0.13l0.12,-0.65l1.07,-1.3l1.08,-1.09l0.52,-0.07l0.43,-0.18l0.84,1.24l-0.15,0.89l0.07,0.25l0.49,0.54l0.22,0.1l0.71,0.01l0.27,-0.16l0.42,-0.83l0.19,0.02l-0.06,0.52l0.23,1.12l-0.5,1.03l0.06,0.35l0.73,0.69l0.14,0.08l0.71,0.15l0.92,0.91l0.06,0.76l-0.17,0.22l-0.06,0.15l-0.17,1.8Z", "name": "Liberia"}, "RO": {"path": "M477.94,155.19l1.02,-0.64l1.49,0.33l1.52,0.01l1.09,0.73l0.32,0.01l0.81,-0.46l1.8,-0.3l0.18,-0.1l0.54,-0.64l0.86,0.0l0.64,0.26l0.71,0.87l0.8,1.35l1.39,1.81l0.07,1.25l-0.26,1.3l0.01,0.15l0.45,1.42l0.15,0.18l1.12,0.57l0.25,0.01l1.05,-0.45l0.86,0.4l0.03,0.43l-0.92,0.51l-0.63,-0.24l-0.4,0.22l-0.64,3.41l-1.12,-0.24l-1.78,-1.09l-0.23,-0.04l-2.95,0.71l-1.25,0.77l-3.55,-0.16l-1.89,-0.47l-0.14,-0.0l-0.75,0.17l-0.61,-1.07l-0.3,-0.36l0.36,-0.32l-0.04,-0.48l-0.62,-0.38l-0.36,0.03l-0.62,0.54l-1.15,-0.71l-0.18,-1.14l-0.17,-0.22l-1.4,-0.67l-0.24,-0.86l-0.09,-0.14l-0.96,-0.87l1.49,-0.44l0.16,-0.11l1.51,-2.14l1.15,-2.09l1.44,-0.63Z", "name": "Romania"}, "GW": {"path": "M383.03,256.73l-1.12,-0.88l-0.14,-0.06l-0.94,-0.15l-0.43,-0.54l0.01,-0.27l-0.13,-0.26l-0.68,-0.48l-0.05,-0.16l0.99,-0.31l0.77,0.08l0.15,-0.02l0.61,-0.26l4.25,0.1l-0.02,0.44l-0.19,0.18l-0.08,0.29l0.17,0.66l-0.17,0.14l-0.44,0.0l-0.16,0.05l-0.57,0.37l-0.66,-0.04l-0.24,0.1l-0.92,1.03Z", "name": "Guinea Bissau"}, "GT": {"path": "M195.13,249.89l-1.05,-0.35l-1.5,-0.04l-1.06,-0.47l-1.19,-0.93l0.04,-0.53l0.27,-0.55l-0.03,-0.31l-0.24,-0.32l1.02,-1.77l3.04,-0.01l0.3,-0.28l0.06,-0.88l-0.19,-0.3l-0.3,-0.11l-0.23,-0.45l-0.11,-0.12l-0.9,-0.58l-0.35,-0.33l0.37,-0.0l0.3,-0.3l0.0,-1.15l4.05,0.02l-0.02,1.74l-0.2,2.89l0.3,0.32l0.67,-0.0l0.75,0.42l0.4,-0.11l-0.62,0.53l-1.17,0.7l-0.13,0.16l-0.18,0.49l0.0,0.21l0.14,0.34l-0.35,0.44l-0.49,0.13l-0.2,0.41l0.03,0.06l-0.27,0.16l-0.86,0.64l-0.12,0.22ZM199.35,245.38l0.07,-0.13l0.05,0.02l-0.13,0.11Z", "name": "Guatemala"}, "GR": {"path": "M487.2,174.55l-0.64,1.54l-0.43,0.24l-1.41,-0.08l-1.28,-0.28l-0.14,0.0l-3.03,0.77l-0.13,0.51l1.39,1.34l-0.78,0.29l-1.2,0.0l-1.23,-1.42l-0.47,0.02l-0.47,0.65l-0.04,0.27l0.56,1.76l0.06,0.11l1.02,1.12l-0.66,0.45l-0.04,0.46l1.39,1.35l1.15,0.79l0.02,1.06l-1.91,-0.63l-0.36,0.42l0.56,1.12l-1.2,0.23l-0.22,0.4l0.8,2.14l-1.15,0.02l-1.89,-1.15l-0.89,-2.19l-0.43,-1.91l-0.05,-0.11l-0.98,-1.35l-1.24,-1.62l-0.13,-0.63l1.07,-1.32l0.06,-0.14l0.13,-0.81l0.68,-0.36l0.16,-0.25l0.03,-0.54l1.4,-0.23l0.12,-0.05l0.87,-0.6l1.26,0.05l0.25,-0.11l0.34,-0.43l0.33,-0.07l1.81,0.08l0.13,-0.02l1.87,-0.77l1.64,0.97l0.19,0.04l2.28,-0.28l0.26,-0.29l0.02,-0.95l0.56,0.36ZM480.44,192.0l1.05,0.74l0.01,0.0l-1.26,-0.23l0.2,-0.51ZM481.76,192.79l1.86,-0.15l1.53,0.17l-0.02,0.19l0.34,0.3l-2.28,0.15l0.01,-0.13l-0.25,-0.31l-1.19,-0.22ZM485.65,193.28l0.65,-0.16l-0.05,0.12l-0.6,0.04Z", "name": "Greece"}, "GQ": {"path": "M444.81,282.04l-0.21,-0.17l0.74,-2.4l3.56,0.05l0.02,2.42l-3.34,-0.02l-0.76,0.13Z", "name": "Equatorial Guinea"}, "GY": {"path": "M271.34,264.25l1.43,0.81l1.44,1.53l0.06,1.19l0.28,0.28l0.84,0.05l2.13,1.92l-0.34,1.93l-1.37,0.59l-0.17,0.34l0.12,0.51l-0.43,1.21l0.03,0.26l1.11,1.82l0.26,0.14l0.56,0.0l0.32,1.29l1.25,1.78l-0.08,0.01l-1.34,-0.21l-0.24,0.06l-0.78,0.64l-1.06,0.41l-0.76,0.1l-0.22,0.15l-0.18,0.32l-0.95,-0.1l-1.38,-1.05l-0.19,-1.13l-0.6,-1.18l0.37,-1.96l0.65,-0.83l0.03,-0.32l-0.57,-1.17l-0.15,-0.14l-0.62,-0.27l0.25,-0.85l-0.08,-0.3l-0.58,-0.58l-0.24,-0.09l-1.15,0.1l-1.41,-1.58l0.48,-0.49l0.09,-0.22l-0.04,-0.92l1.31,-0.34l0.73,-0.52l0.04,-0.44l-0.75,-0.82l0.16,-0.66l1.74,-1.3Z", "name": "Guyana"}, "GE": {"path": "M525.41,174.19l0.26,-0.88l-0.0,-0.17l-0.63,-2.06l-0.1,-0.15l-1.45,-1.12l-0.11,-0.05l-1.31,-0.33l-0.66,-0.69l1.97,0.48l3.65,0.49l3.3,1.41l0.39,0.5l0.33,0.1l1.43,-0.45l2.14,0.58l0.7,1.14l0.13,0.12l1.06,0.47l-0.18,0.11l-0.08,0.43l1.08,1.41l-0.06,0.06l-1.16,-0.15l-1.82,-0.84l-0.31,0.04l-0.55,0.44l-3.29,0.44l-2.32,-1.41l-0.17,-0.04l-2.25,0.12Z", "name": "Georgia"}, "GB": {"path": "M412.82,118.6l-2.31,3.4l-0.0,0.33l0.31,0.13l2.52,-0.49l2.34,0.02l-0.56,2.51l-2.22,3.13l0.22,0.47l2.43,0.21l2.35,4.35l0.17,0.14l1.58,0.51l1.49,3.78l0.73,1.37l0.2,0.15l2.76,0.59l-0.25,1.75l-1.18,0.91l-0.08,0.39l0.87,1.49l-1.96,1.51l-3.31,-0.02l-4.15,0.88l-1.07,-0.59l-0.35,0.04l-1.55,1.44l-2.17,-0.35l-0.22,0.05l-1.61,1.15l-0.78,-0.38l3.31,-3.12l2.18,-0.7l0.21,-0.31l-0.26,-0.27l-3.78,-0.54l-0.48,-0.9l2.3,-0.92l0.13,-0.46l-1.29,-1.71l0.39,-1.83l3.46,0.29l0.32,-0.24l0.37,-1.99l-0.06,-0.24l-1.71,-2.17l-0.18,-0.11l-2.91,-0.58l-0.43,-0.68l0.82,-1.4l-0.03,-0.35l-0.82,-0.97l-0.46,0.01l-0.85,1.05l-0.11,-2.6l-0.05,-0.16l-1.19,-1.7l0.86,-3.53l1.81,-2.75l1.88,0.26l2.38,-0.24ZM406.39,132.84l-1.09,1.92l-1.65,-0.62l-1.26,0.02l0.41,-1.46l0.0,-0.16l-0.42,-1.51l1.62,-0.11l2.39,1.92Z", "name": "United Kingdom"}, "GA": {"path": "M448.76,294.47l-2.38,-2.34l-1.63,-2.04l-1.46,-2.48l0.06,-0.66l0.54,-0.81l0.61,-1.82l0.46,-1.69l0.63,-0.11l3.62,0.03l0.3,-0.3l-0.02,-2.75l0.88,-0.12l1.47,0.32l0.13,0.0l1.39,-0.3l-0.13,0.87l0.03,0.19l0.7,1.29l0.3,0.16l1.74,-0.19l0.36,0.29l-1.01,2.7l0.05,0.29l1.13,1.42l0.25,1.82l-0.3,1.56l-0.64,0.99l-1.93,-0.09l-1.26,-1.13l-0.5,0.17l-0.16,0.91l-1.48,0.27l-0.12,0.05l-0.86,0.63l-0.08,0.39l0.81,1.42l-1.48,1.08Z", "name": "Gabon"}, "GN": {"path": "M399.83,265.31l-0.69,-0.06l-0.3,0.16l-0.43,0.85l-0.39,-0.01l-0.3,-0.33l0.14,-0.87l-0.05,-0.22l-1.05,-1.54l-0.37,-0.11l-0.61,0.27l-0.84,0.12l0.02,-0.54l-0.04,-0.17l-0.35,-0.57l0.07,-0.63l-0.03,-0.17l-0.57,-1.11l-0.7,-0.9l-0.24,-0.12l-2.0,-0.0l-0.19,0.07l-0.51,0.42l-0.6,0.05l-0.21,0.11l-0.43,0.55l-0.3,0.7l-1.04,0.86l-0.91,-1.24l-1.0,-1.02l-0.69,-0.37l-0.52,-0.42l-0.3,-1.11l-0.37,-0.56l-0.1,-0.1l-0.4,-0.23l0.77,-0.85l0.62,0.04l0.18,-0.05l0.58,-0.38l0.46,-0.0l0.19,-0.07l0.39,-0.34l0.1,-0.3l-0.17,-0.67l0.15,-0.14l0.09,-0.2l0.03,-0.57l0.87,0.02l1.76,0.6l0.13,0.01l0.55,-0.06l0.22,-0.13l0.08,-0.12l1.18,0.17l0.17,-0.02l0.09,0.56l0.3,0.25l0.4,-0.0l0.14,-0.03l0.56,-0.29l0.23,0.05l0.63,0.59l0.15,0.07l1.07,0.2l0.24,-0.06l0.65,-0.52l0.77,-0.32l0.55,-0.32l0.3,0.04l0.44,0.45l0.34,0.74l0.84,0.87l-0.35,0.45l-0.06,0.15l-0.1,0.82l0.42,0.31l0.35,-0.16l0.05,0.04l-0.1,0.59l0.09,0.27l0.42,0.4l-0.06,0.02l-0.18,0.21l-0.2,0.86l0.03,0.21l0.56,1.02l0.52,1.71l-0.65,0.21l-0.15,0.12l-0.24,0.35l-0.03,0.28l0.16,0.41l-0.1,0.76l-0.12,0.0Z", "name": "Guinea"}, "GM": {"path": "M379.18,251.48l0.15,-0.55l2.51,-0.07l0.21,-0.09l0.48,-0.52l0.58,-0.03l0.91,0.58l0.16,0.05l0.78,0.01l0.14,-0.03l0.59,-0.31l0.16,0.24l-0.71,0.38l-0.94,-0.04l-1.02,-0.51l-0.3,0.01l-0.86,0.55l-0.37,0.02l-0.14,0.04l-0.53,0.31l-1.81,-0.04Z", "name": "Gambia"}, "GL": {"path": "M304.13,6.6l8.19,-3.63l8.72,0.28l0.19,-0.06l3.12,-2.28l8.75,-0.61l19.94,0.8l14.93,4.75l-3.92,2.01l-9.52,0.27l-13.48,0.6l-0.27,0.2l0.09,0.33l1.26,1.09l0.22,0.07l8.81,-0.67l7.49,2.07l0.19,-0.01l4.68,-1.78l1.76,1.84l-2.59,3.26l-0.01,0.36l0.34,0.11l6.35,-2.2l12.09,-2.32l7.31,1.14l1.17,2.13l-9.9,4.05l-1.43,1.32l-7.91,0.98l-0.26,0.31l0.29,0.29l5.25,0.25l-2.63,3.72l-2.02,3.61l-0.04,0.15l0.08,6.05l0.07,0.19l2.61,3.0l-3.4,0.2l-4.12,1.66l-0.04,0.54l4.5,2.67l0.53,3.9l-2.39,0.42l-0.19,0.48l2.91,3.83l-5.0,0.32l-0.27,0.22l0.12,0.33l2.69,1.84l-0.65,1.35l-3.36,0.71l-3.46,0.01l-0.21,0.51l3.05,3.15l0.02,1.53l-4.54,-1.79l-0.32,0.06l-1.29,1.26l0.11,0.5l3.33,1.15l3.17,2.74l0.85,3.29l-4.0,0.78l-1.83,-1.66l-3.1,-2.64l-0.36,-0.02l-0.13,0.33l0.8,2.92l-2.76,2.26l-0.09,0.33l0.28,0.2l6.59,0.19l2.47,0.18l-5.86,3.38l-6.76,3.43l-7.26,1.48l-2.73,0.02l-0.16,0.05l-2.67,1.72l-3.44,4.42l-5.28,2.86l-1.73,0.18l-3.33,1.01l-3.59,0.96l-0.15,0.1l-2.15,2.52l-0.07,0.19l-0.03,2.76l-1.21,2.49l-4.03,3.1l-0.1,0.33l0.98,2.94l-2.31,6.57l-3.21,0.21l-3.6,-3.0l-0.19,-0.07l-4.9,-0.02l-2.29,-1.97l-1.69,-3.78l-4.31,-4.86l-1.23,-2.52l-0.34,-3.58l-0.08,-0.17l-3.35,-3.67l0.85,-2.92l-0.09,-0.31l-1.5,-1.34l2.33,-4.7l3.67,-1.57l0.15,-0.13l1.02,-1.93l0.52,-3.47l-0.44,-0.31l-2.85,1.57l-1.33,0.64l-2.12,0.59l-2.81,-1.32l-0.15,-2.79l0.88,-2.17l2.09,-0.06l5.07,1.2l0.34,-0.17l-0.11,-0.37l-4.3,-2.9l-2.24,-1.58l-0.25,-0.05l-2.38,0.62l-1.7,-0.93l2.62,-4.1l-0.03,-0.36l-1.51,-1.75l-1.97,-3.3l-3.01,-5.21l-0.1,-0.11l-3.04,-1.85l0.03,-1.94l-0.18,-0.28l-6.82,-3.01l-5.35,-0.38l-6.69,0.21l-6.03,0.37l-2.81,-1.59l-3.84,-2.9l5.94,-1.5l5.01,-0.28l0.28,-0.29l-0.26,-0.31l-10.68,-1.38l-5.38,-2.1l0.27,-1.68l9.3,-2.6l9.18,-2.68l0.19,-0.16l0.97,-2.05l-0.18,-0.42l-6.29,-1.91l1.81,-1.9l8.58,-4.05l3.6,-0.63l0.23,-0.4l-0.92,-2.37l5.59,-1.5l7.66,-0.95l7.58,-0.05l2.65,1.84l0.31,0.02l6.52,-3.29l5.85,2.24l3.55,0.49l5.17,1.95l0.38,-0.16l-0.13,-0.39l-5.77,-3.16l0.29,-2.26Z", "name": "Greenland"}, "KW": {"path": "M540.87,207.81l0.41,0.94l-0.18,0.51l0.0,0.21l0.65,1.66l-1.15,0.05l-0.54,-1.12l-0.24,-0.17l-1.73,-0.2l1.44,-2.06l1.33,0.18Z", "name": "Kuwait"}, "GH": {"path": "M423.16,269.88l-3.58,1.34l-1.41,0.87l-2.13,0.69l-1.91,-0.61l0.09,-0.75l-0.03,-0.17l-1.04,-2.07l0.62,-2.7l1.04,-2.08l0.03,-0.19l-1.0,-5.46l0.05,-1.12l4.04,-0.11l1.08,0.18l0.18,-0.03l0.72,-0.36l0.75,0.13l-0.11,0.48l0.06,0.26l0.98,1.22l-0.0,1.77l0.24,1.99l0.05,0.13l0.55,0.81l-0.52,2.14l0.19,1.37l0.69,1.66l0.38,0.62Z", "name": "Ghana"}, "OM": {"path": "M568.16,231.0l-0.08,0.1l-0.84,1.61l-0.93,-0.11l-0.27,0.11l-0.58,0.73l-0.4,1.32l-0.01,0.14l0.29,1.61l-0.07,0.09l-1.0,-0.01l-0.16,0.04l-1.56,0.97l-0.14,0.2l-0.23,1.17l-0.41,0.4l-1.44,-0.02l-0.17,0.05l-0.98,0.65l-0.13,0.25l0.01,0.87l-0.97,0.57l-1.27,-0.22l-0.19,0.03l-1.63,0.84l-0.88,0.11l-2.55,-5.57l7.2,-2.49l0.19,-0.19l1.67,-5.23l-0.03,-0.25l-1.1,-1.78l0.05,-0.89l0.68,-1.03l0.05,-0.16l0.01,-0.89l0.96,-0.44l0.07,-0.5l-0.32,-0.26l0.16,-1.31l0.85,-0.01l1.03,1.67l0.09,0.09l1.4,0.96l0.11,0.05l1.82,0.34l1.37,0.45l1.75,2.32l0.13,0.1l0.7,0.26l-0.0,0.3l-1.25,2.19l-1.01,0.8ZM561.88,218.47l-0.01,0.02l-0.15,-0.29l0.3,-0.38l-0.14,0.65Z", "name": "Oman"}, "_3": {"path": "M543.2,261.06l-1.07,1.46l-1.65,1.99l-1.91,0.01l-8.08,-2.95l-0.89,-0.84l-0.9,-1.19l-0.81,-1.23l0.44,-0.73l0.76,-1.12l0.49,0.28l0.52,1.05l1.13,1.06l0.2,0.08l1.24,0.01l2.42,-0.65l2.77,-0.31l2.17,-0.78l1.31,-0.19l0.84,-0.43l1.03,-0.06l-0.01,4.54Z", "name": "Somaliland"}, "_2": {"path": "M384.23,230.37l0.07,-0.06l0.28,-0.89l0.99,-1.13l0.07,-0.13l0.8,-3.54l3.4,-2.8l0.09,-0.13l0.76,-2.17l0.07,5.5l-2.07,0.21l-0.24,0.17l-0.61,1.36l-0.02,0.16l0.43,3.46l-4.01,-0.01ZM391.82,218.2l0.07,-0.06l0.75,-1.93l1.86,-0.25l0.94,0.34l1.14,0.0l0.18,-0.06l0.73,-0.56l1.41,-0.08l-0.0,2.72l-7.08,-0.12Z", "name": "Western Sahara"}, "_1": {"path": "M472.71,172.84l-0.07,-0.43l-0.16,-0.22l-0.53,-0.27l-0.38,-0.58l0.3,-0.43l0.51,-0.19l0.18,-0.18l0.3,-0.87l0.12,-0.04l0.22,0.26l0.12,0.09l0.38,0.15l0.28,0.41l0.15,0.12l0.34,0.12l0.43,0.5l0.15,0.07l-0.12,0.3l-0.27,0.32l-0.03,0.18l-0.31,0.06l-1.48,0.47l-0.15,0.17Z", "name": "Kosovo"}, "_0": {"path": "M503.54,192.92l0.09,-0.17l0.41,0.01l-0.08,0.01l-0.42,0.15ZM504.23,192.76l1.02,0.02l0.4,-0.13l-0.09,0.29l0.03,0.08l-0.35,0.16l-0.24,-0.04l-0.06,-0.1l-0.18,-0.17l-0.19,-0.08l-0.33,-0.02Z", "name": "Northern Cyprus"}, "JO": {"path": "M510.26,200.93l0.28,-0.57l2.53,1.0l0.27,-0.02l4.57,-2.77l0.84,2.84l-0.28,0.25l-4.95,1.37l-0.14,0.49l2.24,2.48l-0.5,0.28l-0.13,0.14l-0.35,0.78l-1.76,0.35l-0.2,0.14l-0.57,0.94l-0.94,0.73l-2.45,-0.38l-0.03,-0.12l1.23,-4.32l-0.04,-1.1l0.34,-0.75l0.03,-0.12l0.0,-1.63Z", "name": "Jordan"}, "HR": {"path": "M455.49,162.73l1.53,0.09l0.24,-0.1l0.29,-0.34l0.64,0.38l0.14,0.04l0.98,0.06l0.32,-0.3l-0.01,-0.66l0.67,-0.25l0.19,-0.22l0.21,-1.11l1.72,-0.72l0.65,0.32l1.94,1.37l2.07,0.6l0.22,-0.02l0.67,-0.33l0.47,0.94l0.67,0.76l-0.63,0.77l-0.91,-0.55l-0.16,-0.04l-1.69,0.04l-2.2,-0.51l-1.17,0.07l-0.21,0.11l-0.36,0.42l-0.67,-0.53l-0.46,0.12l-0.52,1.29l0.05,0.31l1.21,1.42l0.58,0.99l1.15,1.14l0.95,0.68l0.92,1.23l0.1,0.09l1.75,0.91l-1.87,-0.89l-1.5,-1.11l-2.23,-0.88l-1.77,-1.9l0.12,-0.06l0.1,-0.47l-1.07,-1.22l-0.04,-0.94l-0.21,-0.27l-1.61,-0.49l-0.35,0.14l-0.53,0.93l-0.41,-0.57l0.04,-0.73Z", "name": "Croatia"}, "HT": {"path": "M237.82,234.68l1.35,0.1l1.95,0.37l0.18,1.15l-0.16,0.83l-0.51,0.37l-0.06,0.44l0.57,0.68l-0.02,0.22l-1.31,-0.35l-1.26,0.17l-1.49,-0.18l-0.15,0.02l-1.03,0.43l-1.02,-0.61l0.09,-0.36l2.04,0.32l1.9,0.21l0.19,-0.05l0.9,-0.58l0.05,-0.47l-1.05,-1.03l0.02,-0.86l-0.23,-0.3l-1.13,-0.29l0.18,-0.23Z", "name": "Haiti"}, "HU": {"path": "M461.96,157.92l0.68,-1.66l-0.03,-0.29l-0.15,-0.22l0.84,-0.0l0.3,-0.26l0.12,-0.84l0.88,0.57l0.98,0.38l0.16,0.01l2.1,-0.39l0.23,-0.21l0.14,-0.45l0.88,-0.1l1.06,-0.43l0.13,0.1l0.28,0.04l1.18,-0.4l0.14,-0.1l0.52,-0.67l0.63,-0.15l2.6,0.95l0.26,-0.03l0.38,-0.23l1.12,0.7l0.1,0.49l-1.31,0.57l-0.14,0.13l-1.18,2.14l-1.44,2.04l-1.85,0.55l-1.51,-0.13l-0.14,0.02l-1.92,0.82l-0.85,0.42l-1.91,-0.55l-1.83,-1.31l-0.74,-0.37l-0.44,-0.97l-0.26,-0.18Z", "name": "Hungary"}, "HN": {"path": "M202.48,251.87l-0.33,-0.62l-0.18,-0.14l-0.5,-0.15l0.13,-0.76l-0.11,-0.28l-0.34,-0.28l-0.6,-0.23l-0.18,-0.01l-0.81,0.22l-0.16,-0.24l-0.72,-0.39l-0.51,-0.48l-0.12,-0.07l-0.31,-0.09l0.24,-0.3l0.04,-0.3l-0.16,-0.4l0.1,-0.28l1.14,-0.69l1.0,-0.86l0.09,0.04l0.3,-0.05l0.47,-0.39l0.49,-0.03l0.14,0.13l0.29,0.06l0.31,-0.1l1.16,0.22l1.24,-0.08l0.81,-0.28l0.29,-0.25l0.63,0.1l0.69,0.18l0.65,-0.06l0.49,-0.2l1.04,0.32l0.38,0.06l0.7,0.44l0.71,0.56l0.92,0.41l0.1,0.11l-0.11,-0.01l-0.23,0.09l-0.3,0.3l-0.76,0.29l-0.58,0.0l-0.15,0.04l-0.45,0.26l-0.31,-0.07l-0.37,-0.34l-0.28,-0.07l-0.26,0.07l-0.18,0.15l-0.23,0.43l-0.04,-0.0l-0.33,0.28l-0.03,0.4l-0.76,0.61l-0.45,0.3l-0.15,0.16l-0.51,-0.36l-0.41,0.06l-0.45,0.56l-0.41,-0.01l-0.59,0.06l-0.27,0.31l0.04,0.96l-0.07,0.0l-0.25,0.16l-0.24,0.45l-0.42,0.06Z", "name": "Honduras"}, "PR": {"path": "M254.95,238.31l1.15,0.21l0.2,0.23l-0.36,0.36l-1.76,-0.01l-1.2,0.07l-0.09,-0.69l0.17,-0.18l1.89,0.01Z", "name": "Puerto Rico"}, "PS": {"path": "M509.66,201.06l-0.0,1.44l-0.29,0.63l-0.59,0.19l0.02,-0.11l0.52,-0.31l-0.02,-0.53l-0.41,-0.2l0.36,-1.28l0.41,0.17Z", "name": "West Bank"}, "PT": {"path": "M398.65,173.6l0.75,-0.63l0.7,-0.3l0.51,1.2l0.28,0.18l1.48,-0.0l0.2,-0.08l0.33,-0.3l1.16,0.08l0.52,1.11l-0.95,0.66l-0.13,0.24l-0.03,2.2l-0.33,0.35l-0.08,0.18l-0.08,1.17l-0.86,0.19l-0.2,0.44l0.93,1.64l-0.64,1.79l0.07,0.31l0.72,0.72l-0.24,0.56l-0.9,1.05l-0.07,0.26l0.17,0.77l-0.73,0.54l-1.18,-0.36l-0.16,-0.0l-0.85,0.21l0.31,-1.81l-0.23,-1.87l-0.23,-0.25l-0.99,-0.24l-0.49,-0.91l0.18,-1.72l0.93,-0.99l0.08,-0.16l0.17,-1.17l0.52,-1.76l-0.04,-1.36l-0.51,-1.14l-0.09,-0.8Z", "name": "Portugal"}, "PY": {"path": "M264.33,341.43l0.93,-2.96l0.07,-1.42l1.1,-2.1l4.19,-0.73l2.22,0.04l2.12,1.21l0.07,0.76l0.7,1.38l-0.16,3.48l0.24,0.31l2.64,0.5l0.19,-0.03l0.9,-0.45l1.47,0.62l0.38,0.64l0.23,2.35l0.3,1.07l0.25,0.21l0.93,0.12l0.16,-0.02l0.8,-0.37l0.61,0.33l-0.0,1.25l-0.33,1.53l-0.5,1.57l-0.39,2.26l-2.14,1.94l-1.85,0.4l-2.74,-0.4l-2.13,-0.62l2.26,-3.75l0.03,-0.24l-0.36,-1.18l-0.17,-0.19l-2.55,-1.03l-3.04,-1.95l-2.07,-0.43l-4.4,-4.12Z", "name": "Paraguay"}, "PA": {"path": "M213.65,263.79l0.18,-0.43l0.02,-0.18l-0.06,-0.28l0.23,-0.18l-0.01,-0.48l-0.4,-0.29l-0.01,-0.62l0.57,-0.13l0.68,0.69l-0.04,0.39l0.26,0.33l1.0,0.11l0.27,-0.1l0.49,0.44l0.24,0.07l1.34,-0.22l1.04,-0.62l1.49,-0.5l0.86,-0.73l0.99,0.11l0.18,0.28l1.35,0.08l1.02,0.4l0.78,0.72l0.71,0.53l-0.1,0.12l-0.05,0.3l0.53,1.34l-0.28,0.44l-0.6,-0.13l-0.36,0.22l-0.2,0.76l-0.41,-0.36l-0.44,-1.12l0.49,-0.53l-0.14,-0.49l-0.51,-0.14l-0.41,-0.72l-0.11,-0.11l-1.25,-0.7l-0.19,-0.04l-1.1,0.16l-0.22,0.15l-0.47,0.81l-0.9,0.56l-0.49,0.08l-0.22,0.17l-0.25,0.52l0.05,0.32l0.93,1.07l-0.41,0.21l-0.29,0.3l-0.81,0.09l-0.36,-1.26l-0.53,-0.1l-0.21,0.28l-0.5,-0.09l-0.44,-0.88l-0.22,-0.16l-0.99,-0.16l-0.61,-0.28l-0.13,-0.03l-1.0,0.0Z", "name": "Panama"}, "PG": {"path": "M808.4,298.6l0.62,0.46l1.19,1.56l1.04,0.77l-0.18,0.37l-0.42,0.15l-0.92,-0.82l-1.05,-1.53l-0.27,-0.96ZM804.09,296.06l-0.3,0.26l-0.36,-1.11l-0.66,-1.06l-2.55,-1.89l-1.42,-0.59l0.17,-0.15l1.16,0.6l0.85,0.55l1.01,0.58l0.97,1.02l0.9,0.76l0.24,1.03ZM796.71,297.99l0.15,0.82l0.34,0.24l1.43,-0.19l0.19,-0.11l0.68,-0.82l1.36,-0.87l0.13,-0.31l-0.21,-1.13l1.04,-0.03l0.3,0.25l-0.04,1.17l-0.74,1.34l-1.17,0.18l-0.22,0.15l-0.35,0.62l-2.51,1.13l-1.21,-0.0l-1.99,-0.71l-1.19,-0.58l0.07,-0.28l1.98,0.32l1.46,-0.2l0.24,-0.21l0.25,-0.79ZM789.24,303.52l0.11,0.15l2.19,1.62l1.6,2.62l0.27,0.14l1.09,-0.06l-0.07,0.77l0.23,0.32l1.23,0.27l-0.14,0.09l0.05,0.53l2.39,0.95l-0.11,0.28l-1.33,0.14l-0.51,-0.55l-0.18,-0.09l-4.59,-0.65l-1.87,-1.55l-1.38,-1.35l-1.28,-2.17l-0.16,-0.13l-3.27,-1.1l-0.19,0.0l-2.12,0.72l-1.58,0.85l-0.15,0.31l0.28,1.63l-1.65,0.73l-1.37,-0.4l-2.3,-0.09l-0.08,-15.65l3.95,1.57l4.58,1.42l1.67,1.25l1.32,1.19l0.36,1.39l0.19,0.21l4.06,1.51l0.39,0.85l-1.9,0.22l-0.25,0.39l0.55,1.68Z", "name": "Papua New Guinea"}, "PE": {"path": "M246.44,329.21l-0.63,1.25l-1.05,0.54l-2.25,-1.33l-0.19,-0.93l-0.16,-0.21l-4.95,-2.58l-4.46,-2.79l-1.87,-1.52l-0.94,-1.91l0.33,-0.6l-0.01,-0.31l-2.11,-3.33l-2.46,-4.66l-2.36,-5.02l-1.04,-1.18l-0.77,-1.81l-0.08,-0.11l-1.95,-1.64l-1.54,-0.88l0.61,-0.85l0.02,-0.31l-1.15,-2.27l0.69,-1.56l1.59,-1.26l0.12,0.42l-0.56,0.47l-0.11,0.25l0.07,0.92l0.36,0.27l0.97,-0.19l0.85,0.23l0.99,1.19l0.41,0.05l1.42,-1.03l0.11,-0.16l0.46,-1.64l1.45,-2.06l2.92,-0.96l0.11,-0.07l2.73,-2.62l0.84,-1.72l0.02,-0.18l-0.3,-1.65l0.28,-0.1l1.49,1.06l0.77,1.14l0.1,0.09l1.08,0.6l1.43,2.55l0.21,0.15l1.86,0.31l0.18,-0.03l1.25,-0.6l0.77,0.37l0.17,0.03l1.4,-0.2l1.57,0.96l-1.45,2.29l0.23,0.46l0.63,0.05l0.66,0.7l-1.51,-0.08l-0.24,0.1l-0.27,0.31l-1.96,0.46l-2.95,1.74l-0.14,0.21l-0.17,1.1l-0.6,0.82l-0.05,0.23l0.21,1.13l-1.31,0.63l-0.17,0.27l0.0,0.91l-0.53,0.37l-0.1,0.37l1.04,2.27l1.31,1.46l-0.44,0.9l0.24,0.43l1.52,0.13l0.87,1.23l0.24,0.13l2.21,0.07l0.18,-0.06l1.55,-1.13l-0.14,3.22l0.23,0.3l1.14,0.29l0.16,-0.0l1.18,-0.36l1.97,3.71l-0.45,0.71l-0.04,0.14l-0.12,1.8l-0.05,2.07l-0.92,1.2l-0.03,0.31l0.38,0.8l-0.48,0.72l-0.02,0.3l1.01,2.02l-1.5,2.64Z", "name": "Peru"}, "PK": {"path": "M609.08,187.76l1.66,1.21l0.71,2.11l0.2,0.19l3.62,1.01l-1.98,1.95l-2.65,0.4l-3.75,-0.68l-0.26,0.08l-1.23,1.22l-0.07,0.31l0.89,2.46l0.88,1.92l0.1,0.12l1.67,1.14l-1.8,1.35l-0.12,0.25l0.04,1.85l-2.35,2.67l-1.59,2.79l-2.5,2.72l-2.76,-0.2l-0.24,0.09l-2.76,2.83l0.04,0.45l1.54,1.13l0.27,1.94l0.09,0.17l1.34,1.29l0.4,1.83l-5.14,-0.01l-0.22,0.09l-1.53,1.63l-1.52,-0.56l-0.76,-1.88l-1.93,-2.03l-0.25,-0.09l-4.6,0.5l-4.05,0.05l-3.1,0.33l0.77,-2.53l3.48,-1.33l0.19,-0.33l-0.21,-1.24l-0.19,-0.23l-1.01,-0.37l-0.06,-2.18l-0.17,-0.26l-2.32,-1.16l-0.96,-1.57l-0.56,-0.65l3.16,1.05l0.14,0.01l2.45,-0.4l1.44,0.33l0.3,-0.1l0.4,-0.47l1.58,0.22l0.14,-0.01l3.25,-1.14l0.2,-0.27l0.08,-2.23l1.23,-1.38l1.73,0.0l0.28,-0.2l0.22,-0.61l1.68,-0.32l0.86,0.24l0.27,-0.05l0.98,-0.78l0.11,-0.26l-0.13,-1.57l0.96,-1.52l1.51,-0.67l0.14,-0.41l-0.74,-1.4l1.86,0.07l0.26,-0.13l0.69,-1.01l0.05,-0.2l-0.09,-0.94l1.14,-1.09l0.09,-0.28l-0.29,-1.41l-0.51,-1.07l1.23,-1.05l2.6,-0.58l2.86,-0.33l1.33,-0.54l1.3,-0.29Z", "name": "Pakistan"}, "PH": {"path": "M737.11,263.82l0.25,1.66l0.14,1.34l-0.54,1.46l-0.64,-1.79l-0.5,-0.1l-1.17,1.28l-0.05,0.32l0.74,1.71l-0.49,0.81l-2.6,-1.28l-0.61,-1.57l0.68,-1.07l-0.07,-0.4l-1.59,-1.19l-0.42,0.06l-0.69,0.91l-1.01,-0.08l-0.21,0.06l-1.58,1.2l-0.17,-0.3l0.87,-1.88l1.48,-0.66l1.18,-0.81l0.71,0.92l0.34,0.1l1.9,-0.69l0.18,-0.18l0.34,-0.94l1.57,-0.06l0.29,-0.32l-0.1,-1.38l1.41,0.83l0.36,2.06ZM734.94,254.42l0.56,2.24l-1.41,-0.49l-0.4,0.3l0.07,0.94l0.51,1.3l-0.54,0.26l-0.08,-1.34l-0.25,-0.28l-0.56,-0.1l-0.23,-0.91l1.03,0.14l0.34,-0.31l-0.03,-0.96l-0.06,-0.18l-1.14,-1.44l1.62,0.04l0.57,0.78ZM724.68,238.33l1.48,0.71l0.33,-0.04l0.44,-0.38l0.05,0.13l-0.37,0.97l0.01,0.23l0.81,1.75l-0.59,1.92l-1.37,0.79l-0.14,0.2l-0.39,2.07l0.01,0.14l0.56,2.04l0.23,0.21l1.33,0.28l0.14,-0.0l1.0,-0.27l2.82,1.28l-0.2,1.16l0.12,0.29l0.66,0.5l-0.13,0.56l-1.54,-0.99l-0.89,-1.29l-0.49,0.0l-0.44,0.65l-1.34,-1.28l-0.26,-0.08l-2.18,0.36l-0.96,-0.44l0.09,-0.72l0.69,-0.57l-0.01,-0.47l-0.75,-0.59l-0.47,0.14l-0.15,0.43l-0.86,-1.02l-0.34,-1.02l-0.07,-1.74l0.49,0.41l0.49,-0.21l0.26,-3.99l0.73,-2.1l1.23,0.0ZM731.12,258.92l-0.82,0.75l-0.83,1.64l-0.52,0.5l-1.17,-1.33l0.36,-0.47l0.62,-0.7l0.07,-0.15l0.24,-1.35l0.73,-0.08l-0.31,1.29l0.16,0.34l0.37,-0.09l1.21,-1.6l-0.12,1.24ZM726.66,255.58l0.85,0.45l0.14,0.03l1.28,-0.0l-0.03,0.62l-1.04,0.96l-1.15,0.55l-0.05,-0.71l0.17,-1.26l-0.01,-0.13l-0.16,-0.51ZM724.92,252.06l-0.45,1.5l-0.7,-0.83l-0.95,-1.43l1.44,0.06l0.67,0.7ZM717.48,261.28l-1.87,1.35l0.21,-0.3l1.81,-1.57l1.5,-1.75l0.97,-1.84l0.23,1.08l-1.56,1.33l-1.29,1.7Z", "name": "Philippines"}, "PL": {"path": "M458.8,144.25l-0.96,-1.98l0.18,-1.06l-0.01,-0.15l-0.62,-1.8l-0.82,-1.11l0.56,-0.73l0.05,-0.28l-0.51,-1.51l1.48,-0.87l3.88,-1.58l3.06,-1.14l2.23,0.52l0.15,0.66l0.29,0.23l2.4,0.04l3.11,0.39l4.56,-0.05l1.12,0.32l0.51,0.89l0.1,1.45l0.03,0.12l0.66,1.23l-0.01,1.08l-1.33,0.61l-0.14,0.41l0.74,1.5l0.07,1.53l1.22,2.79l-0.19,0.66l-1.09,0.33l-0.14,0.09l-2.27,2.72l-0.04,0.31l0.35,0.8l-2.22,-1.16l-0.21,-0.02l-1.72,0.44l-1.1,-0.31l-0.21,0.02l-1.3,0.61l-1.11,-1.02l-0.32,-0.05l-0.81,0.35l-1.15,-1.61l-0.21,-0.12l-1.65,-0.17l-0.19,-0.82l-0.23,-0.23l-1.72,-0.37l-0.34,0.17l-0.25,0.56l-0.88,-0.44l0.12,-0.69l-0.25,-0.35l-1.78,-0.27l-1.08,-0.97Z", "name": "Poland"}, "ZM": {"path": "M502.81,308.32l1.09,1.04l0.58,1.94l-0.39,0.66l-0.5,2.05l-0.0,0.14l0.45,1.95l-0.69,0.77l-0.06,0.11l-0.76,2.37l0.15,0.36l0.62,0.31l-6.85,1.9l-0.22,0.33l0.2,1.54l-1.62,0.3l-0.12,0.05l-1.43,1.02l-0.11,0.15l-0.25,0.73l-0.73,0.17l-0.14,0.08l-2.18,2.12l-1.33,1.6l-0.65,0.05l-0.83,-0.29l-2.75,-0.28l-0.24,-0.1l-0.15,-0.27l-0.99,-0.58l-0.12,-0.04l-1.73,-0.14l-1.88,0.54l-1.5,-1.48l-1.61,-2.01l0.11,-7.73l4.92,0.03l0.29,-0.37l-0.19,-0.79l0.34,-0.86l0.0,-0.21l-0.41,-1.11l0.26,-1.14l-0.01,-0.16l-0.12,-0.36l0.18,0.01l0.1,0.56l0.31,0.25l1.14,-0.06l1.44,0.21l0.76,1.05l0.19,0.12l2.01,0.35l0.19,-0.03l1.24,-0.65l0.44,1.03l0.22,0.18l1.81,0.34l0.85,0.99l1.02,1.39l0.24,0.12l1.92,0.02l0.3,-0.32l-0.21,-2.74l-0.47,-0.23l-0.53,0.36l-1.58,-0.89l-0.51,-0.34l0.29,-2.36l0.44,-2.99l-0.03,-0.18l-0.5,-0.99l0.61,-1.38l0.53,-0.24l3.26,-0.41l0.89,0.23l1.01,0.62l1.04,0.44l1.6,0.43l1.35,0.72Z", "name": "Zambia"}, "EE": {"path": "M482.19,120.88l0.23,-1.68l-0.43,-0.31l-0.75,0.37l-1.34,-1.1l-0.18,-1.75l2.92,-0.95l3.07,-0.53l2.66,0.6l2.48,-0.1l0.18,0.31l-1.65,1.96l-0.06,0.26l0.71,3.25l-0.88,0.94l-1.85,-0.01l-2.08,-1.3l-1.14,-0.47l-0.2,-0.01l-1.69,0.51Z", "name": "Estonia"}, "EG": {"path": "M508.07,208.8l-0.66,1.06l-0.53,2.03l-0.64,1.32l-0.32,0.26l-1.74,-1.85l-1.77,-3.86l-0.48,-0.09l-0.26,0.25l-0.07,0.32l1.04,2.88l1.55,2.76l1.89,4.18l0.94,1.48l0.83,1.54l2.08,2.73l-0.3,0.28l-0.1,0.23l0.08,1.72l0.11,0.22l2.91,2.37l-28.78,0.0l0.0,-19.06l-0.73,-2.2l0.61,-1.59l0.0,-0.2l-0.34,-1.04l0.73,-1.08l3.13,-0.04l2.36,0.72l2.48,0.81l1.15,0.43l0.23,-0.01l1.93,-0.87l1.02,-0.78l2.08,-0.21l1.59,0.31l0.62,1.24l0.52,0.03l0.46,-0.71l1.86,0.59l1.95,0.16l0.17,-0.04l0.92,-0.52l1.48,4.24Z", "name": "Egypt"}, "ZA": {"path": "M467.06,373.27l-0.13,-0.29l0.01,-1.58l-0.02,-0.12l-0.71,-1.64l0.59,-0.37l0.14,-0.26l-0.07,-2.13l-0.05,-0.15l-1.63,-2.58l-1.25,-2.31l-1.71,-3.37l0.88,-0.98l0.7,0.52l0.39,1.08l0.23,0.19l1.1,0.19l1.55,0.51l0.14,0.01l1.35,-0.2l0.11,-0.04l2.24,-1.39l0.14,-0.25l0.0,-9.4l0.16,0.09l1.39,2.38l-0.22,1.53l0.04,0.19l0.56,0.94l0.3,0.14l1.79,-0.27l0.16,-0.08l1.23,-1.18l1.17,-0.79l0.1,-0.12l0.57,-1.19l1.02,-0.52l0.9,0.28l1.16,0.73l0.14,0.05l2.04,0.13l0.13,-0.02l1.6,-0.62l0.18,-0.19l0.63,-1.93l1.18,-0.19l0.19,-0.12l0.78,-1.05l0.81,-1.71l2.18,-1.91l3.44,-1.88l0.89,0.02l1.17,0.43l0.21,-0.0l0.76,-0.29l1.07,0.21l1.15,3.55l0.63,1.82l-0.44,2.9l0.1,0.52l-0.74,-0.29l-0.18,-0.01l-0.72,0.19l-0.21,0.2l-0.22,0.74l-0.66,0.97l-0.05,0.18l0.02,0.93l0.09,0.21l1.49,1.46l0.27,0.08l1.47,-0.29l0.22,-0.18l0.43,-1.01l1.29,0.02l-0.51,1.63l-0.29,2.2l-0.59,1.12l-2.2,1.78l-1.06,1.39l-0.72,1.44l-1.39,1.93l-2.81,2.84l-1.75,1.65l-1.85,1.24l-2.55,1.06l-1.23,0.14l-0.24,0.18l-0.22,0.54l-1.27,-0.35l-0.2,0.01l-1.15,0.5l-2.62,-0.52l-0.12,0.0l-1.46,0.33l-0.98,-0.14l-0.16,0.02l-2.55,1.1l-2.11,0.44l-1.59,1.07l-0.93,0.06l-0.97,-0.92l-0.19,-0.08l-0.72,-0.04l-1.0,-1.16l-0.25,0.05ZM493.72,359.24l-1.12,-0.86l-0.31,-0.03l-1.23,0.59l-1.36,1.07l-1.39,1.78l0.01,0.38l1.88,2.11l0.31,0.09l0.9,-0.27l0.18,-0.15l0.4,-0.77l1.28,-0.39l0.18,-0.16l0.42,-0.88l0.76,-1.32l-0.05,-0.37l-0.87,-0.82Z", "name": "South Africa"}, "EC": {"path": "M220.2,293.48l1.25,-1.76l0.02,-0.31l-0.54,-1.09l-0.5,-0.06l-0.78,0.94l-1.03,-0.75l0.33,-0.46l0.05,-0.23l-0.38,-2.04l0.66,-0.28l0.17,-0.19l0.45,-1.52l0.93,-1.58l0.04,-0.2l-0.13,-0.78l1.19,-0.47l1.57,-0.91l2.35,1.34l0.17,0.04l0.28,-0.02l0.52,0.91l0.21,0.15l2.12,0.35l0.2,-0.03l0.55,-0.31l1.08,0.73l0.97,0.54l0.31,1.67l-0.71,1.49l-2.64,2.54l-2.95,0.97l-0.15,0.11l-1.53,2.18l-0.49,1.68l-1.1,0.8l-0.87,-1.05l-0.15,-0.1l-1.01,-0.27l-0.13,-0.0l-0.7,0.14l-0.03,-0.43l0.6,-0.5l0.1,-0.31l-0.26,-0.91Z", "name": "Ecuador"}, "AL": {"path": "M470.27,171.7l0.38,0.19l0.45,-0.18l0.4,0.61l0.11,0.1l0.46,0.24l0.13,0.87l-0.3,0.95l-0.0,0.17l0.36,1.28l0.12,0.17l0.9,0.63l-0.03,0.44l-0.67,0.35l-0.16,0.22l-0.14,0.88l-0.96,1.18l-0.06,-0.03l-0.04,-0.48l-0.12,-0.22l-1.28,-0.92l-0.19,-1.25l0.2,-1.96l0.33,-0.89l-0.06,-0.3l-0.36,-0.41l-0.13,-0.75l0.66,-0.9Z", "name": "Albania"}, "AO": {"path": "M461.62,299.93l0.55,1.67l0.73,1.54l1.56,2.18l0.28,0.12l1.66,-0.2l0.81,-0.34l1.28,0.33l0.33,-0.14l0.39,-0.67l0.56,-1.3l1.37,-0.09l0.27,-0.21l0.07,-0.23l0.67,-0.01l-0.13,0.53l0.29,0.37l2.74,-0.02l0.04,1.29l0.03,0.13l0.46,0.87l-0.35,1.52l0.18,1.55l0.07,0.16l0.75,0.85l-0.13,2.89l0.41,0.29l0.56,-0.21l1.11,0.05l1.5,-0.37l0.9,0.12l0.18,0.53l-0.27,1.15l0.01,0.17l0.4,1.08l-0.33,0.85l-0.01,0.18l0.12,0.51l-4.83,-0.03l-0.3,0.3l-0.12,8.13l0.07,0.19l1.69,2.1l1.27,1.25l-4.03,0.92l-5.93,-0.36l-1.66,-1.19l-0.18,-0.06l-10.15,0.11l-0.34,0.13l-1.35,-1.05l-0.17,-0.06l-1.62,-0.08l-1.6,0.45l-0.88,0.36l-0.17,-1.2l0.34,-2.19l0.85,-2.32l0.14,-1.13l0.79,-2.24l0.57,-1.0l1.42,-1.64l0.82,-1.15l0.05,-0.13l0.26,-1.88l-0.13,-1.51l-0.07,-0.16l-0.72,-0.87l-1.23,-2.91l0.09,-0.37l0.73,-0.95l0.05,-0.27l-1.27,-4.12l-1.19,-1.54l0.1,-0.2l0.86,-0.28l0.78,0.03l0.83,-0.29l7.12,0.03ZM451.81,298.94l-0.17,0.07l-0.5,-1.42l0.85,-0.92l0.53,-0.29l0.48,0.44l-0.56,0.32l-0.1,0.1l-0.41,0.65l-0.05,0.14l-0.07,0.91Z", "name": "Angola"}, "KZ": {"path": "M598.42,172.08l-1.37,0.54l-3.3,2.09l-0.11,0.12l-1.01,1.97l-0.56,0.01l-0.6,-1.24l-0.26,-0.17l-2.95,-0.09l-0.46,-2.22l-0.29,-0.24l-0.91,-0.02l0.17,-2.72l-0.12,-0.26l-3.0,-2.22l-0.2,-0.06l-4.29,0.24l-2.8,0.42l-2.36,-2.7l-6.4,-3.65l-0.23,-0.03l-6.45,1.83l-0.22,0.29l0.1,10.94l-0.84,0.1l-1.65,-2.21l-0.11,-0.09l-1.69,-0.84l-0.2,-0.02l-2.84,0.63l-0.14,0.07l-0.71,0.64l-0.02,-0.11l0.57,-1.17l0.0,-0.26l-0.48,-1.05l-0.17,-0.16l-2.78,-0.99l-1.08,-2.62l-0.13,-0.15l-1.24,-0.7l-0.04,-0.48l2.07,0.25l0.34,-0.29l0.09,-2.03l1.84,-0.44l2.12,0.45l0.36,-0.25l0.45,-3.04l-0.45,-2.06l-0.31,-0.23l-2.44,0.15l-2.07,-0.75l-0.23,0.01l-2.88,1.38l-2.21,0.62l-0.96,-0.38l0.22,-1.39l-0.06,-0.23l-1.6,-2.12l-0.25,-0.12l-1.72,0.08l-1.87,-1.91l1.33,-2.24l-0.06,-0.38l-0.55,-0.5l1.72,-3.08l2.3,1.7l0.48,-0.2l0.29,-2.26l4.99,-3.48l3.76,-0.08l5.46,2.27l2.96,1.33l0.26,-0.01l2.59,-1.36l3.82,-0.06l3.13,1.67l0.38,-0.09l0.63,-0.85l3.36,0.14l0.29,-0.19l0.63,-1.57l-0.13,-0.37l-3.64,-2.05l2.0,-1.36l0.1,-0.38l-0.32,-0.62l2.09,-0.76l0.13,-0.47l-1.65,-2.13l0.89,-0.91l9.27,-1.18l0.13,-0.05l1.17,-0.82l6.2,-1.27l2.26,-1.43l4.19,0.7l0.74,3.39l0.38,0.22l2.52,-0.81l2.9,1.06l-0.18,1.63l0.32,0.33l2.52,-0.23l5.0,-2.58l0.03,0.39l3.16,2.62l5.57,8.48l0.49,0.02l1.18,-1.53l3.22,1.78l0.21,0.03l3.5,-0.83l1.21,0.52l1.16,1.82l0.15,0.12l1.67,0.61l1.01,1.32l0.28,0.11l3.04,-0.41l1.1,1.64l-1.68,1.89l-1.97,0.28l-0.26,0.29l-0.12,3.09l-1.2,1.23l-4.81,-1.01l-0.35,0.2l-1.77,5.51l-1.14,0.62l-4.92,1.23l-0.2,0.41l2.14,5.06l-1.45,0.67l-0.17,0.31l0.15,1.28l-1.05,-0.3l-1.21,-1.04l-0.17,-0.07l-3.73,-0.32l-4.15,-0.08l-0.92,0.31l-3.46,-1.24l-0.22,0.01l-1.42,0.63l-0.17,0.21l-0.32,1.49l-3.82,-0.97l-0.15,0.0l-1.65,0.43l-0.2,0.17l-0.51,1.21Z", "name": "Kazakhstan"}, "ET": {"path": "M516.0,247.63l1.21,0.92l0.3,0.04l1.3,-0.53l0.46,0.41l0.19,0.08l1.65,0.03l2.05,0.96l0.67,0.88l1.07,0.79l1.0,1.45l0.7,0.68l-0.72,0.92l-0.85,1.19l-0.04,0.25l0.19,0.67l0.04,0.74l0.29,0.28l1.4,0.04l0.55,-0.15l0.23,0.19l-0.41,0.67l0.01,0.32l0.92,1.39l0.93,1.23l0.99,0.94l0.1,0.06l8.19,2.99l1.51,0.01l-6.51,6.95l-3.14,0.11l-0.18,0.06l-2.15,1.71l-1.51,0.04l-0.22,0.1l-0.6,0.69l-1.46,-0.0l-0.93,-0.78l-0.32,-0.04l-2.29,1.05l-0.12,0.1l-0.64,0.9l-1.44,-0.17l-0.51,-0.26l-0.17,-0.03l-0.56,0.07l-0.68,-0.02l-3.1,-2.08l-0.17,-0.05l-1.62,0.0l-0.68,-0.65l0.0,-1.28l-0.21,-0.29l-1.19,-0.38l-1.42,-2.63l-0.13,-0.12l-1.05,-0.53l-0.46,-1.0l-1.27,-1.23l-0.17,-0.08l-1.08,-0.13l0.53,-0.9l1.17,-0.05l0.26,-0.17l0.37,-0.77l0.03,-0.14l-0.03,-2.23l0.7,-2.49l1.08,-0.65l0.14,-0.19l0.24,-1.0l1.03,-1.85l1.47,-1.22l0.09,-0.12l1.02,-2.51l0.36,-1.96l2.62,0.48l0.33,-0.18l0.63,-1.55Z", "name": "Ethiopia"}, "ZW": {"path": "M498.95,341.2l-1.16,-0.23l-0.16,0.01l-0.74,0.28l-1.11,-0.41l-1.02,-0.04l-1.52,-1.13l-0.12,-0.05l-1.79,-0.37l-0.65,-1.46l-0.01,-0.86l-0.22,-0.29l-0.99,-0.26l-2.74,-2.77l-0.77,-1.46l-0.52,-0.5l-0.72,-1.54l2.24,0.23l0.78,0.28l0.12,0.02l0.85,-0.06l0.21,-0.11l1.38,-1.66l2.11,-2.05l0.81,-0.18l0.22,-0.2l0.27,-0.8l1.29,-0.93l1.53,-0.28l0.11,0.66l0.3,0.25l2.02,-0.05l1.04,0.48l0.5,0.59l0.18,0.1l1.13,0.18l1.11,0.7l0.01,3.06l-0.49,1.82l-0.11,1.94l0.03,0.16l0.35,0.68l-0.24,1.3l-0.27,0.17l-0.12,0.15l-0.64,1.83l-2.49,2.8Z", "name": "Zimbabwe"}, "ES": {"path": "M398.67,172.8l0.09,-1.45l-0.06,-0.2l-0.82,-1.05l3.16,-1.96l3.01,0.54l3.33,-0.02l2.64,0.52l2.14,-0.15l3.9,0.1l0.91,1.08l0.14,0.09l4.61,1.38l0.26,-0.04l0.77,-0.55l2.66,1.29l0.17,0.03l2.59,-0.35l0.1,1.28l-2.2,1.85l-3.13,0.62l-0.23,0.23l-0.21,0.92l-1.54,1.68l-0.97,2.4l0.02,0.26l0.85,1.46l-1.27,1.14l-0.09,0.14l-0.5,1.73l-1.73,0.53l-0.15,0.1l-1.68,2.1l-3.03,0.04l-2.38,-0.05l-0.17,0.05l-1.57,1.01l-0.9,1.01l-0.96,-0.19l-0.82,-0.86l-0.69,-1.6l-0.22,-0.18l-2.14,-0.41l-0.13,-0.62l0.83,-0.97l0.39,-0.86l-0.06,-0.33l-0.73,-0.73l0.63,-1.74l-0.02,-0.25l-0.8,-1.41l0.69,-0.15l0.23,-0.27l0.09,-1.29l0.33,-0.36l0.08,-0.2l0.03,-2.16l1.03,-0.72l0.1,-0.37l-0.7,-1.5l-0.25,-0.17l-1.46,-0.11l-0.22,0.07l-0.34,0.3l-1.17,0.0l-0.55,-1.29l-0.39,-0.16l-1.02,0.44l-0.45,0.36Z", "name": "Spain"}, "ER": {"path": "M527.15,253.05l-0.77,-0.74l-1.01,-1.47l-1.14,-0.86l-0.62,-0.84l-0.11,-0.09l-2.18,-1.02l-0.12,-0.03l-1.61,-0.03l-0.52,-0.46l-0.31,-0.05l-1.31,0.54l-1.38,-1.06l-0.46,0.12l-0.69,1.68l-2.49,-0.46l-0.2,-0.76l1.06,-3.69l0.24,-1.65l0.66,-0.66l1.76,-0.4l0.16,-0.1l0.97,-1.13l1.24,2.55l0.68,2.34l0.09,0.14l1.4,1.27l3.39,2.4l1.37,1.43l2.14,2.34l0.94,0.6l-0.32,0.26l-0.85,-0.17Z", "name": "Eritrea"}, "ME": {"path": "M469.05,172.9l-0.57,-0.8l-0.1,-0.09l-0.82,-0.46l0.16,-0.33l0.35,-1.57l0.72,-0.62l0.27,-0.16l0.48,0.38l0.35,0.4l0.12,0.08l0.79,0.32l0.66,0.43l-0.43,0.62l-0.28,0.11l-0.07,-0.25l-0.53,-0.1l-1.09,1.49l-0.05,0.23l0.06,0.32Z", "name": "Montenegro"}, "MD": {"path": "M488.2,153.75l0.14,-0.11l1.49,-0.28l1.75,0.95l1.06,0.14l0.92,0.7l-0.15,0.9l0.15,0.31l0.8,0.46l0.33,1.2l0.09,0.14l0.72,0.66l-0.11,0.28l0.1,0.33l-0.06,0.02l-1.25,-0.08l-0.17,-0.29l-0.39,-0.12l-0.52,0.25l-0.16,0.36l0.13,0.42l-0.6,0.88l-0.43,1.03l-0.22,0.12l-0.32,-1.0l0.25,-1.34l-0.08,-1.38l-0.06,-0.17l-1.43,-1.87l-0.81,-1.36l-0.78,-0.95l-0.12,-0.09l-0.29,-0.12Z", "name": "Moldova"}, "MG": {"path": "M544.77,316.45l0.64,1.04l0.6,1.62l0.4,3.04l0.63,1.21l-0.22,1.07l-0.15,0.26l-0.59,-1.05l-0.52,-0.01l-0.47,0.76l-0.04,0.23l0.46,1.84l-0.19,0.92l-0.61,0.53l-0.1,0.21l-0.16,2.15l-0.97,2.98l-1.24,3.59l-1.55,4.97l-0.96,3.67l-1.08,2.93l-1.94,0.61l-2.05,1.06l-3.2,-1.53l-0.62,-1.26l-0.18,-2.39l-0.87,-2.07l-0.22,-1.8l0.4,-1.69l1.01,-0.4l0.19,-0.28l0.01,-0.79l1.15,-1.91l0.04,-0.11l0.23,-1.66l-0.03,-0.17l-0.57,-1.21l-0.46,-1.58l-0.19,-2.25l0.82,-1.36l0.33,-1.51l1.11,-0.1l1.4,-0.53l0.9,-0.45l1.03,-0.03l0.21,-0.09l1.41,-1.45l2.12,-1.65l0.75,-1.29l0.03,-0.24l-0.17,-0.56l0.53,0.15l0.32,-0.1l1.38,-1.77l0.06,-0.18l0.04,-1.44l0.54,-0.74l0.62,0.77Z", "name": "Madagascar"}, "MA": {"path": "M378.66,230.13l0.07,-0.75l0.93,-0.72l0.82,-1.37l0.04,-0.21l-0.14,-0.8l0.8,-1.74l1.33,-1.61l0.79,-0.4l0.14,-0.15l0.66,-1.55l0.08,-1.46l0.83,-1.52l1.6,-0.94l0.11,-0.11l1.56,-2.71l1.2,-0.99l2.24,-0.29l0.17,-0.08l1.95,-1.83l1.3,-0.77l2.09,-2.28l0.07,-0.26l-0.61,-3.34l0.92,-2.3l0.33,-1.44l1.52,-1.79l2.48,-1.27l1.86,-1.16l0.1,-0.11l1.67,-2.93l0.72,-1.59l1.54,0.01l1.43,1.14l0.21,0.06l2.33,-0.19l2.55,0.62l0.97,0.03l0.83,1.6l0.15,1.71l0.86,2.96l0.09,0.14l0.5,0.45l-0.31,0.73l-3.11,0.44l-0.16,0.07l-1.07,0.97l-1.36,0.23l-0.25,0.28l-0.1,1.85l-2.74,1.02l-0.14,0.11l-0.9,1.3l-1.93,0.69l-2.56,0.44l-4.04,2.01l-0.17,0.27l0.02,2.91l-0.08,0.0l-0.3,0.31l0.05,1.15l-1.25,0.07l-0.16,0.06l-0.73,0.55l-0.98,0.0l-0.85,-0.33l-0.15,-0.02l-2.11,0.29l-0.24,0.19l-0.76,1.95l-0.63,0.16l-0.21,0.19l-1.15,3.29l-3.42,2.81l-0.1,0.17l-0.81,3.57l-0.98,1.12l-0.3,0.85l-5.13,0.19Z", "name": "Morocco"}, "UZ": {"path": "M587.83,186.48l0.06,-1.46l-0.19,-0.29l-3.31,-1.24l-2.57,-1.4l-1.63,-1.38l-2.79,-1.98l-1.2,-2.98l-0.12,-0.14l-0.84,-0.54l-0.18,-0.05l-2.61,0.13l-0.76,-0.48l-0.25,-2.25l-0.17,-0.24l-3.37,-1.6l-0.32,0.04l-2.08,1.73l-2.11,1.02l-0.16,0.35l0.31,1.14l-2.14,0.03l-0.09,-10.68l6.1,-1.74l6.25,3.57l2.36,2.72l0.27,0.1l2.92,-0.44l4.17,-0.23l2.78,2.06l-0.18,2.87l0.29,0.32l0.98,0.02l0.46,2.22l0.28,0.24l3.0,0.09l0.61,1.25l0.28,0.17l0.93,-0.02l0.26,-0.16l1.06,-2.06l3.21,-2.03l1.3,-0.5l0.19,0.08l-1.75,1.62l0.05,0.48l1.85,1.12l0.27,0.02l1.65,-0.69l2.4,1.27l-2.69,1.79l-1.79,-0.27l-0.89,0.06l-0.22,-0.52l0.48,-1.26l-0.34,-0.4l-3.35,0.69l-0.22,0.18l-0.78,1.87l-1.07,1.47l-1.93,-0.13l-0.29,0.16l-0.65,1.29l0.16,0.42l1.69,0.64l0.48,1.91l-1.25,2.6l-1.64,-0.53l-1.18,-0.03Z", "name": "Uzbekistan"}, "MM": {"path": "M670.1,233.39l-1.46,1.11l-1.68,0.11l-0.26,0.19l-1.1,2.7l-0.95,0.42l-0.14,0.42l1.21,2.27l1.61,1.92l0.94,1.55l-0.82,1.99l-0.77,0.42l-0.13,0.39l0.64,1.35l1.62,1.97l0.26,1.32l-0.04,1.15l0.02,0.13l0.92,2.18l-1.3,2.23l-0.79,1.69l-0.1,-0.77l0.74,-1.87l-0.02,-0.26l-0.8,-1.42l0.2,-2.68l-0.06,-0.2l-0.98,-1.27l-0.8,-2.98l-0.45,-3.22l-1.11,-2.22l-0.45,-0.1l-1.64,1.28l-2.74,1.76l-1.26,-0.2l-1.27,-0.49l0.79,-2.93l0.0,-0.14l-0.52,-2.42l-1.93,-2.97l0.26,-0.8l-0.22,-0.39l-1.37,-0.31l-1.65,-1.98l-0.12,-1.5l0.41,0.19l0.42,-0.26l0.05,-1.7l1.08,-0.54l0.16,-0.34l-0.24,-1.0l0.5,-0.79l0.05,-0.15l0.08,-2.35l1.58,0.49l0.36,-0.15l1.12,-2.19l0.15,-1.34l1.35,-2.18l0.04,-0.17l-0.07,-1.35l2.97,-1.71l1.67,0.45l0.38,-0.33l-0.18,-1.46l0.7,-0.4l0.15,-0.32l-0.13,-0.72l0.94,-0.13l0.74,1.41l0.11,0.12l0.95,0.56l0.07,1.89l-0.09,2.08l-2.28,2.15l-0.09,0.19l-0.3,3.15l0.35,0.32l2.37,-0.39l0.53,2.17l0.2,0.21l1.3,0.42l-0.63,1.9l0.14,0.36l1.86,0.99l1.1,0.49l0.24,0.0l1.45,-0.6l0.04,0.51l-2.01,1.6l-0.56,0.96l-1.34,0.56Z", "name": "Myanmar"}, "ML": {"path": "M390.79,248.2l0.67,-0.37l0.14,-0.18l0.36,-1.31l0.51,-0.04l1.68,0.69l0.21,0.0l1.34,-0.48l0.89,0.16l0.3,-0.13l0.29,-0.44l9.89,-0.04l0.29,-0.21l0.56,-1.8l-0.11,-0.33l-0.33,-0.24l-2.37,-22.1l3.41,-0.04l8.37,5.73l8.38,5.68l0.56,1.15l0.14,0.14l1.56,0.75l0.99,0.36l0.03,1.45l0.33,0.29l2.45,-0.22l0.01,5.52l-1.3,1.64l-0.06,0.15l-0.18,1.37l-1.99,0.36l-3.4,0.22l-0.19,0.09l-0.85,0.83l-1.48,0.09l-1.49,0.01l-0.54,-0.43l-0.26,-0.05l-1.38,0.36l-2.39,1.08l-0.13,0.12l-0.44,0.73l-1.88,1.11l-0.11,0.12l-0.3,0.57l-0.86,0.42l-1.1,-0.31l-0.28,0.07l-0.69,0.62l-0.09,0.16l-0.35,1.66l-1.93,2.04l-0.08,0.23l0.05,0.76l-0.63,0.99l-0.04,0.19l0.14,1.23l-0.81,0.29l-0.32,0.17l-0.27,-0.75l-0.39,-0.18l-0.65,0.26l-0.36,-0.04l-0.29,0.14l-0.37,0.6l-1.69,-0.02l-0.63,-0.34l-0.32,0.02l-0.12,0.09l-0.47,-0.45l0.1,-0.6l-0.09,-0.27l-0.31,-0.3l-0.33,-0.05l-0.05,0.02l0.02,-0.21l0.46,-0.59l-0.02,-0.39l-0.99,-1.02l-0.34,-0.74l-0.56,-0.56l-0.17,-0.09l-0.5,-0.07l-0.19,0.04l-0.58,0.35l-0.79,0.33l-0.65,0.51l-0.85,-0.16l-0.63,-0.59l-0.14,-0.07l-0.41,-0.08l-0.2,0.03l-0.59,0.31l-0.07,0.0l-0.1,-0.63l0.11,-0.85l-0.21,-0.98l-0.11,-0.17l-0.86,-0.66l-0.45,-1.34l-0.1,-1.36Z", "name": "Mali"}, "MN": {"path": "M641.06,150.59l2.41,-0.53l4.76,-2.8l3.67,-1.49l2.06,0.96l0.12,0.03l2.5,0.05l1.59,1.45l0.19,0.08l2.47,0.12l3.59,0.81l0.27,-0.07l2.43,-2.28l0.06,-0.36l-0.93,-1.77l2.33,-3.1l2.66,1.3l2.26,0.39l2.75,0.8l0.44,2.3l0.19,0.22l3.56,1.38l0.18,0.01l2.35,-0.6l3.1,-0.42l2.4,0.41l2.37,1.52l1.49,1.63l0.23,0.1l2.29,-0.03l3.13,0.52l0.15,-0.01l2.28,-0.79l3.27,-0.53l0.11,-0.04l3.56,-2.23l1.31,0.31l1.26,1.05l0.22,0.07l2.45,-0.22l-0.98,1.96l-1.77,3.21l-0.01,0.28l0.64,1.31l0.35,0.16l1.35,-0.38l2.4,0.48l0.22,-0.04l1.78,-1.09l1.82,0.92l2.11,2.07l-0.17,0.68l-1.79,-0.31l-3.74,0.45l-1.85,0.96l-1.78,2.01l-3.74,1.18l-2.46,1.61l-2.45,-0.6l-1.42,-0.28l-0.31,0.13l-1.31,1.99l0.0,0.33l0.78,1.15l0.3,0.74l-1.58,0.93l-1.75,1.59l-2.83,1.03l-3.77,0.12l-4.05,1.05l-2.81,1.54l-0.95,-0.8l-0.19,-0.07l-2.96,0.0l-3.64,-1.8l-2.55,-0.48l-3.38,0.41l-5.13,-0.67l-2.66,0.06l-1.35,-1.65l-1.12,-2.78l-0.21,-0.18l-1.5,-0.33l-2.98,-1.89l-0.12,-0.04l-3.37,-0.43l-2.84,-0.51l-0.75,-1.13l0.93,-3.54l-0.04,-0.24l-1.73,-2.55l-0.15,-0.12l-3.52,-1.18l-1.99,-1.61l-0.54,-1.85Z", "name": "Mongolia"}, "MK": {"path": "M472.73,173.87l0.08,0.01l0.32,-0.25l0.08,-0.44l1.29,-0.41l1.37,-0.28l1.03,-0.04l1.06,0.82l0.14,1.59l-0.22,0.04l-0.17,0.11l-0.32,0.4l-1.2,-0.05l-0.18,0.05l-0.9,0.61l-1.45,0.23l-0.85,-0.59l-0.3,-1.09l0.22,-0.71Z", "name": "Macedonia"}, "MW": {"path": "M507.18,313.84l-0.67,1.85l-0.01,0.16l0.7,3.31l0.31,0.24l0.75,-0.03l0.78,0.71l0.99,1.75l0.2,3.03l-0.91,0.45l-0.14,0.15l-0.59,1.38l-1.24,-1.21l-0.17,-1.62l0.49,-1.12l0.02,-0.16l-0.15,-1.03l-0.13,-0.21l-0.99,-0.65l-0.26,-0.03l-0.53,0.18l-1.31,-1.12l-1.15,-0.59l0.66,-2.06l0.75,-0.84l0.07,-0.27l-0.47,-2.04l0.48,-1.94l0.4,-0.65l0.03,-0.24l-0.64,-2.15l-0.08,-0.13l-0.44,-0.42l1.34,0.26l1.25,1.73l0.67,3.3Z", "name": "Malawi"}, "MR": {"path": "M390.54,247.66l-1.48,-1.58l-1.51,-1.88l-0.12,-0.09l-1.64,-0.67l-1.17,-0.74l-0.17,-0.05l-1.4,0.03l-0.12,0.03l-1.14,0.52l-1.15,-0.21l-0.26,0.08l-0.44,0.43l-0.11,-0.72l0.68,-1.29l0.31,-2.43l-0.28,-2.63l-0.29,-1.27l0.24,-1.24l-0.03,-0.2l-0.65,-1.24l-1.19,-1.05l0.32,-0.51l9.64,0.02l0.3,-0.34l-0.46,-3.71l0.51,-1.12l2.17,-0.22l0.27,-0.3l-0.08,-6.5l7.91,0.13l0.31,-0.3l0.01,-3.5l8.17,5.63l-2.89,0.04l-0.29,0.33l2.42,22.56l0.12,0.21l0.26,0.19l-0.43,1.38l-9.83,0.04l-0.25,0.13l-0.27,0.41l-0.77,-0.14l-0.15,0.01l-1.3,0.47l-1.64,-0.67l-0.14,-0.02l-0.79,0.06l-0.27,0.22l-0.39,1.39l-0.53,0.29Z", "name": "Mauritania"}, "UG": {"path": "M500.74,287.17l-2.84,-0.02l-0.92,0.32l-1.37,0.71l-0.29,-0.12l0.02,-1.6l0.54,-0.89l0.04,-0.13l0.14,-1.96l0.49,-1.09l0.91,-1.24l0.97,-0.68l0.8,-0.89l-0.13,-0.49l-0.79,-0.27l0.13,-2.55l0.78,-0.52l1.45,0.51l0.18,0.01l1.97,-0.57l1.72,0.01l0.18,-0.06l1.29,-0.97l0.98,1.44l0.29,1.24l1.05,2.75l-0.84,1.68l-1.94,2.66l-0.06,0.18l0.02,2.36l-4.8,0.18Z", "name": "Uganda"}, "MY": {"path": "M717.6,273.52l-1.51,0.7l-2.13,-0.41l-2.88,-0.0l-0.29,0.21l-0.84,2.77l-0.9,0.82l-0.08,0.12l-1.23,3.34l-1.81,0.47l-2.29,-0.68l-0.14,-0.01l-1.2,0.22l-0.14,0.07l-1.36,1.18l-1.47,-0.17l-0.12,0.01l-1.46,0.46l-1.51,-1.25l-0.24,-0.97l1.26,0.59l0.2,0.02l1.93,-0.47l0.22,-0.22l0.47,-1.98l0.9,-0.4l2.97,-0.54l0.17,-0.09l1.8,-1.98l1.02,-1.32l0.9,1.03l0.48,-0.04l0.43,-0.7l1.02,0.07l0.32,-0.27l0.25,-2.72l1.84,-1.67l1.23,-1.89l0.73,-0.01l1.12,1.11l0.1,0.99l0.18,0.24l1.66,0.71l1.85,0.67l-0.09,0.51l-1.45,0.11l-0.26,0.4l0.35,0.97ZM673.78,269.53l0.17,1.14l0.35,0.25l1.65,-0.3l0.18,-0.11l0.68,-0.86l0.31,0.13l1.41,1.45l0.99,1.59l0.13,1.57l-0.26,1.09l0.0,0.15l0.24,0.84l0.18,1.46l0.11,0.2l0.82,0.64l0.92,2.08l-0.03,0.52l-1.4,0.13l-2.29,-1.79l-2.86,-1.92l-0.27,-1.16l-0.07,-0.13l-1.39,-1.61l-0.33,-1.99l-0.05,-0.12l-0.84,-1.27l0.26,-1.72l-0.03,-0.18l-0.45,-0.87l0.13,-0.13l1.71,0.92Z", "name": "Malaysia"}, "MX": {"path": "M133.41,213.83l0.61,0.09l0.27,-0.09l0.93,-1.01l0.08,-0.18l0.09,-1.22l-0.09,-0.23l-1.93,-1.94l-1.46,-0.77l-2.96,-5.62l-0.86,-2.1l2.44,-0.18l2.68,-0.25l-0.03,0.08l0.17,0.4l3.79,1.35l5.81,1.97l6.96,-0.02l0.3,-0.3l0.0,-0.84l3.91,0.0l0.87,0.93l1.27,0.87l1.44,1.17l0.79,1.37l0.62,1.49l0.12,0.14l1.35,0.85l2.08,0.82l0.35,-0.1l1.49,-2.04l1.81,-0.05l1.63,1.01l1.21,1.8l0.86,1.58l1.47,1.55l0.53,1.82l0.73,1.32l0.14,0.13l1.98,0.84l1.78,0.59l0.61,-0.03l-0.78,1.89l-0.45,1.96l-0.19,3.58l-0.24,1.27l0.01,0.14l0.43,1.43l0.78,1.31l0.49,1.98l0.06,0.12l1.63,1.9l0.61,1.51l0.98,1.28l0.16,0.11l2.58,0.67l0.98,1.02l0.31,0.08l2.17,-0.71l1.91,-0.26l1.87,-0.47l1.67,-0.49l1.59,-1.06l0.11,-0.14l0.6,-1.52l0.22,-2.21l0.35,-0.62l1.58,-0.64l2.59,-0.59l2.18,0.09l1.43,-0.2l0.39,0.36l-0.07,1.02l-1.28,1.48l-0.65,1.68l0.07,0.32l0.33,0.32l-0.79,2.49l-0.28,-0.3l-0.24,-0.09l-1.0,0.08l-0.24,0.15l-0.74,1.28l-0.19,-0.13l-0.28,-0.03l-0.3,0.12l-0.19,0.29l0.0,0.06l-4.34,-0.02l-0.3,0.3l-0.0,1.16l-0.83,0.0l-0.28,0.19l0.08,0.33l0.93,0.86l0.9,0.58l0.24,0.48l0.16,0.15l0.2,0.08l-0.03,0.38l-2.94,0.01l-0.26,0.15l-1.21,2.09l0.02,0.33l0.25,0.33l-0.21,0.44l-0.04,0.22l-2.42,-2.35l-1.36,-0.87l-2.04,-0.67l-0.13,-0.01l-1.4,0.19l-2.07,0.98l-1.14,0.23l-1.72,-0.66l-1.85,-0.48l-2.31,-1.16l-1.92,-0.38l-2.79,-1.18l-2.04,-1.2l-0.6,-0.66l-0.19,-0.1l-1.37,-0.15l-2.45,-0.78l-1.07,-1.18l-2.63,-1.44l-1.2,-1.56l-0.44,-0.93l0.5,-0.15l0.2,-0.39l-0.2,-0.58l0.46,-0.55l0.07,-0.19l0.01,-0.91l-0.06,-0.18l-0.81,-1.13l-0.25,-1.08l-0.86,-1.36l-2.21,-2.63l-2.53,-2.09l-1.2,-1.63l-0.11,-0.09l-2.08,-1.06l-0.34,-0.48l0.35,-1.53l-0.16,-0.34l-1.24,-0.61l-1.39,-1.23l-0.6,-1.81l-0.24,-0.2l-1.25,-0.2l-1.38,-1.35l-1.11,-1.25l-0.1,-0.76l-0.05,-0.13l-1.33,-2.04l-0.85,-2.02l0.04,-0.99l-0.14,-0.27l-1.81,-1.1l-0.2,-0.04l-0.74,0.11l-1.34,-0.72l-0.42,0.16l-0.4,1.12l-0.0,0.19l0.41,1.3l0.24,2.04l0.06,0.15l0.88,1.16l1.84,1.86l0.4,0.61l0.12,0.1l0.27,0.14l0.29,0.82l0.31,0.2l0.2,-0.02l0.43,1.51l0.09,0.14l0.72,0.65l0.51,0.91l1.58,1.4l0.8,2.42l0.77,1.23l0.66,1.19l0.13,1.34l0.28,0.27l1.08,0.08l0.92,1.1l0.83,1.08l-0.03,0.24l-0.88,0.81l-0.13,-0.0l-0.59,-1.42l-0.07,-0.11l-1.67,-1.53l-1.81,-1.28l-1.15,-0.61l0.07,-1.85l-0.38,-1.45l-0.12,-0.17l-2.91,-2.03l-0.39,0.04l-0.11,0.11l-0.42,-0.46l-0.11,-0.08l-1.49,-0.63l-1.09,-1.16Z", "name": "Mexico"}, "VU": {"path": "M839.92,325.66l0.78,0.73l-0.18,0.07l-0.6,-0.8ZM839.13,322.74l0.27,1.36l-0.13,-0.06l-0.21,-0.02l-0.29,0.08l-0.22,-0.43l-0.03,-1.32l0.61,0.4Z", "name": "Vanuatu"}, "FR": {"path": "M444.58,172.63l-0.68,1.92l-0.72,-0.38l-0.51,-1.79l0.43,-0.95l1.15,-0.83l0.33,2.04ZM429.71,147.03l1.77,1.57l0.26,0.07l1.16,-0.23l2.12,1.44l0.56,0.28l0.16,0.03l0.61,-0.06l1.09,0.78l0.13,0.05l3.18,0.53l-1.09,1.94l-0.3,2.16l-0.48,0.38l-1.0,-0.26l-0.37,0.32l0.07,0.66l-1.73,1.68l-0.09,0.21l-0.04,1.42l0.41,0.29l0.96,-0.4l0.67,1.07l-0.09,0.78l0.04,0.19l0.61,0.97l-0.71,0.78l-0.07,0.28l0.65,2.39l0.21,0.21l1.09,0.31l-0.2,0.95l-2.08,1.58l-4.81,-0.8l-0.13,0.01l-3.65,0.99l-0.22,0.24l-0.25,1.6l-2.59,0.35l-2.74,-1.33l-0.31,0.03l-0.79,0.57l-4.38,-1.31l-0.79,-0.94l1.16,-1.64l0.05,-0.15l0.48,-6.17l-0.06,-0.21l-2.58,-3.3l-1.89,-1.65l-0.11,-0.06l-3.64,-1.17l-0.2,-1.88l2.92,-0.63l4.14,0.82l0.35,-0.36l-0.65,-3.0l1.77,1.05l0.27,0.02l5.83,-2.54l0.17,-0.19l0.71,-2.54l1.75,-0.53l0.27,0.88l0.27,0.21l1.04,0.05l1.08,1.23ZM289.1,278.45l-0.85,0.84l-0.88,0.13l-0.25,-0.51l-0.21,-0.16l-0.56,-0.1l-0.25,0.07l-0.63,0.55l-0.62,-0.29l0.5,-0.88l0.21,-1.11l0.42,-1.05l-0.03,-0.28l-0.93,-1.42l-0.18,-1.54l1.13,-1.87l2.42,0.78l2.55,2.04l0.33,0.81l-1.4,2.16l-0.77,1.84Z", "name": "France"}, "FI": {"path": "M492.26,76.42l-0.38,3.12l0.12,0.28l3.6,2.69l-2.14,2.96l-0.01,0.33l2.83,4.61l-1.61,3.36l0.03,0.31l2.15,2.87l-0.96,2.44l0.1,0.35l3.51,2.55l-0.81,1.72l-2.28,2.19l-5.28,4.79l-4.51,0.31l-4.39,1.37l-3.87,0.75l-1.34,-1.89l-0.11,-0.09l-2.23,-1.14l0.53,-3.54l-0.01,-0.14l-1.17,-3.37l1.12,-2.13l2.23,-2.44l5.69,-4.33l1.65,-0.84l0.16,-0.31l-0.26,-1.73l-0.15,-0.22l-3.4,-1.91l-0.77,-1.47l-0.07,-6.45l-0.12,-0.24l-3.91,-2.94l-3.0,-1.92l0.97,-0.76l2.6,2.17l0.21,0.07l3.2,-0.21l2.63,1.03l0.3,-0.05l2.39,-1.94l0.09,-0.13l1.18,-3.12l3.63,-1.42l2.87,1.59l-0.98,2.87Z", "name": "Finland"}, "FJ": {"path": "M869.98,327.07l-1.31,0.44l-0.14,-0.41l0.96,-0.41l0.85,-0.17l1.43,-0.78l-0.16,0.65l-1.64,0.67ZM867.58,329.12l0.54,0.47l-0.31,1.0l-1.32,0.3l-1.13,-0.26l-0.17,-0.78l0.72,-0.66l0.98,0.27l0.25,-0.04l0.43,-0.29Z", "name": "Fiji"}, "FK": {"path": "M268.15,427.89l2.6,-1.73l1.98,0.77l0.31,-0.05l1.32,-1.17l1.58,1.18l-0.54,0.84l-3.1,0.92l-1.0,-1.04l-0.39,-0.04l-1.9,1.35l-0.86,-1.04Z", "name": "Falkland Islands"}, "NI": {"path": "M202.1,252.6l0.23,-0.0l0.12,-0.11l0.68,-0.09l0.22,-0.15l0.23,-0.43l0.2,-0.01l0.28,-0.31l-0.04,-0.97l0.29,-0.03l0.5,0.02l0.25,-0.11l0.37,-0.46l0.51,0.35l0.4,-0.06l0.23,-0.28l0.45,-0.29l0.87,-0.7l0.11,-0.21l0.02,-0.26l0.23,-0.12l0.25,-0.48l0.29,0.27l0.14,0.07l0.5,0.12l0.22,-0.03l0.48,-0.28l0.66,-0.02l0.87,-0.33l0.36,-0.32l0.21,0.01l-0.11,0.48l0.0,0.14l0.22,0.8l-0.54,0.85l-0.27,1.03l-0.09,1.18l0.14,0.72l0.05,0.95l-0.24,0.15l-0.13,0.19l-0.23,1.09l0.0,0.14l0.14,0.53l-0.42,0.53l-0.06,0.24l0.12,0.69l0.08,0.15l0.18,0.19l-0.26,0.23l-0.49,-0.11l-0.35,-0.44l-0.16,-0.1l-0.79,-0.21l-0.23,0.03l-0.45,0.26l-1.51,-0.62l-0.31,0.05l-0.17,0.15l-1.81,-1.62l-0.6,-0.9l-1.04,-0.79l-0.77,-0.71Z", "name": "Nicaragua"}, "NL": {"path": "M436.22,136.65l1.82,0.08l0.36,0.89l-0.6,2.96l-0.53,1.06l-1.32,0.0l-0.3,0.34l0.35,2.89l-0.83,-0.47l-1.56,-1.43l-0.29,-0.07l-2.26,0.67l-1.02,-0.15l0.68,-0.48l0.1,-0.12l2.14,-4.84l3.25,-1.35Z", "name": "Netherlands"}, "NO": {"path": "M491.45,67.31l7.06,3.0l-2.52,0.94l-0.11,0.49l2.43,2.49l-3.82,1.59l-1.48,0.3l0.89,-2.61l-0.14,-0.36l-3.21,-1.78l-0.25,-0.02l-3.89,1.52l-0.17,0.17l-1.2,3.17l-2.19,1.78l-2.53,-0.99l-0.13,-0.02l-3.15,0.21l-2.69,-2.25l-0.38,-0.01l-1.43,1.11l-1.47,0.17l-0.26,0.26l-0.33,2.57l-4.42,-0.65l-0.33,0.22l-0.6,2.19l-2.17,-0.01l-0.27,0.16l-4.15,7.68l-3.88,5.76l-0.0,0.33l0.81,1.23l-0.7,1.27l-2.3,-0.06l-0.28,0.18l-1.63,3.72l-0.02,0.13l0.15,5.17l0.07,0.18l1.51,1.84l-0.79,4.24l-2.04,2.5l-0.92,1.75l-1.39,-1.88l-0.44,-0.05l-4.89,4.21l-3.16,0.81l-3.24,-1.74l-0.86,-3.82l-0.78,-8.6l2.18,-2.36l6.56,-3.28l5.0,-4.16l4.63,-5.74l5.99,-8.09l4.17,-3.23l6.84,-5.49l5.39,-1.92l4.06,0.24l0.23,-0.09l3.72,-3.67l4.51,0.19l4.4,-0.89ZM484.58,19.95l4.42,1.82l-3.25,2.68l-7.14,0.65l-7.16,-0.91l-0.39,-1.37l-0.28,-0.22l-3.48,-0.1l-2.25,-2.15l7.09,-1.48l3.55,1.36l0.28,-0.03l2.42,-1.66l6.18,1.41ZM481.99,33.92l-4.73,1.85l-3.76,-1.06l1.27,-1.02l0.04,-0.43l-1.18,-1.35l4.46,-0.94l0.89,1.83l0.17,0.15l2.83,0.96ZM466.5,23.95l7.64,3.87l-5.63,1.94l-0.19,0.19l-1.35,3.88l-2.08,0.96l-0.16,0.19l-1.14,4.18l-2.71,0.18l-4.94,-2.95l1.95,-1.63l-0.08,-0.51l-3.7,-1.54l-4.79,-4.54l-1.78,-4.01l6.29,-1.88l1.25,1.81l0.25,0.13l3.57,-0.08l0.26,-0.17l0.87,-1.79l3.41,-0.18l3.08,1.94Z", "name": "Norway"}, "NA": {"path": "M461.88,357.98l-1.61,-1.77l-0.94,-1.9l-0.54,-2.58l-0.62,-1.95l-0.83,-4.05l-0.06,-3.13l-0.33,-1.5l-0.07,-0.14l-0.95,-1.06l-1.27,-2.12l-1.3,-3.1l-0.59,-1.71l-1.98,-2.46l-0.13,-1.67l0.99,-0.4l1.44,-0.42l1.48,0.07l1.42,1.11l0.31,0.03l0.32,-0.15l9.99,-0.11l1.66,1.18l0.16,0.06l6.06,0.37l4.69,-1.06l2.01,-0.57l1.5,0.14l0.63,0.37l-1.0,0.41l-0.7,0.01l-0.16,0.05l-1.38,0.88l-0.79,-0.88l-0.29,-0.09l-3.83,0.9l-1.84,0.08l-0.29,0.3l-0.07,8.99l-2.18,0.08l-0.29,0.3l-0.0,17.47l-2.04,1.27l-1.21,0.18l-1.51,-0.49l-0.99,-0.18l-0.36,-1.0l-0.1,-0.14l-0.99,-0.74l-0.4,0.04l-0.98,1.09Z", "name": "Namibia"}, "NC": {"path": "M835.87,338.68l2.06,1.63l1.01,0.94l-0.49,0.32l-1.21,-0.62l-1.76,-1.16l-1.58,-1.36l-1.61,-1.79l-0.16,-0.41l0.54,0.02l1.32,0.83l1.08,0.87l0.79,0.73Z", "name": "New Caledonia"}, "NE": {"path": "M426.67,254.17l0.03,-1.04l-0.24,-0.3l-2.66,-0.53l-0.06,-1.0l-0.07,-0.17l-1.37,-1.62l-0.3,-1.04l0.15,-0.94l1.37,-0.09l0.19,-0.09l0.85,-0.83l3.34,-0.22l2.22,-0.41l0.24,-0.26l0.2,-1.5l1.32,-1.65l0.07,-0.19l-0.01,-5.74l3.4,-1.13l7.24,-5.12l8.46,-4.95l3.76,1.08l1.35,1.39l0.36,0.05l1.39,-0.77l0.55,3.66l0.12,0.2l0.82,0.6l0.03,0.69l0.1,0.21l0.87,0.74l-0.47,0.99l-0.96,5.26l-0.13,3.25l-3.08,2.34l-0.1,0.15l-1.08,3.37l0.08,0.31l0.94,0.86l-0.01,1.51l0.29,0.3l1.25,0.05l-0.14,0.66l-0.51,0.11l-0.24,0.26l-0.06,0.57l-0.04,0.0l-1.59,-2.62l-0.21,-0.14l-0.59,-0.1l-0.23,0.05l-1.83,1.33l-1.79,-0.68l-1.42,-0.17l-0.17,0.03l-0.65,0.32l-1.39,-0.07l-0.19,0.06l-1.4,1.03l-1.12,0.05l-2.97,-1.29l-0.26,0.01l-1.12,0.59l-1.08,-0.04l-0.85,-0.88l-0.11,-0.07l-2.51,-0.95l-0.14,-0.02l-2.69,0.3l-0.16,0.07l-0.65,0.55l-0.1,0.16l-0.34,1.41l-0.69,0.98l-0.05,0.15l-0.13,1.72l-1.47,-1.13l-0.18,-0.06l-0.9,0.01l-0.2,0.08l-0.32,0.28Z", "name": "Niger"}, "NG": {"path": "M442.0,272.7l-2.4,0.83l-0.88,-0.12l-0.19,0.04l-0.89,0.52l-1.78,-0.05l-1.23,-1.44l-0.88,-1.87l-1.77,-1.66l-0.21,-0.08l-3.78,0.03l0.13,-3.75l-0.06,-1.58l0.44,-1.47l0.74,-0.75l1.21,-1.56l0.04,-0.29l-0.22,-0.56l0.44,-0.9l0.01,-0.24l-0.54,-1.44l0.26,-2.97l0.72,-1.06l0.33,-1.37l0.51,-0.43l2.53,-0.28l2.38,0.9l0.89,0.91l0.2,0.09l1.28,0.04l0.15,-0.03l1.06,-0.56l2.9,1.26l0.13,0.02l1.28,-0.06l0.16,-0.06l1.39,-1.02l1.36,0.07l0.15,-0.03l0.64,-0.32l1.22,0.13l1.9,0.73l0.28,-0.04l1.86,-1.35l0.33,0.06l1.62,2.67l0.29,0.14l0.32,-0.04l0.73,0.74l-0.19,0.37l-0.12,0.74l-2.03,1.89l-0.07,0.11l-0.66,1.62l-0.35,1.28l-0.48,0.51l-0.07,0.12l-0.48,1.67l-1.26,0.98l-0.1,0.15l-0.38,1.24l-0.58,1.07l-0.2,0.91l-1.43,0.7l-1.26,-0.93l-0.19,-0.06l-0.95,0.04l-0.2,0.09l-1.41,1.39l-0.61,0.02l-0.26,0.17l-1.19,2.42l-0.61,1.67Z", "name": "Nigeria"}, "NZ": {"path": "M857.9,379.62l1.85,3.1l0.33,0.14l0.22,-0.28l0.04,-1.41l0.57,0.4l0.35,2.06l0.17,0.22l2.02,0.94l1.78,0.26l0.22,-0.06l1.31,-1.01l0.84,0.22l-0.53,2.27l-0.67,1.5l-1.71,-0.05l-0.25,0.12l-0.67,0.89l-0.05,0.23l0.21,1.15l-0.31,0.46l-2.15,3.57l-1.6,0.99l-0.28,-0.51l-0.15,-0.13l-0.72,-0.3l1.27,-2.15l0.01,-0.29l-0.82,-1.63l-0.15,-0.14l-2.5,-1.09l0.05,-0.69l1.67,-0.94l0.15,-0.21l0.42,-2.24l-0.11,-1.95l-0.03,-0.12l-0.97,-1.85l0.05,-0.41l-0.09,-0.25l-1.18,-1.17l-1.94,-2.49l-0.86,-1.64l0.38,-0.09l1.24,1.43l0.12,0.08l1.81,0.68l0.67,2.39ZM853.93,393.55l0.57,1.24l0.44,0.12l1.51,-1.03l0.52,0.91l0.0,1.09l-0.88,1.31l-1.62,2.2l-1.26,1.2l-0.05,0.38l0.64,1.02l-1.4,0.03l-0.14,0.04l-2.14,1.16l-0.14,0.17l-0.67,2.0l-1.38,3.06l-3.07,2.19l-2.12,-0.06l-1.55,-0.99l-0.14,-0.05l-2.53,-0.2l-0.31,-0.84l1.25,-2.15l3.07,-2.97l1.62,-0.59l1.81,-1.17l2.18,-1.63l1.55,-1.65l1.08,-2.18l0.9,-0.72l0.11,-0.17l0.35,-1.56l1.37,-1.07l0.4,0.91Z", "name": "New Zealand"}, "NP": {"path": "M641.26,213.53l-0.14,0.95l0.32,1.64l-0.21,0.78l-1.83,0.04l-2.98,-0.62l-1.86,-0.25l-1.37,-1.3l-0.18,-0.08l-3.38,-0.34l-3.21,-1.49l-2.38,-1.34l-2.16,-0.92l0.84,-2.2l1.51,-1.18l0.89,-0.57l1.83,0.77l2.5,1.76l1.39,0.41l0.78,1.21l0.17,0.13l1.91,0.53l2.0,1.17l2.92,0.66l2.63,0.24Z", "name": "Nepal"}, "CI": {"path": "M413.53,272.08l-0.83,0.02l-1.79,-0.49l-1.64,0.03l-3.04,0.46l-1.73,0.72l-2.4,0.89l-0.12,-0.02l0.16,-1.7l0.19,-0.25l0.06,-0.2l-0.08,-0.99l-0.09,-0.19l-1.06,-1.05l-0.15,-0.08l-0.71,-0.15l-0.51,-0.48l0.45,-0.92l0.02,-0.19l-0.24,-1.16l0.07,-0.43l0.14,-0.0l0.3,-0.26l0.15,-1.1l-0.02,-0.15l-0.13,-0.34l0.09,-0.13l0.83,-0.27l0.19,-0.37l-0.62,-2.02l-0.55,-1.0l0.14,-0.59l0.35,-0.14l0.24,-0.16l0.53,0.29l0.14,0.04l1.93,0.02l0.26,-0.14l0.36,-0.58l0.39,0.01l0.43,-0.17l0.28,0.79l0.43,0.16l0.56,-0.31l0.89,-0.32l0.92,0.45l0.39,0.75l0.14,0.13l1.13,0.53l0.3,-0.03l0.81,-0.59l1.02,-0.08l1.49,0.57l0.62,3.33l-1.03,2.09l-0.65,2.84l0.02,0.2l1.05,2.08l-0.07,0.64Z", "name": "Ivory Coast"}, "CH": {"path": "M444.71,156.27l0.05,0.3l-0.34,0.69l0.13,0.4l1.13,0.58l1.07,0.1l-0.12,0.81l-0.87,0.42l-1.75,-0.37l-0.34,0.18l-0.47,1.1l-0.86,0.07l-0.33,-0.38l-0.41,-0.04l-1.34,1.01l-1.02,0.13l-0.93,-0.58l-0.82,-1.32l-0.37,-0.12l-0.77,0.32l0.02,-0.84l1.74,-1.69l0.09,-0.25l-0.04,-0.38l0.73,0.19l0.26,-0.06l0.6,-0.48l2.02,0.02l0.24,-0.12l0.38,-0.51l2.31,0.84Z", "name": "Switzerland"}, "CO": {"path": "M232.24,284.95l-0.94,-0.52l-1.22,-0.82l-0.31,-0.01l-0.62,0.35l-1.88,-0.31l-0.54,-0.95l-0.29,-0.15l-0.37,0.03l-2.34,-1.33l-0.15,-0.35l0.57,-0.11l0.24,-0.32l-0.1,-1.15l0.46,-0.71l1.11,-0.15l0.21,-0.13l1.05,-1.57l0.95,-1.31l-0.08,-0.43l-0.73,-0.47l0.4,-1.24l0.01,-0.16l-0.53,-2.15l0.44,-0.54l0.06,-0.24l-0.4,-2.13l-0.06,-0.13l-0.93,-1.22l0.21,-0.8l0.52,0.12l0.32,-0.13l0.47,-0.75l0.03,-0.27l-0.52,-1.32l0.09,-0.11l1.14,0.07l0.22,-0.08l1.82,-1.71l0.96,-0.25l0.22,-0.28l0.02,-0.81l0.43,-2.01l1.28,-1.04l1.48,-0.05l0.27,-0.19l0.12,-0.31l1.73,0.19l0.2,-0.05l1.96,-1.28l0.97,-0.56l1.16,-1.16l0.64,0.11l0.43,0.44l-0.31,0.55l-1.49,0.39l-0.19,0.16l-0.6,1.2l-0.97,0.74l-0.73,0.94l-0.06,0.13l-0.3,1.76l-0.68,1.44l0.23,0.43l1.1,0.14l0.27,0.97l0.08,0.13l0.49,0.49l0.17,0.85l-0.27,0.86l-0.01,0.14l0.09,0.53l0.2,0.23l0.52,0.18l0.54,0.79l0.27,0.13l3.18,-0.24l1.31,0.29l1.7,2.08l0.31,0.1l0.96,-0.26l1.75,0.13l1.41,-0.27l0.56,0.27l-0.36,1.07l-0.54,0.81l-0.05,0.13l-0.2,1.8l0.51,1.79l0.07,0.12l0.65,0.68l0.05,0.32l-1.16,1.14l0.05,0.47l0.86,0.52l0.6,0.79l0.31,1.01l-0.7,-0.81l-0.44,-0.01l-0.74,0.77l-4.75,-0.05l-0.3,0.31l0.03,1.57l0.25,0.29l1.2,0.21l-0.02,0.24l-0.1,-0.05l-0.22,-0.02l-1.41,0.41l-0.22,0.29l-0.01,1.82l0.11,0.23l1.04,0.85l0.35,1.3l-0.06,1.02l-1.02,6.26l-0.84,-0.89l-0.19,-0.09l-0.25,-0.02l1.35,-2.13l-0.1,-0.42l-1.92,-1.17l-0.2,-0.04l-1.41,0.2l-0.82,-0.39l-0.26,0.0l-1.29,0.62l-1.63,-0.27l-1.4,-2.5l-0.12,-0.12l-1.1,-0.61l-0.83,-1.2l-1.67,-1.19l-0.27,-0.04l-0.54,0.19Z", "name": "Colombia"}, "CN": {"path": "M740.32,148.94l0.22,0.21l4.3,1.03l2.84,2.2l0.99,2.92l0.28,0.2l3.8,0.0l0.15,-0.04l2.13,-1.24l3.5,-0.8l-1.05,2.29l-0.95,1.13l-0.06,0.12l-0.85,3.41l-1.56,2.81l-2.83,-0.51l-0.19,0.03l-2.15,1.09l-0.15,0.34l0.65,2.59l-0.33,3.3l-1.03,0.07l-0.28,0.3l0.01,0.75l-1.09,-1.2l-0.48,0.05l-0.94,1.6l-3.76,1.26l-0.2,0.36l0.29,1.19l-1.67,-0.08l-1.11,-0.88l-0.42,0.05l-1.69,2.08l-2.71,1.57l-2.04,1.88l-3.42,0.84l-0.11,0.05l-1.8,1.34l-1.54,0.46l0.52,-0.53l0.06,-0.33l-0.44,-0.96l1.84,-1.84l0.02,-0.41l-1.32,-1.56l-0.36,-0.08l-2.23,1.08l-2.83,2.06l-1.52,1.85l-2.32,0.13l-0.2,0.09l-1.28,1.37l-0.03,0.37l1.32,1.97l0.18,0.13l1.83,0.43l0.07,1.08l0.18,0.26l1.98,0.84l0.3,-0.03l2.66,-1.96l2.06,1.04l0.12,0.03l1.4,0.07l0.27,1.0l-3.24,0.73l-0.17,0.11l-1.13,1.5l-2.38,1.4l-0.1,0.1l-1.29,1.99l0.1,0.42l2.6,1.5l0.97,2.72l1.52,2.56l1.66,2.08l-0.03,1.76l-1.4,0.67l-0.15,0.38l0.6,1.47l0.13,0.15l1.29,0.75l-0.35,2.0l-0.58,1.96l-1.22,0.21l-0.2,0.14l-1.83,2.93l-2.02,3.51l-2.29,3.13l-3.4,2.42l-3.42,2.18l-2.75,0.3l-0.15,0.06l-1.32,1.01l-0.68,-0.67l-0.41,-0.01l-1.37,1.27l-3.42,1.28l-2.62,0.4l-0.24,0.21l-0.8,2.57l-0.95,0.11l-0.53,-1.54l0.52,-0.89l-0.19,-0.44l-3.36,-0.84l-0.17,0.01l-1.09,0.4l-2.36,-0.64l-1.0,-0.9l0.35,-1.34l-0.23,-0.37l-2.22,-0.47l-1.15,-0.94l-0.36,-0.02l-2.08,1.37l-2.35,0.29l-1.98,-0.01l-0.13,0.03l-1.32,0.63l-1.28,0.38l-0.21,0.33l0.33,2.65l-0.78,-0.04l-0.14,-0.39l-0.07,-1.04l-0.41,-0.26l-1.72,0.71l-0.96,-0.43l-1.63,-0.86l0.65,-1.95l-0.19,-0.38l-1.43,-0.46l-0.56,-2.27l-0.34,-0.22l-2.26,0.38l0.25,-2.65l2.29,-2.15l0.09,-0.2l0.1,-2.21l-0.07,-2.09l-0.15,-0.25l-1.02,-0.6l-0.8,-1.52l-0.31,-0.16l-1.42,0.2l-2.16,-0.32l0.55,-0.74l0.01,-0.35l-1.17,-1.7l-0.41,-0.08l-1.67,1.07l-1.97,-0.63l-0.25,0.03l-2.89,1.73l-2.26,1.99l-1.82,0.3l-1.0,-0.66l-0.15,-0.05l-1.28,-0.06l-1.75,-0.61l-0.24,0.02l-1.35,0.69l-0.1,0.08l-1.2,1.45l-0.14,-1.41l-0.4,-0.25l-1.46,0.55l-2.83,-0.26l-2.77,-0.61l-1.99,-1.17l-1.91,-0.54l-0.78,-1.21l-0.17,-0.13l-1.36,-0.38l-2.54,-1.79l-2.01,-0.84l-0.28,0.02l-0.89,0.56l-3.31,-1.83l-2.35,-1.67l-0.57,-2.49l1.34,0.28l0.36,-0.28l0.08,-1.42l-0.05,-0.19l-0.93,-1.34l0.24,-2.18l-0.07,-0.22l-2.69,-3.32l-0.15,-0.1l-3.97,-1.11l-0.69,-2.05l-0.11,-0.15l-1.79,-1.3l-0.39,-0.73l-0.36,-1.57l0.08,-1.09l-0.18,-0.3l-1.52,-0.66l-0.22,-0.01l-0.51,0.18l-0.52,-2.21l0.59,-0.55l0.06,-0.35l-0.22,-0.44l2.12,-1.24l1.63,-0.55l2.58,0.39l0.31,-0.16l0.87,-1.75l3.05,-0.34l0.21,-0.12l0.84,-1.12l3.87,-1.59l0.15,-0.14l0.35,-0.68l0.03,-0.17l-0.17,-1.51l1.52,-0.7l0.15,-0.39l-2.12,-5.0l4.62,-1.15l1.35,-0.72l0.14,-0.17l1.72,-5.37l4.7,0.99l0.28,-0.08l1.39,-1.43l0.08,-0.2l0.11,-2.95l1.83,-0.26l0.18,-0.1l1.85,-2.08l0.61,-0.17l0.57,1.97l0.1,0.15l2.2,1.75l3.48,1.17l1.59,2.36l-0.93,3.53l0.04,0.24l0.9,1.35l0.2,0.13l2.98,0.53l3.32,0.43l2.97,1.89l1.49,0.35l1.08,2.67l1.52,1.88l0.24,0.11l2.74,-0.07l5.15,0.67l3.36,-0.41l2.39,0.43l3.67,1.81l0.13,0.03l2.92,-0.0l1.02,0.86l0.34,0.03l2.88,-1.59l3.98,-1.03l3.81,-0.13l3.02,-1.12l1.77,-1.61l1.73,-1.01l0.13,-0.37l-0.41,-1.01l-0.72,-1.07l1.09,-1.66l1.21,0.24l2.57,0.63l0.24,-0.04l2.46,-1.62l3.78,-1.19l0.13,-0.09l1.8,-2.03l1.66,-0.84l3.54,-0.41l1.93,0.35l0.34,-0.22l0.27,-1.12l-0.08,-0.29l-2.27,-2.22l-2.08,-1.07l-0.29,0.01l-1.82,1.12l-2.36,-0.47l-0.14,0.01l-1.18,0.34l-0.46,-0.94l1.69,-3.08l1.1,-2.21l2.75,1.12l0.26,-0.02l3.53,-2.06l0.15,-0.26l-0.02,-1.35l2.18,-3.39l1.35,-1.04l0.12,-0.24l-0.03,-1.85l-0.15,-0.25l-1.0,-0.58l1.68,-1.37l3.01,-0.59l3.25,-0.09l3.67,0.99l2.08,1.18l1.51,3.3l0.95,1.45l0.85,1.99l0.92,3.19ZM697.0,237.37l-1.95,1.12l-1.74,-0.68l-0.06,-1.9l1.08,-1.03l2.62,-0.7l1.23,0.05l0.37,0.65l-1.01,1.08l-0.54,1.4Z", "name": "China"}, "CM": {"path": "M453.76,278.92l-0.26,-0.11l-0.18,-0.02l-1.42,0.31l-1.56,-0.33l-1.17,0.16l-3.7,-0.05l0.3,-1.63l-0.04,-0.21l-0.98,-1.66l-0.15,-0.13l-1.03,-0.38l-0.46,-1.01l-0.13,-0.14l-0.48,-0.27l0.02,-0.46l0.62,-1.72l1.1,-2.25l0.54,-0.02l0.2,-0.09l1.41,-1.39l0.73,-0.03l1.32,0.97l0.31,0.03l1.72,-0.85l0.16,-0.2l0.22,-1.0l0.57,-1.03l0.36,-1.18l1.26,-0.98l0.1,-0.15l0.49,-1.7l0.48,-0.51l0.07,-0.13l0.35,-1.3l0.63,-1.54l2.06,-1.92l0.09,-0.17l0.12,-0.79l0.24,-0.41l-0.04,-0.36l-0.89,-0.91l0.04,-0.45l0.28,-0.06l0.85,1.39l0.16,1.59l-0.09,1.66l0.04,0.17l1.09,1.84l-0.86,-0.02l-0.72,0.17l-1.07,-0.24l-0.34,0.17l-0.54,1.19l0.06,0.34l1.48,1.47l1.06,0.44l0.32,0.94l0.73,1.6l-0.32,0.57l-1.23,2.49l-0.54,0.41l-0.12,0.21l-0.19,1.95l0.24,1.08l-0.18,0.67l0.07,0.28l1.13,1.25l0.24,0.93l0.92,1.29l1.1,0.8l0.1,1.01l0.26,0.73l-0.12,0.93l-1.65,-0.49l-2.02,-0.66l-3.19,-0.11Z", "name": "Cameroon"}, "CL": {"path": "M246.8,429.1l-1.14,0.78l-2.25,1.21l-0.16,0.23l-0.37,2.94l-0.75,0.06l-2.72,-1.07l-2.83,-2.34l-3.06,-1.9l-0.71,-1.92l0.67,-1.84l-0.02,-0.25l-1.22,-2.13l-0.31,-5.41l1.02,-2.95l2.59,-2.4l-0.13,-0.51l-3.32,-0.8l2.06,-2.4l0.07,-0.15l0.79,-4.77l2.44,0.95l0.4,-0.22l1.31,-6.31l-0.16,-0.33l-1.68,-0.8l-0.42,0.21l-0.72,3.47l-1.01,-0.27l0.74,-4.06l0.85,-5.46l1.12,-1.96l0.03,-0.22l-0.71,-2.82l-0.19,-2.94l0.76,-0.07l0.26,-0.2l1.53,-4.62l1.73,-4.52l1.07,-4.2l-0.56,-4.2l0.73,-2.2l0.01,-0.12l-0.29,-3.3l1.46,-3.34l0.45,-5.19l0.8,-5.52l0.78,-5.89l-0.18,-4.33l-0.49,-3.47l1.1,-0.56l0.13,-0.13l0.44,-0.88l0.9,1.29l0.32,1.8l0.1,0.18l1.16,0.97l-0.73,2.33l0.01,0.21l1.33,2.91l0.97,3.6l0.35,0.22l1.57,-0.31l0.16,0.34l-0.79,2.51l-2.61,1.25l-0.17,0.28l0.08,4.36l-0.48,0.79l0.01,0.33l0.6,0.84l-1.62,1.55l-1.67,2.6l-0.89,2.47l-0.02,0.13l0.23,2.56l-1.5,2.76l-0.03,0.21l1.15,4.8l0.11,0.17l0.54,0.42l-0.01,2.37l-1.4,2.7l-0.03,0.15l0.06,2.25l-1.8,1.78l-0.09,0.21l0.02,2.73l0.71,2.63l-1.33,0.94l-0.12,0.17l-0.67,2.64l-0.59,3.03l0.4,3.55l-0.84,0.51l-0.14,0.31l0.58,3.5l0.08,0.16l0.96,0.99l-0.7,1.08l0.11,0.43l1.04,0.55l0.19,0.8l-0.89,0.48l-0.16,0.31l0.26,1.77l-0.89,4.06l-1.31,2.67l-0.03,0.19l0.28,1.53l-0.73,1.88l-1.85,1.37l-0.12,0.26l0.22,3.46l0.06,0.16l0.88,1.19l0.28,0.12l1.32,-0.17l-0.04,2.13l0.04,0.15l1.04,1.95l0.24,0.16l5.94,0.44ZM248.79,430.71l0.0,7.41l0.3,0.3l2.67,0.0l1.01,0.06l-0.54,0.91l-1.99,1.01l-1.13,-0.1l-1.42,-0.27l-1.87,-1.06l-2.57,-0.49l-3.09,-1.9l-2.52,-1.83l-2.65,-2.93l0.93,0.32l3.54,2.29l3.32,1.23l0.34,-0.09l1.29,-1.57l0.83,-2.32l2.11,-1.28l1.43,0.32Z", "name": "Chile"}, "CA": {"path": "M280.14,145.66l-1.66,2.88l0.06,0.37l0.37,0.03l1.5,-1.01l1.17,0.49l-0.64,0.83l0.13,0.46l2.22,0.89l0.28,-0.03l1.02,-0.7l2.09,0.83l-0.69,2.1l0.37,0.38l1.43,-0.45l0.27,1.43l0.74,1.88l-0.95,2.5l-0.88,0.09l-1.34,-0.48l0.49,-2.34l-0.14,-0.32l-0.7,-0.4l-0.36,0.04l-2.81,2.66l-0.63,-0.05l1.2,-1.01l-0.1,-0.52l-2.4,-0.77l-2.79,0.18l-4.65,-0.09l-0.22,-0.54l1.37,-0.99l0.01,-0.48l-0.82,-0.65l1.91,-1.79l2.57,-5.17l1.49,-1.81l2.04,-1.07l0.63,0.08l-0.27,0.51l-1.33,2.07ZM193.92,74.85l-0.01,4.24l0.19,0.28l0.33,-0.07l3.14,-3.22l2.65,2.5l-0.71,3.04l0.06,0.26l2.42,2.88l0.46,0.0l2.66,-3.14l1.83,-3.74l0.03,-0.12l0.13,-4.53l3.23,0.31l3.63,0.64l3.18,2.08l0.13,1.91l-1.79,2.22l-0.0,0.37l1.69,2.2l-0.28,1.8l-4.74,2.84l-3.33,0.62l-2.5,-1.21l-0.41,0.17l-0.73,2.05l-2.39,3.44l-0.74,1.78l-2.78,2.61l-3.48,0.26l-0.17,0.07l-1.98,1.68l-0.1,0.21l-0.15,2.33l-2.68,0.45l-0.17,0.09l-3.1,3.2l-2.75,4.38l-0.99,3.06l-0.14,4.31l0.25,0.31l3.5,0.58l1.07,3.24l1.18,2.76l0.34,0.18l3.43,-0.69l4.55,1.52l2.45,1.32l1.76,1.65l0.12,0.07l3.11,0.96l2.63,1.46l0.13,0.04l4.12,0.2l2.41,0.3l-0.36,2.81l0.8,3.51l1.81,3.78l0.08,0.1l3.73,3.17l0.34,0.03l1.93,-1.08l0.13,-0.15l1.35,-3.44l0.01,-0.18l-1.31,-5.38l-0.08,-0.14l-1.46,-1.5l3.68,-1.51l2.84,-2.46l1.45,-2.55l0.04,-0.17l-0.2,-2.39l-0.04,-0.12l-1.7,-3.07l-2.9,-2.64l2.79,-3.66l0.05,-0.27l-1.08,-3.38l-0.8,-5.75l1.45,-0.75l4.18,1.03l2.6,0.38l0.18,-0.03l1.93,-0.95l2.18,1.23l3.01,2.18l0.73,1.42l0.25,0.16l4.18,0.27l-0.06,2.95l0.83,4.7l0.22,0.24l2.19,0.55l1.75,2.08l0.38,0.07l3.63,-2.03l0.11,-0.11l2.38,-4.06l1.36,-1.43l1.76,3.01l3.26,4.68l2.68,4.19l-0.94,2.09l0.12,0.38l3.31,1.98l2.23,1.98l0.13,0.07l3.94,0.89l1.48,1.02l0.96,2.82l0.22,0.2l1.85,0.43l0.88,1.13l0.17,3.53l-1.68,1.16l-1.76,1.14l-4.08,1.17l-0.11,0.06l-3.08,2.65l-4.11,0.52l-5.35,-0.69l-3.76,-0.02l-2.62,0.23l-0.2,0.1l-2.05,2.29l-3.13,1.41l-0.11,0.08l-3.6,4.24l-2.87,2.92l-0.05,0.36l0.33,0.14l2.13,-0.52l0.15,-0.08l3.98,-4.15l5.16,-2.63l3.58,-0.31l1.82,1.3l-2.09,1.91l-0.09,0.29l0.8,3.46l0.82,2.37l0.15,0.17l3.25,1.56l0.16,0.03l4.14,-0.45l0.21,-0.12l2.03,-2.86l0.11,1.46l0.13,0.22l1.26,0.88l-2.7,1.78l-5.51,1.83l-2.52,1.26l-2.75,2.16l-1.52,-0.18l-0.08,-2.16l4.19,-2.47l0.14,-0.34l-0.3,-0.22l-4.01,0.1l-2.66,0.36l-1.45,-1.56l0.0,-4.16l-0.11,-0.23l-1.11,-0.91l-0.28,-0.05l-1.5,0.48l-0.7,-0.7l-0.45,0.02l-1.91,2.39l-0.8,2.5l-0.82,1.31l-0.95,0.43l-0.77,0.15l-0.23,0.2l-0.18,0.56l-8.2,0.02l-0.13,0.03l-1.19,0.61l-2.95,2.45l-0.78,1.13l-4.6,0.01l-0.12,0.02l-1.13,0.48l-0.13,0.44l0.37,0.55l0.2,0.82l-0.01,0.09l-3.1,1.42l-2.63,0.5l-2.84,1.57l-0.47,0.0l-0.72,-0.4l-0.18,-0.27l0.03,-0.15l0.52,-1.0l1.2,-1.71l0.73,-1.8l0.02,-0.17l-1.03,-5.47l-0.15,-0.21l-2.35,-1.32l0.16,-0.29l-0.05,-0.35l-0.37,-0.38l-0.22,-0.09l-0.56,0.0l-0.35,-0.34l-0.11,-0.65l-0.46,-0.2l-0.39,0.26l-0.2,-0.03l-0.11,-0.33l-0.48,-0.25l-0.21,-0.71l-0.15,-0.18l-3.97,-2.07l-4.8,-2.39l-0.25,-0.01l-2.19,0.89l-0.72,0.03l-3.04,-0.82l-0.14,-0.0l-1.94,0.4l-2.4,-0.98l-2.56,-0.51l-1.7,-0.19l-0.62,-0.44l-0.42,-1.67l-0.3,-0.23l-0.85,0.02l-0.29,0.3l-0.01,0.95l-69.26,-0.01l-4.77,-3.14l-1.78,-1.41l-4.51,-1.38l-1.3,-2.73l0.34,-1.96l-0.17,-0.33l-3.06,-1.37l-0.41,-2.58l-0.11,-0.18l-2.92,-2.4l-0.05,-1.53l1.32,-1.59l0.07,-0.2l-0.07,-2.21l-0.16,-0.26l-4.19,-2.22l-2.52,-4.02l-1.56,-2.6l-0.08,-0.09l-2.28,-1.64l-1.65,-1.48l-1.31,-1.89l-0.38,-0.1l-2.51,1.21l-2.28,1.92l-2.03,-2.22l-1.85,-1.71l-2.44,-1.04l-2.28,-0.12l0.03,-37.72l4.27,0.98l4.0,2.13l2.61,0.4l0.24,-0.07l2.17,-1.81l2.92,-1.33l3.63,0.53l0.18,-0.03l3.72,-1.94l3.89,-1.06l1.6,1.72l0.37,0.06l1.87,-1.04l0.14,-0.19l0.48,-1.83l1.37,0.38l4.18,3.96l0.41,0.0l2.89,-2.62l0.28,2.79l0.37,0.26l3.08,-0.73l0.17,-0.12l0.85,-1.16l2.81,0.24l3.83,1.86l5.86,1.61l3.46,0.75l2.44,-0.26l2.89,1.89l-3.12,1.89l-0.14,0.31l0.24,0.24l4.53,0.92l6.84,-0.5l2.04,-0.71l2.54,2.44l0.39,0.02l2.72,-2.16l-0.01,-0.48l-2.26,-1.61l1.27,-1.16l2.94,-0.19l1.94,-0.42l1.89,0.97l2.49,2.32l0.24,0.08l2.71,-0.33l4.35,1.9l0.17,0.02l3.86,-0.67l3.62,0.1l0.31,-0.33l-0.26,-2.44l1.9,-0.65l3.58,1.36l-0.01,3.84l0.23,0.29l0.34,-0.17l1.51,-3.23l1.81,0.1l0.31,-0.22l1.13,-4.37l-0.08,-0.29l-2.68,-2.73l-2.83,-1.76l0.19,-4.73l2.77,-3.15l3.06,0.69l2.44,1.97l3.24,4.88l-2.05,2.02l0.15,0.51l4.41,0.85ZM265.85,150.7l-0.84,0.04l-3.15,-0.99l-1.77,-1.17l0.19,-0.06l3.17,0.79l2.39,1.27l0.01,0.12ZM249.41,3.71l6.68,0.49l5.34,0.79l4.34,1.6l-0.08,1.24l-5.91,2.56l-6.03,1.21l-2.36,1.38l-0.14,0.34l0.29,0.22l4.37,-0.02l-4.96,3.01l-4.06,1.64l-0.11,0.08l-4.21,4.62l-5.07,0.92l-0.12,0.05l-1.53,1.1l-7.5,0.59l-0.28,0.28l0.24,0.31l2.67,0.54l-1.04,0.6l-0.09,0.44l1.89,2.49l-2.11,1.66l-3.83,1.52l-0.15,0.13l-1.14,2.01l-3.41,1.55l-0.16,0.36l0.35,1.19l0.3,0.22l3.98,-0.19l0.03,0.78l-6.42,2.99l-6.44,-1.41l-7.41,0.79l-3.72,-0.62l-4.48,-0.26l-0.25,-2.0l4.37,-1.13l0.21,-0.38l-1.14,-3.55l1.13,-0.28l6.61,2.29l0.35,-0.12l-0.04,-0.37l-3.41,-3.45l-0.14,-0.08l-3.57,-0.92l1.62,-1.7l4.36,-1.3l0.2,-0.18l0.71,-1.94l-0.12,-0.36l-3.45,-2.15l-0.88,-2.43l6.36,0.23l1.94,0.61l0.23,-0.02l3.91,-2.1l0.15,-0.32l-0.26,-0.24l-5.69,-0.67l-8.69,0.37l-4.3,-1.92l-2.12,-2.39l-2.82,-1.68l-0.44,-1.65l3.41,-1.06l2.93,-0.2l4.91,-0.99l3.69,-2.28l2.93,0.31l2.64,1.68l0.42,-0.1l1.84,-3.23l3.17,-0.96l4.45,-0.69l7.56,-0.26l1.26,0.64l0.18,0.03l7.2,-1.06l10.81,0.8ZM203.94,57.59l0.01,0.32l1.97,2.97l0.51,-0.01l2.26,-3.75l6.05,-1.89l4.08,4.72l-0.36,2.95l0.38,0.33l4.95,-1.36l0.11,-0.05l2.23,-1.77l5.37,2.31l3.32,2.14l0.3,1.89l0.36,0.25l4.48,-1.01l2.49,2.8l0.14,0.09l5.99,1.78l2.09,1.74l2.18,3.83l-4.29,1.91l-0.01,0.54l5.9,2.83l3.95,0.94l3.54,3.84l0.2,0.1l3.58,0.25l-0.67,2.51l-4.18,4.54l-2.84,-1.61l-3.91,-3.95l-0.26,-0.09l-3.24,0.52l-0.25,0.26l-0.32,2.37l0.1,0.26l2.63,2.38l3.42,1.89l0.96,1.0l1.57,3.8l-0.74,2.43l-2.85,-0.96l-6.26,-3.15l-0.38,0.09l0.04,0.39l3.54,3.4l2.55,2.31l0.23,0.78l-6.26,-1.43l-5.33,-2.25l-2.73,-1.73l0.67,-0.86l-0.09,-0.45l-7.38,-4.01l-0.44,0.27l0.03,0.89l-6.85,0.61l-1.8,-1.17l1.43,-2.6l4.56,-0.07l5.15,-0.52l0.23,-0.45l-0.76,-1.34l0.8,-1.89l3.21,-4.06l0.05,-0.29l-0.72,-1.95l-0.97,-1.47l-0.11,-0.1l-3.84,-2.1l-4.53,-1.33l1.09,-0.75l0.05,-0.45l-2.65,-2.75l-0.18,-0.09l-2.12,-0.24l-1.91,-1.47l-0.39,0.02l-1.27,1.25l-4.4,0.56l-9.06,-0.99l-5.28,-1.31l-4.01,-0.67l-1.72,-1.31l2.32,-1.85l0.1,-0.33l-0.28,-0.2l-3.3,-0.02l-0.74,-4.36l1.86,-4.09l2.46,-1.88l5.74,-1.15l-1.5,2.55ZM261.28,159.28l0.19,0.14l1.82,0.42l1.66,-0.05l-0.66,0.68l-0.75,0.16l-3.0,-1.25l-0.46,-0.77l0.51,-0.52l0.68,1.19ZM230.87,84.48l-2.48,0.19l-0.52,-1.74l0.96,-2.17l2.03,-0.53l1.71,1.04l0.02,1.6l-0.22,0.46l-1.5,1.16ZM229.52,58.19l0.14,0.82l-4.99,-0.22l-2.73,0.63l-0.59,-0.23l-2.61,-2.4l0.08,-1.38l0.94,-0.25l5.61,0.51l4.14,2.54ZM222.12,105.0l-0.79,1.63l-0.75,-0.22l-0.52,-0.91l0.04,-0.09l0.84,-1.01l0.74,0.06l0.44,0.55ZM183.77,38.22l2.72,1.65l0.16,0.04l4.83,-0.01l1.92,1.52l-0.51,1.75l0.18,0.36l2.84,1.14l1.56,1.19l0.16,0.06l3.37,0.22l3.65,0.42l4.07,-1.1l5.05,-0.43l3.96,0.35l2.53,1.8l0.48,1.79l-1.37,1.16l-3.6,1.03l-3.22,-0.59l-7.17,0.76l-5.1,0.09l-4.0,-0.6l-6.48,-1.56l-0.81,-2.57l-0.3,-2.49l-0.1,-0.19l-2.51,-2.25l-0.16,-0.07l-5.12,-0.63l-2.61,-1.45l0.75,-1.71l4.88,0.32ZM207.46,91.26l0.42,1.62l0.42,0.19l1.12,-0.55l1.35,0.99l2.74,1.39l2.73,1.2l0.2,1.74l0.35,0.26l1.72,-0.29l1.31,0.97l-1.72,0.96l-3.68,-0.9l-1.34,-1.71l-0.43,-0.04l-2.46,2.1l-3.23,1.85l-0.74,-1.98l-0.31,-0.19l-2.47,0.28l1.49,-1.34l0.1,-0.19l0.32,-3.15l0.79,-3.45l1.34,0.25ZM215.59,102.66l-2.73,2.0l-1.49,-0.08l-0.37,-0.7l1.61,-1.56l3.0,0.03l-0.02,0.3ZM202.79,24.07l0.11,0.12l2.54,1.53l-3.01,1.47l-4.55,4.07l-4.3,0.38l-5.07,-0.68l-2.51,-2.09l0.03,-1.72l1.86,-1.4l0.1,-0.34l-0.29,-0.2l-4.49,0.04l-2.63,-1.79l-1.45,-2.36l1.61,-2.38l1.65,-1.69l2.47,-0.4l0.19,-0.48l-0.72,-0.89l5.1,-0.26l3.1,3.05l0.13,0.07l4.21,1.25l3.99,1.06l1.92,3.65ZM187.5,59.3l-0.15,0.1l-2.59,3.4l-2.5,-0.15l-1.47,-3.92l0.04,-2.24l1.22,-1.92l2.34,-1.26l5.11,0.17l4.28,1.06l-3.36,3.86l-2.9,0.9ZM186.19,48.8l-1.15,1.63l-3.42,-0.35l-2.68,-1.15l1.11,-1.88l3.34,-1.27l2.01,1.63l0.79,1.38ZM185.78,35.41l-0.95,0.13l-4.48,-0.33l-0.4,-0.91l4.5,0.07l1.45,0.82l-0.1,0.21ZM180.76,32.56l-3.43,1.03l-1.85,-1.14l-1.01,-1.92l-0.16,-1.87l2.87,0.2l1.39,0.35l2.75,1.75l-0.55,1.6ZM181.03,76.32l-1.21,1.2l-3.19,-1.26l-0.18,-0.01l-1.92,0.45l-2.88,-1.67l1.84,-1.16l1.6,-1.77l2.45,1.17l1.45,0.77l2.05,2.28ZM169.72,54.76l2.83,0.97l0.14,0.01l4.25,-0.58l0.47,1.01l-2.19,2.16l0.07,0.48l3.61,1.95l-0.41,3.84l-3.87,1.68l-2.23,-0.36l-1.73,-1.75l-6.07,-3.53l0.03,-1.01l4.79,0.55l0.3,-0.16l-0.04,-0.34l-2.55,-2.89l2.59,-2.05ZM174.44,40.56l1.49,1.87l0.07,2.48l-1.07,3.52l-3.87,0.48l-2.41,-0.72l0.05,-2.72l-0.33,-0.3l-3.79,0.36l-0.13,-3.31l2.36,0.14l0.15,-0.03l3.7,-1.74l3.44,0.29l0.31,-0.22l0.03,-0.12ZM170.14,31.5l0.75,1.74l-3.52,-0.52l-4.19,-1.77l-4.65,-0.17l1.65,-1.11l-0.05,-0.52l-2.86,-1.26l-0.13,-1.58l4.52,0.7l6.66,1.99l1.84,2.5ZM134.64,58.08l-1.08,1.93l0.34,0.44l5.44,-1.41l3.37,2.32l0.37,-0.02l2.66,-2.28l2.03,1.38l2.01,4.53l0.53,0.04l1.26,-1.93l0.03,-0.27l-1.67,-4.55l1.82,-0.58l2.36,0.73l2.69,1.84l1.53,4.46l0.77,3.24l0.15,0.19l4.22,2.26l4.32,2.04l-0.21,1.51l-3.87,0.34l-0.19,0.5l1.45,1.54l-0.65,1.23l-4.3,-0.65l-4.4,-1.19l-2.97,0.28l-4.67,1.48l-6.31,0.65l-4.27,0.39l-1.26,-1.91l-0.15,-0.12l-3.42,-1.2l-0.16,-0.01l-2.05,0.45l-2.66,-3.02l1.2,-0.34l3.82,-0.76l3.58,0.19l3.27,-0.78l0.23,-0.29l-0.24,-0.29l-4.84,-1.06l-5.42,0.35l-3.4,-0.09l-0.97,-1.22l5.39,-1.7l0.21,-0.33l-0.3,-0.25l-3.82,0.06l-3.95,-1.1l1.88,-3.13l1.68,-1.81l6.54,-2.84l2.11,0.77ZM158.85,56.58l-1.82,2.62l-3.38,-2.9l0.49,-0.39l3.17,-0.18l1.54,0.86ZM149.71,42.7l1.0,1.87l0.37,0.14l2.17,-0.83l2.33,0.2l0.38,2.16l-1.38,2.17l-8.33,0.76l-6.34,2.15l-3.51,0.1l-0.22,-1.13l4.98,-2.12l0.17,-0.34l-0.31,-0.23l-11.27,0.6l-3.04,-0.78l3.14,-4.57l2.2,-1.35l6.87,1.7l4.4,3.0l0.14,0.05l4.37,0.39l0.27,-0.48l-3.41,-4.68l1.96,-1.62l2.28,0.53l0.79,2.32ZM145.44,29.83l-2.18,0.77l-3.79,-0.0l0.02,-0.31l2.34,-1.5l1.2,0.23l2.42,0.83ZM144.83,34.5l-4.44,1.46l-3.18,-1.48l1.6,-1.36l3.51,-0.53l3.1,0.75l-0.6,1.16ZM119.02,65.87l-6.17,2.07l-1.19,-1.82l-0.13,-0.11l-5.48,-2.32l0.92,-1.7l1.73,-3.44l2.16,-3.15l-0.02,-0.36l-2.09,-2.56l7.84,-0.71l3.59,1.02l6.32,0.27l2.35,1.37l2.25,1.71l-2.68,1.04l-6.21,3.41l-3.1,3.28l-0.08,0.21l0.0,1.81ZM129.66,35.4l-0.3,3.55l-1.77,1.67l-2.34,0.27l-4.62,2.2l-3.89,0.76l-2.83,-0.93l3.85,-3.52l5.04,-3.36l3.75,0.07l3.11,-0.7ZM111.24,152.74l-0.82,0.29l-3.92,-1.39l-0.7,-1.06l-0.12,-0.1l-2.15,-1.09l-0.41,-0.84l-0.2,-0.16l-2.44,-0.56l-0.84,-1.56l0.1,-0.36l2.34,0.64l1.53,0.5l2.28,0.34l0.78,1.04l1.24,1.55l0.09,0.08l2.42,1.3l0.81,1.39ZM88.54,134.82l0.14,0.02l2.0,-0.23l-0.67,3.48l0.06,0.24l1.78,2.22l-0.24,-0.0l-1.4,-1.42l-0.91,-1.53l-1.26,-1.08l-0.42,-1.35l0.09,-0.66l0.82,0.31Z", "name": "Canada"}, "CG": {"path": "M453.66,296.61l-0.9,-0.82l-0.35,-0.04l-0.83,0.48l-0.77,0.83l-1.65,-2.13l1.66,-1.2l0.08,-0.39l-0.81,-1.43l0.59,-0.43l1.62,-0.29l0.24,-0.24l0.1,-0.58l0.94,0.84l0.19,0.08l2.21,0.11l0.27,-0.14l0.81,-1.29l0.32,-1.76l-0.27,-1.96l-0.06,-0.15l-1.08,-1.35l1.02,-2.74l-0.09,-0.34l-0.62,-0.5l-0.22,-0.06l-1.66,0.18l-0.55,-1.03l0.12,-0.73l2.85,0.09l1.98,0.65l2.0,0.59l0.38,-0.25l0.17,-1.3l1.26,-2.24l1.34,-1.19l1.54,0.38l1.35,0.12l-0.11,1.15l-0.74,1.34l-0.5,1.61l-0.31,2.22l0.12,1.41l-0.4,0.9l-0.06,0.88l-0.24,0.67l-1.57,1.15l-1.24,1.41l-1.09,2.43l-0.03,0.13l0.08,1.95l-0.55,0.69l-1.46,1.23l-1.32,1.41l-0.61,-0.29l-0.13,-0.57l-0.29,-0.23l-1.36,-0.02l-0.23,0.1l-0.72,0.81l-0.41,-0.16Z", "name": "Republic of the Congo"}, "CF": {"path": "M459.41,266.56l1.9,-0.17l0.22,-0.12l0.36,-0.5l0.14,0.02l0.55,0.51l0.29,0.07l3.15,-0.96l0.12,-0.07l1.05,-0.97l1.29,-0.87l0.12,-0.33l-0.17,-0.61l0.38,-0.12l2.36,0.15l0.15,-0.03l2.36,-1.17l0.12,-0.1l1.78,-2.72l1.18,-0.96l1.23,-0.34l0.21,0.79l0.07,0.13l1.37,1.5l0.01,0.86l-0.39,1.0l-0.01,0.17l0.16,0.78l0.1,0.17l0.91,0.76l1.89,1.09l1.24,0.92l0.02,0.67l0.12,0.23l1.67,1.3l0.99,1.03l0.61,1.46l0.14,0.15l1.79,0.95l0.2,0.4l-0.44,0.14l-1.54,-0.06l-1.98,-0.26l-0.93,0.22l-0.19,0.14l-0.3,0.48l-0.57,0.05l-0.91,-0.49l-0.26,-0.01l-2.7,1.21l-1.04,-0.23l-0.21,0.03l-0.34,0.19l-0.12,0.13l-0.64,1.3l-1.67,-0.43l-1.77,-0.24l-1.58,-0.91l-2.06,-0.85l-0.27,0.02l-1.42,0.88l-0.97,1.27l-0.06,0.14l-0.19,1.46l-1.3,-0.11l-1.67,-0.42l-0.27,0.07l-1.55,1.41l-0.99,1.76l-0.14,-1.18l-0.13,-0.22l-1.1,-0.78l-0.86,-1.2l-0.2,-0.84l-0.07,-0.13l-1.07,-1.19l0.16,-0.59l0.0,-0.15l-0.24,-1.01l0.18,-1.77l0.5,-0.38l0.09,-0.11l1.18,-2.4Z", "name": "Central African Republic"}, "CD": {"path": "M497.85,276.25l-0.14,2.77l0.2,0.3l0.57,0.19l-0.47,0.52l-1.0,0.71l-0.96,1.31l-0.56,1.22l-0.16,2.04l-0.54,0.89l-0.04,0.15l-0.02,1.76l-0.63,0.61l-0.09,0.2l-0.08,1.33l-0.2,0.11l-0.15,0.21l-0.23,1.37l0.03,0.2l0.6,1.08l0.16,2.96l0.44,2.29l-0.24,1.25l0.01,0.15l0.5,1.46l0.07,0.12l1.41,1.37l1.09,2.56l-0.51,-0.11l-3.45,0.45l-0.67,0.3l-0.15,0.15l-0.71,1.61l0.01,0.26l0.52,1.03l-0.43,2.9l-0.31,2.55l0.13,0.29l0.7,0.46l1.75,0.99l0.31,-0.01l0.26,-0.17l0.15,1.9l-1.44,-0.02l-0.94,-1.28l-0.94,-1.1l-0.17,-0.1l-1.76,-0.33l-0.5,-1.18l-0.42,-0.15l-1.44,0.75l-1.79,-0.32l-0.77,-1.05l-0.2,-0.12l-1.59,-0.23l-0.97,0.04l-0.1,-0.53l-0.27,-0.25l-0.86,-0.06l-1.13,-0.15l-1.62,0.37l-1.04,-0.06l-0.32,0.09l0.11,-2.56l-0.08,-0.21l-0.77,-0.87l-0.17,-1.41l0.36,-1.47l-0.03,-0.21l-0.48,-0.91l-0.04,-1.52l-0.3,-0.29l-2.65,0.02l0.13,-0.53l-0.29,-0.37l-1.28,0.01l-0.28,0.21l-0.07,0.24l-1.35,0.09l-0.26,0.18l-0.62,1.45l-0.25,0.42l-1.17,-0.3l-0.19,0.01l-0.79,0.34l-1.44,0.18l-1.41,-1.96l-0.7,-1.47l-0.61,-1.86l-0.28,-0.21l-7.39,-0.03l-0.92,0.3l-0.78,-0.03l-0.78,0.25l-0.11,-0.25l0.35,-0.15l0.18,-0.26l0.07,-1.02l0.33,-0.52l0.72,-0.42l0.52,0.2l0.33,-0.08l0.76,-0.86l0.99,0.02l0.11,0.48l0.16,0.2l0.94,0.44l0.35,-0.07l1.46,-1.56l1.44,-1.21l0.68,-0.85l0.06,-0.2l-0.08,-1.99l1.04,-2.33l1.1,-1.23l1.62,-1.19l0.11,-0.14l0.29,-0.8l0.08,-0.94l0.38,-0.82l0.03,-0.16l-0.13,-1.38l0.3,-2.16l0.47,-1.51l0.73,-1.31l0.04,-0.12l0.15,-1.51l0.21,-1.66l0.89,-1.16l1.16,-0.7l1.9,0.79l1.69,0.95l1.81,0.24l1.85,0.48l0.35,-0.16l0.71,-1.43l0.16,-0.09l1.03,0.23l0.19,-0.02l2.65,-1.19l0.86,0.46l0.17,0.03l0.81,-0.08l0.23,-0.14l0.31,-0.5l0.75,-0.17l1.83,0.26l1.64,0.06l0.72,-0.21l1.39,1.9l0.16,0.11l1.12,0.3l0.24,-0.04l0.58,-0.36l1.05,0.15l0.15,-0.02l1.15,-0.44l0.47,0.84l0.08,0.09l2.08,1.57Z", "name": "Democratic Republic of the Congo"}, "CZ": {"path": "M463.29,152.22l-0.88,-0.47l-0.18,-0.03l-1.08,0.15l-1.86,-0.94l-0.21,-0.02l-0.88,0.24l-0.13,0.07l-1.25,1.17l-1.63,-0.91l-1.38,-1.36l-1.22,-0.75l-0.24,-1.24l-0.33,-0.75l1.53,-0.6l0.98,-0.84l1.74,-0.62l0.11,-0.07l0.47,-0.47l0.46,0.27l0.24,0.03l0.96,-0.3l1.06,0.95l0.15,0.07l1.57,0.24l-0.1,0.6l0.16,0.32l1.36,0.68l0.41,-0.15l0.28,-0.62l1.29,0.28l0.19,0.84l0.26,0.23l1.73,0.18l0.74,1.02l-0.17,0.0l-0.25,0.13l-0.32,0.49l-0.46,0.11l-0.22,0.23l-0.13,0.57l-0.32,0.1l-0.2,0.22l-0.03,0.14l-0.65,0.25l-1.05,-0.05l-0.28,0.17l-0.22,0.43Z", "name": "Czech Republic"}, "CY": {"path": "M505.03,193.75l-1.51,0.68l-1.0,-0.3l-0.32,-0.63l0.69,-0.06l0.41,0.13l0.19,-0.0l0.62,-0.22l0.31,0.02l0.06,0.22l0.49,0.17l0.06,-0.01Z", "name": "Cyprus"}, "CR": {"path": "M213.0,263.84l-0.98,-0.4l-0.3,-0.31l0.16,-0.24l0.05,-0.21l-0.09,-0.56l-0.1,-0.18l-0.76,-0.65l-0.99,-0.5l-0.74,-0.28l-0.13,-0.58l-0.12,-0.18l-0.66,-0.45l-0.34,-0.0l-0.13,0.31l0.13,0.59l-0.17,0.21l-0.34,-0.42l-0.14,-0.1l-0.7,-0.22l-0.23,-0.34l0.01,-0.62l0.31,-0.74l-0.14,-0.38l-0.3,-0.15l0.47,-0.4l1.48,0.6l0.26,-0.02l0.47,-0.27l0.58,0.15l0.35,0.44l0.17,0.11l0.74,0.17l0.27,-0.07l0.3,-0.27l0.52,1.09l0.97,1.02l0.77,0.71l-0.41,0.1l-0.23,0.3l0.01,1.02l0.12,0.24l0.2,0.14l-0.07,0.05l-0.11,0.3l0.08,0.37l-0.23,0.63Z", "name": "Costa Rica"}, "CU": {"path": "M215.01,226.09l2.08,0.18l1.94,0.03l2.24,0.86l0.95,0.92l0.25,0.08l2.22,-0.28l0.79,0.55l3.68,2.81l0.19,0.06l0.77,-0.03l1.18,0.42l-0.12,0.47l0.27,0.37l1.78,0.1l1.59,0.9l-0.11,0.22l-1.5,0.3l-1.64,0.13l-1.75,-0.2l-2.69,0.19l1.0,-0.86l-0.03,-0.48l-1.02,-0.68l-0.13,-0.05l-1.52,-0.16l-0.74,-0.64l-0.57,-1.42l-0.3,-0.19l-1.36,0.1l-2.23,-0.67l-0.71,-0.52l-0.14,-0.06l-3.2,-0.4l-0.42,-0.25l0.56,-0.39l0.12,-0.33l-0.27,-0.22l-2.46,-0.13l-0.2,0.06l-1.72,1.31l-0.94,0.03l-0.25,0.15l-0.29,0.53l-1.04,0.24l-0.29,-0.07l0.7,-0.43l0.1,-0.11l0.5,-0.87l1.04,-0.54l1.23,-0.49l1.86,-0.25l0.62,-0.28Z", "name": "Cuba"}, "SZ": {"path": "M500.95,353.41l-0.41,0.97l-1.16,0.23l-1.29,-1.26l-0.02,-0.71l0.63,-0.93l0.23,-0.7l0.47,-0.12l1.04,0.4l0.32,1.05l0.2,1.08Z", "name": "Swaziland"}, "SY": {"path": "M510.84,199.83l0.09,-0.11l0.07,-0.2l-0.04,-1.08l0.56,-1.4l1.3,-1.01l0.1,-0.34l-0.41,-1.11l-0.24,-0.19l-0.89,-0.11l-0.2,-1.84l0.55,-1.05l1.3,-1.22l0.09,-0.19l0.09,-1.09l0.39,0.27l0.25,0.04l2.66,-0.77l1.35,0.52l2.06,-0.01l2.93,-1.08l1.35,0.04l2.14,-0.34l-0.83,1.16l-1.31,0.68l-0.16,0.3l0.23,2.03l-0.9,3.25l-5.43,2.87l-4.79,2.91l-2.32,-0.92Z", "name": "Syria"}, "KG": {"path": "M599.04,172.15l0.38,-0.9l1.43,-0.37l4.04,1.02l0.37,-0.23l0.36,-1.64l1.17,-0.52l3.45,1.24l0.2,-0.0l0.86,-0.31l4.09,0.08l3.61,0.31l1.18,1.02l0.11,0.06l1.19,0.34l-0.13,0.26l-3.84,1.58l-0.13,0.1l-0.81,1.08l-3.08,0.34l-0.24,0.16l-0.85,1.7l-2.43,-0.37l-0.14,0.01l-1.79,0.61l-2.39,1.4l-0.12,0.39l0.25,0.49l-0.48,0.45l-4.57,0.43l-3.04,-0.94l-2.45,0.18l0.14,-1.02l2.42,0.44l0.27,-0.08l0.81,-0.81l1.76,0.27l0.21,-0.05l3.21,-2.14l-0.03,-0.51l-2.97,-1.57l-0.26,-0.01l-1.64,0.69l-1.38,-0.84l1.81,-1.67l-0.09,-0.5l-0.46,-0.18Z", "name": "Kyrgyzstan"}, "KE": {"path": "M523.3,287.04l0.06,0.17l1.29,1.8l-1.46,0.84l-0.11,0.11l-0.55,0.93l-0.81,0.16l-0.24,0.24l-0.34,1.69l-0.81,1.06l-0.46,1.58l-0.76,0.63l-3.3,-2.3l-0.16,-1.32l-0.15,-0.23l-9.35,-5.28l-0.02,-2.4l1.92,-2.63l0.91,-1.83l0.01,-0.24l-1.09,-2.86l-0.29,-1.24l-1.09,-1.63l2.93,-2.85l0.92,0.3l0.0,1.19l0.09,0.22l0.86,0.83l0.21,0.08l1.65,0.0l3.09,2.08l0.16,0.05l0.79,0.03l0.54,-0.06l0.58,0.28l1.67,0.2l0.28,-0.12l0.69,-0.98l2.04,-0.94l0.86,0.73l0.19,0.07l1.1,0.0l-1.82,2.36l-0.06,0.18l0.03,9.12Z", "name": "Kenya"}, "SS": {"path": "M505.7,261.39l0.02,1.64l-0.27,0.55l-1.15,0.05l-0.24,0.15l-0.85,1.44l0.22,0.45l1.44,0.17l1.15,1.12l0.42,0.95l0.14,0.15l1.06,0.54l1.33,2.45l-3.06,2.98l-1.44,1.08l-1.75,0.01l-1.92,0.56l-1.5,-0.53l-0.27,0.03l-0.85,0.57l-1.98,-1.5l-0.56,-1.02l-0.37,-0.13l-1.32,0.5l-1.08,-0.15l-0.2,0.04l-0.56,0.35l-0.9,-0.24l-1.44,-1.97l-0.39,-0.77l-0.13,-0.13l-1.78,-0.94l-0.65,-1.5l-1.08,-1.12l-1.57,-1.22l-0.02,-0.68l-0.12,-0.23l-1.37,-1.02l-1.17,-0.68l0.2,-0.08l0.86,-0.48l0.14,-0.18l0.63,-2.22l0.6,-1.02l1.47,-0.28l0.35,0.56l1.29,1.48l0.14,0.09l0.69,0.22l0.22,-0.02l0.83,-0.4l1.58,0.08l0.26,0.39l0.25,0.13l2.49,0.0l0.3,-0.25l0.06,-0.35l1.13,-0.42l0.18,-0.18l0.22,-0.63l0.68,-0.38l1.95,1.37l0.23,0.05l1.29,-0.26l0.19,-0.12l1.23,-1.8l1.36,-1.37l0.08,-0.25l-0.21,-1.52l-0.06,-0.15l-0.25,-0.3l0.94,-0.08l0.26,-0.21l0.1,-0.32l0.6,0.09l-0.25,1.67l0.3,1.83l0.11,0.19l1.22,0.94l0.25,0.73l-0.04,1.2l0.26,0.31l0.09,0.01Z", "name": "South Sudan"}, "SR": {"path": "M278.1,270.26l2.71,0.45l0.31,-0.14l0.19,-0.32l1.82,-0.16l2.25,0.56l-1.09,1.81l-0.04,0.19l0.2,1.72l0.05,0.13l0.9,1.35l-0.39,0.99l-0.21,1.09l-0.48,0.8l-1.2,-0.44l-0.17,-0.01l-1.12,0.24l-0.95,-0.21l-0.35,0.2l-0.25,0.73l0.05,0.29l0.3,0.35l-0.06,0.13l-1.01,-0.15l-1.42,-2.03l-0.32,-1.36l-0.29,-0.23l-0.63,-0.0l-0.95,-1.56l0.41,-1.16l0.01,-0.17l-0.08,-0.35l1.29,-0.56l0.18,-0.22l0.35,-1.97Z", "name": "Suriname"}, "KH": {"path": "M680.28,257.89l-0.93,-1.2l-1.24,-2.56l-0.56,-2.9l1.45,-1.92l3.07,-0.46l2.26,0.35l2.03,0.98l0.38,-0.11l1.0,-1.55l1.86,0.79l0.52,1.51l-0.28,2.82l-4.05,1.88l-0.12,0.45l0.79,1.1l-2.2,0.17l-2.08,0.98l-1.89,-0.33Z", "name": "Cambodia"}, "SV": {"path": "M197.02,248.89l0.18,-0.05l0.59,0.17l0.55,0.51l0.64,0.35l0.06,0.22l0.37,0.21l1.01,-0.28l0.38,0.13l0.16,0.13l-0.14,0.81l-0.18,0.38l-1.22,-0.03l-0.84,-0.23l-1.11,-0.52l-1.31,-0.15l-0.49,-0.38l0.02,-0.08l0.76,-0.57l0.46,-0.27l0.11,-0.35Z", "name": "El Salvador"}, "SK": {"path": "M468.01,150.02l0.05,0.07l0.36,0.1l0.85,-0.37l1.12,1.02l0.33,0.05l1.38,-0.65l1.07,0.3l0.16,0.0l1.69,-0.43l1.95,1.02l-0.51,0.64l-0.45,1.2l-0.32,0.2l-2.55,-0.93l-0.17,-0.01l-0.82,0.2l-0.17,0.11l-0.53,0.68l-0.94,0.32l-0.14,-0.11l-0.29,-0.04l-1.18,0.48l-0.95,0.09l-0.26,0.21l-0.15,0.47l-1.84,0.34l-0.82,-0.31l-1.14,-0.73l-0.2,-0.89l0.42,-0.84l0.91,0.05l0.12,-0.02l0.86,-0.33l0.18,-0.21l0.03,-0.13l0.32,-0.1l0.2,-0.22l0.12,-0.55l0.39,-0.1l0.18,-0.13l0.3,-0.45l0.43,-0.0Z", "name": "Slovakia"}, "KR": {"path": "M737.31,185.72l0.84,0.08l0.27,-0.12l0.89,-1.2l1.63,-0.13l1.1,-0.2l0.21,-0.16l0.12,-0.24l1.86,2.95l0.59,1.79l0.02,3.17l-0.84,1.38l-2.23,0.55l-1.95,1.14l-1.91,0.21l-0.22,-1.21l0.45,-2.07l-0.01,-0.17l-0.99,-2.67l1.54,-0.4l0.17,-0.46l-1.55,-2.24Z", "name": "South Korea"}, "SI": {"path": "M455.77,159.59l1.79,0.21l0.18,-0.04l1.2,-0.68l2.12,-0.08l0.21,-0.1l0.38,-0.42l0.1,0.01l0.28,0.62l-1.71,0.71l-0.18,0.22l-0.21,1.1l-0.71,0.26l-0.2,0.28l0.01,0.55l-0.59,-0.04l-0.79,-0.47l-0.38,0.06l-0.36,0.41l-0.84,-0.05l0.05,-0.15l-0.56,-1.24l0.21,-1.17Z", "name": "Slovenia"}, "KP": {"path": "M747.76,172.02l-0.23,-0.04l-0.26,0.08l-1.09,1.02l-0.78,1.06l-0.06,0.19l0.09,1.95l-1.12,0.57l-0.53,0.58l-0.88,0.82l-1.69,0.51l-1.09,0.79l-0.12,0.22l-0.07,1.17l-0.22,0.25l0.09,0.47l0.96,0.46l1.22,1.1l-0.19,0.37l-0.91,0.16l-1.75,0.14l-0.22,0.12l-0.87,1.18l-0.95,-0.09l-0.3,0.18l-0.97,-0.44l-0.39,0.13l-0.25,0.44l-0.29,0.09l-0.03,-0.2l-0.18,-0.23l-0.62,-0.25l-0.43,-0.29l0.52,-0.97l0.52,-0.3l0.13,-0.38l-0.18,-0.42l0.59,-1.47l0.01,-0.21l-0.16,-0.48l-0.22,-0.2l-1.41,-0.31l-0.82,-0.55l1.74,-1.62l2.73,-1.58l1.62,-1.96l0.96,0.76l0.17,0.06l2.17,0.11l0.31,-0.37l-0.32,-1.31l3.61,-1.21l0.16,-0.13l0.79,-1.34l1.25,1.38Z", "name": "North Korea"}, "SO": {"path": "M543.8,256.48l0.61,-0.05l1.14,-0.37l1.31,-0.25l0.12,-0.05l1.11,-0.81l0.57,-0.0l0.03,0.39l-0.23,1.49l0.01,1.25l-0.52,0.92l-0.7,2.71l-1.19,2.79l-1.54,3.2l-2.13,3.66l-2.12,2.79l-2.92,3.39l-2.47,2.0l-3.76,2.5l-2.33,1.9l-2.77,3.06l-0.61,1.35l-0.28,0.29l-1.22,-1.69l-0.03,-8.92l2.12,-2.76l0.59,-0.68l1.47,-0.04l0.18,-0.06l2.15,-1.71l3.16,-0.11l0.21,-0.09l7.08,-7.55l1.76,-2.12l1.14,-1.57l0.06,-0.18l0.01,-4.67Z", "name": "Somalia"}, "SN": {"path": "M379.28,250.34l-0.95,-1.82l-0.09,-0.1l-0.83,-0.6l0.62,-0.28l0.13,-0.11l1.21,-1.8l0.6,-1.31l0.71,-0.68l1.09,0.2l0.18,-0.02l1.17,-0.53l1.25,-0.03l1.17,0.73l1.59,0.65l1.47,1.83l1.59,1.7l0.12,1.56l0.49,1.46l0.1,0.14l0.85,0.65l0.18,0.82l-0.08,0.57l-0.13,0.05l-1.29,-0.19l-0.29,0.13l-0.11,0.16l-0.35,0.04l-1.83,-0.61l-5.84,-0.13l-0.12,0.02l-0.6,0.26l-0.87,-0.06l-1.01,0.32l-0.26,-1.26l1.9,0.04l0.16,-0.04l0.54,-0.32l0.37,-0.02l0.15,-0.05l0.78,-0.5l0.92,0.46l0.12,0.03l1.09,0.04l0.15,-0.03l1.08,-0.57l0.11,-0.44l-0.51,-0.74l-0.39,-0.1l-0.76,0.39l-0.62,-0.01l-0.92,-0.58l-0.18,-0.05l-0.79,0.04l-0.2,0.09l-0.48,0.51l-2.41,0.06Z", "name": "Senegal"}, "SL": {"path": "M392.19,267.53l-0.44,-0.12l-1.73,-0.97l-1.24,-1.28l-0.4,-0.84l-0.27,-1.65l1.21,-1.0l0.09,-0.12l0.27,-0.66l0.32,-0.41l0.56,-0.05l0.16,-0.07l0.5,-0.41l1.75,0.0l0.59,0.77l0.49,0.96l-0.07,0.64l0.04,0.19l0.36,0.58l-0.03,0.84l0.24,0.2l-0.64,0.65l-1.13,1.37l-0.06,0.14l-0.12,0.66l-0.43,0.58Z", "name": "Sierra Leone"}, "SB": {"path": "M826.74,311.51l0.23,0.29l-0.95,-0.01l-0.39,-0.63l0.65,0.27l0.45,0.09ZM825.01,308.52l-1.18,-1.39l-0.37,-1.06l0.24,0.0l0.82,1.84l0.49,0.6ZM823.21,309.42l-0.44,0.03l-1.43,-0.24l-0.32,-0.24l0.08,-0.5l1.29,0.31l0.72,0.47l0.11,0.18ZM817.9,303.81l2.59,1.44l0.3,0.41l-1.21,-0.66l-1.34,-0.89l-0.34,-0.3ZM813.77,302.4l0.48,0.34l0.1,0.08l-0.33,-0.17l-0.25,-0.25Z", "name": "Solomon Islands"}, "SA": {"path": "M528.24,243.1l-0.2,-0.69l-0.07,-0.12l-0.69,-0.71l-0.18,-0.94l-0.12,-0.19l-1.24,-0.89l-1.28,-2.09l-0.7,-2.08l-0.07,-0.11l-1.73,-1.79l-0.11,-0.07l-1.03,-0.39l-1.57,-2.36l-0.27,-1.72l0.1,-1.53l-0.03,-0.15l-1.44,-2.93l-1.25,-1.13l-1.34,-0.56l-0.72,-1.33l0.11,-0.49l-0.02,-0.2l-0.7,-1.38l-0.08,-0.1l-0.68,-0.56l-0.97,-1.98l-2.8,-4.03l-0.25,-0.13l-0.85,0.01l0.29,-1.11l0.12,-0.97l0.23,-0.81l2.52,0.39l0.23,-0.06l1.08,-0.84l0.6,-0.95l1.78,-0.35l0.22,-0.17l0.37,-0.83l0.74,-0.42l0.08,-0.46l-2.17,-2.4l4.55,-1.26l0.12,-0.06l0.36,-0.32l2.83,0.71l3.67,1.91l7.04,5.5l0.17,0.06l4.64,0.22l2.06,0.24l0.55,1.15l0.28,0.17l1.56,-0.06l0.9,2.15l0.14,0.15l1.14,0.57l0.39,0.85l0.11,0.13l1.59,1.06l0.12,0.91l-0.23,0.83l0.01,0.18l0.32,0.9l0.07,0.11l0.68,0.7l0.33,0.86l0.37,0.65l0.09,0.1l0.76,0.53l0.25,0.04l0.45,-0.12l0.35,0.75l0.1,0.63l0.96,2.68l0.23,0.19l7.53,1.33l0.27,-0.09l0.24,-0.26l0.87,1.41l-1.58,4.96l-7.34,2.54l-7.28,1.02l-2.34,1.17l-0.12,0.1l-1.74,2.63l-0.86,0.32l-0.49,-0.68l-0.28,-0.12l-0.92,0.12l-2.32,-0.25l-0.41,-0.23l-0.15,-0.04l-2.89,0.06l-0.63,0.2l-0.91,-0.59l-0.43,0.11l-0.66,1.27l-0.03,0.21l0.21,0.89l-0.6,0.45Z", "name": "Saudi Arabia"}, "SE": {"path": "M476.42,90.44l-0.15,0.1l-2.43,2.86l-0.07,0.24l0.36,2.31l-3.84,3.1l-4.83,3.38l-0.11,0.15l-1.82,5.45l0.03,0.26l1.78,2.68l2.27,1.99l-2.13,3.88l-2.49,0.82l-0.2,0.24l-0.95,6.05l-1.32,3.09l-2.82,-0.32l-0.3,0.16l-1.34,2.64l-2.48,0.14l-0.76,-3.15l-2.09,-4.04l-1.85,-5.01l1.03,-1.98l2.06,-2.53l0.06,-0.13l0.83,-4.45l-0.06,-0.25l-1.54,-1.86l-0.15,-5.0l1.52,-3.48l2.28,0.06l0.27,-0.16l0.87,-1.59l-0.01,-0.31l-0.8,-1.21l3.79,-5.63l4.07,-7.54l2.23,0.01l0.29,-0.22l0.59,-2.15l4.46,0.66l0.34,-0.26l0.34,-2.64l1.21,-0.14l3.24,2.08l3.78,2.85l0.06,6.37l0.03,0.14l0.67,1.29l-3.95,1.07Z", "name": "Sweden"}, "SD": {"path": "M505.98,259.75l-0.31,-0.9l-0.1,-0.14l-1.2,-0.93l-0.27,-1.66l0.29,-1.83l-0.25,-0.34l-1.16,-0.17l-0.33,0.21l-0.11,0.37l-1.3,0.11l-0.21,0.49l0.55,0.68l0.18,1.29l-1.31,1.33l-1.18,1.72l-1.04,0.21l-2.0,-1.4l-0.32,-0.02l-0.95,0.52l-0.14,0.16l-0.21,0.6l-1.16,0.43l-0.19,0.23l-0.04,0.27l-2.08,0.0l-0.25,-0.39l-0.24,-0.13l-1.81,-0.09l-0.14,0.03l-0.8,0.38l-0.49,-0.16l-1.22,-1.39l-0.42,-0.67l-0.31,-0.14l-1.81,0.35l-0.2,0.14l-0.72,1.24l-0.61,2.14l-0.73,0.4l-0.62,0.22l-0.83,-0.68l-0.12,-0.6l0.38,-0.97l0.01,-1.14l-0.08,-0.2l-1.39,-1.53l-0.25,-0.97l0.03,-0.57l-0.11,-0.25l-0.81,-0.66l-0.03,-1.34l-0.04,-0.14l-0.52,-0.98l-0.31,-0.15l-0.42,0.07l0.12,-0.44l0.63,-1.03l0.03,-0.23l-0.24,-0.88l0.69,-0.66l0.02,-0.41l-0.4,-0.46l0.58,-1.39l1.04,-1.71l1.97,0.16l0.32,-0.3l-0.12,-10.24l0.02,-0.8l2.59,-0.01l0.3,-0.3l0.0,-4.92l29.19,0.0l0.68,2.17l-0.4,0.35l-0.1,0.27l0.36,2.69l0.93,3.15l0.12,0.16l2.05,1.4l-0.99,1.15l-1.75,0.4l-0.15,0.08l-0.79,0.79l-0.08,0.17l-0.24,1.69l-1.07,3.75l-0.0,0.16l0.25,0.96l-0.38,2.1l-0.98,2.41l-1.52,1.3l-1.07,1.94l-0.25,0.99l-1.08,0.64l-0.13,0.18l-0.46,1.65Z", "name": "Sudan"}, "DO": {"path": "M241.7,234.97l0.15,-0.22l1.73,0.01l1.43,0.64l0.15,0.03l0.45,-0.04l0.36,0.74l0.28,0.17l1.02,-0.04l-0.04,0.43l0.27,0.33l1.03,0.09l0.91,0.7l-0.57,0.64l-0.99,-0.47l-0.16,-0.03l-1.11,0.11l-0.79,-0.12l-0.26,0.09l-0.38,0.4l-0.66,0.11l-0.28,-0.45l-0.38,-0.12l-0.83,0.37l-0.14,0.13l-0.85,1.49l-0.27,-0.17l-0.1,-0.58l0.05,-0.67l-0.07,-0.21l-0.44,-0.53l0.35,-0.25l0.12,-0.19l0.19,-1.0l-0.2,-1.4Z", "name": "Dominican Republic"}, "DJ": {"path": "M528.78,253.36l0.34,0.45l-0.06,0.76l-1.26,0.54l-0.05,0.53l0.82,0.53l-0.57,0.83l-0.3,-0.25l-0.27,-0.05l-0.56,0.17l-1.07,-0.03l-0.04,-0.56l-0.16,-0.56l0.76,-1.07l0.76,-0.97l0.89,0.18l0.25,-0.06l0.51,-0.42Z", "name": "Djibouti"}, "DK": {"path": "M452.4,129.07l-1.27,2.39l-2.25,-1.69l-0.26,-1.08l3.15,-1.0l0.63,1.39ZM447.87,126.25l-0.35,0.76l-0.47,-0.24l-0.38,0.09l-1.8,2.53l-0.03,0.29l0.56,1.4l-1.22,0.4l-1.68,-0.41l-0.92,-1.76l-0.07,-3.47l0.38,-0.88l0.62,-0.93l2.07,-0.21l0.19,-0.1l0.84,-0.95l1.5,-0.76l-0.06,1.26l-0.7,1.1l-0.03,0.25l0.3,1.0l0.18,0.19l1.06,0.42Z", "name": "Denmark"}, "DE": {"path": "M445.51,131.69l0.03,0.94l0.21,0.28l2.32,0.74l-0.02,1.0l0.37,0.3l2.55,-0.65l1.36,-0.89l2.63,1.27l1.09,1.01l0.51,1.51l-0.6,0.78l-0.0,0.36l0.88,1.17l0.58,1.68l-0.18,1.08l0.03,0.18l0.87,1.81l-0.66,0.2l-0.55,-0.32l-0.36,0.05l-0.58,0.58l-1.73,0.62l-0.99,0.84l-1.77,0.7l-0.16,0.4l0.42,0.94l0.26,1.34l0.14,0.2l1.25,0.76l1.22,1.2l-0.71,1.2l-0.81,0.37l-0.17,0.32l0.34,1.99l-0.04,0.09l-0.47,-0.39l-0.17,-0.07l-1.2,-0.1l-1.85,0.57l-2.15,-0.13l-0.29,0.18l-0.21,0.5l-0.96,-0.67l-0.24,-0.05l-0.67,0.16l-2.6,-0.94l-0.34,0.1l-0.42,0.57l-1.64,-0.02l0.26,-1.88l1.24,-2.15l-0.21,-0.45l-3.54,-0.58l-0.98,-0.71l0.12,-1.26l-0.05,-0.2l-0.44,-0.64l0.27,-2.18l-0.38,-3.14l1.17,-0.0l0.27,-0.17l0.63,-1.26l0.65,-3.17l-0.02,-0.17l-0.41,-1.0l0.32,-0.47l1.77,-0.16l0.37,0.6l0.47,0.06l1.7,-1.69l0.06,-0.33l-0.55,-1.24l-0.09,-1.51l1.5,0.36l0.16,-0.01l1.22,-0.4Z", "name": "Germany"}, "YE": {"path": "M553.53,242.65l-1.51,0.58l-0.17,0.16l-0.48,1.14l-0.07,0.79l-2.31,1.0l-3.98,1.19l-2.28,1.8l-0.97,0.12l-0.7,-0.14l-0.23,0.05l-1.42,1.03l-1.51,0.47l-2.07,0.13l-0.68,0.15l-0.17,0.1l-0.49,0.6l-0.57,0.16l-0.18,0.13l-0.3,0.49l-1.06,-0.05l-0.13,0.02l-0.73,0.32l-1.48,-0.11l-0.55,-1.26l0.07,-1.32l-0.04,-0.16l-0.39,-0.72l-0.48,-1.85l-0.52,-0.79l0.08,-0.02l0.22,-0.36l-0.23,-1.05l0.24,-0.39l0.04,-0.19l-0.09,-0.95l0.96,-0.72l0.11,-0.31l-0.23,-0.98l0.46,-0.88l0.75,0.49l0.26,0.03l0.63,-0.22l2.76,-0.06l0.5,0.25l2.42,0.26l0.85,-0.11l0.52,0.71l0.35,0.1l1.17,-0.43l0.15,-0.12l1.75,-2.64l2.22,-1.11l6.95,-0.96l2.55,5.58Z", "name": "Yemen"}, "AT": {"path": "M463.17,154.15l-0.14,0.99l-1.15,0.01l-0.24,0.47l0.39,0.56l-0.75,1.84l-0.36,0.4l-2.06,0.07l-0.14,0.04l-1.18,0.67l-1.96,-0.23l-3.43,-0.78l-0.5,-0.97l-0.33,-0.16l-2.47,0.55l-0.2,0.16l-0.18,0.37l-1.27,-0.38l-1.28,-0.09l-0.81,-0.41l0.25,-0.51l0.03,-0.18l-0.05,-0.28l0.35,-0.08l1.16,0.81l0.45,-0.13l0.27,-0.64l2.0,0.12l1.84,-0.57l1.05,0.09l0.71,0.59l0.47,-0.11l0.23,-0.54l0.02,-0.17l-0.32,-1.85l0.69,-0.31l0.13,-0.12l0.73,-1.23l1.61,0.89l0.35,-0.04l1.35,-1.27l0.7,-0.19l1.84,0.93l0.18,0.03l1.08,-0.15l0.81,0.43l-0.07,0.15l-0.02,0.2l0.24,1.06Z", "name": "Austria"}, "DZ": {"path": "M450.58,224.94l-8.31,4.86l-7.23,5.12l-3.46,1.13l-2.42,0.22l-0.02,-1.33l-0.2,-0.28l-1.15,-0.42l-1.45,-0.69l-0.55,-1.13l-0.1,-0.12l-8.45,-5.72l-17.72,-12.17l0.03,-0.38l-0.02,-3.21l3.84,-1.91l2.46,-0.41l2.1,-0.75l0.14,-0.11l0.9,-1.3l2.84,-1.06l0.19,-0.27l0.09,-1.81l1.21,-0.2l0.15,-0.07l1.06,-0.96l3.19,-0.46l0.23,-0.18l0.46,-1.08l-0.08,-0.34l-0.6,-0.54l-0.83,-2.85l-0.18,-1.8l-0.82,-1.57l2.13,-1.37l2.65,-0.49l0.13,-0.05l1.55,-1.15l2.34,-0.85l4.2,-0.51l4.07,-0.23l1.21,0.41l0.23,-0.01l2.3,-1.11l2.52,-0.02l0.94,0.62l0.2,0.05l1.25,-0.13l-0.36,1.03l-0.01,0.14l0.39,2.66l-0.56,2.2l-1.49,1.52l-0.08,0.24l0.22,2.12l0.11,0.2l1.94,1.58l0.02,0.54l0.12,0.23l1.45,1.06l1.04,4.85l0.81,2.42l0.13,1.19l-0.43,2.17l0.17,1.28l-0.31,1.53l0.2,1.56l-0.9,1.02l-0.01,0.38l1.43,1.88l0.09,1.06l0.04,0.13l0.89,1.48l0.37,0.12l1.03,-0.43l1.79,1.12l0.89,1.34Z", "name": "Algeria"}, "US": {"path": "M892.64,99.05l1.16,0.57l0.21,0.02l1.45,-0.38l1.92,0.99l2.17,0.47l-1.65,0.72l-1.75,-0.79l-0.93,-0.7l-0.21,-0.06l-2.11,0.22l-0.35,-0.2l0.09,-0.87ZM183.29,150.37l0.39,1.54l0.12,0.17l0.78,0.55l0.14,0.05l1.74,0.2l2.52,0.5l2.4,0.98l0.17,0.02l1.96,-0.4l3.01,0.81l0.91,-0.02l2.22,-0.88l4.67,2.33l3.86,2.01l0.21,0.71l0.15,0.18l0.33,0.17l-0.02,0.05l0.23,0.43l0.67,0.1l0.21,-0.05l0.1,-0.07l0.05,0.29l0.09,0.16l0.5,0.5l0.21,0.09l0.56,0.0l0.13,0.13l-0.2,0.36l0.12,0.41l2.49,1.39l0.99,5.24l-0.69,1.68l-1.16,1.64l-0.6,1.18l-0.06,0.31l0.04,0.22l0.28,0.43l0.11,0.1l0.85,0.47l0.15,0.04l0.63,0.0l0.14,-0.04l2.87,-1.58l2.6,-0.49l3.28,-1.5l0.17,-0.23l0.04,-0.43l-0.23,-0.93l-0.24,-0.39l0.74,-0.32l4.7,-0.01l0.25,-0.13l0.77,-1.15l2.9,-2.41l1.04,-0.52l8.35,-0.02l0.28,-0.21l0.2,-0.6l0.7,-0.14l1.06,-0.48l0.13,-0.11l0.92,-1.49l0.75,-2.39l1.67,-2.08l0.59,0.6l0.3,0.07l1.52,-0.49l0.88,0.72l-0.0,4.14l0.08,0.2l1.6,1.72l0.31,0.72l-2.42,1.35l-2.55,1.05l-2.64,0.9l-0.14,0.11l-1.33,1.81l-0.44,0.7l-0.05,0.15l-0.03,1.6l0.03,0.14l0.83,1.59l0.24,0.16l0.78,0.06l-1.15,0.33l-1.25,-0.04l-1.83,0.52l-2.51,0.29l-2.17,0.88l-0.17,0.36l0.33,0.22l3.55,-0.54l0.15,0.11l-2.87,0.73l-1.19,0.0l-0.16,-0.33l-0.36,0.06l-0.76,0.82l0.17,0.5l0.42,0.08l-0.45,1.75l-1.4,1.74l-0.04,-0.17l-0.21,-0.22l-0.48,-0.13l-0.77,-0.69l-0.36,-0.03l-0.12,0.34l0.52,1.58l0.09,0.14l0.52,0.43l0.03,0.87l-0.74,1.05l-0.39,0.63l0.05,-0.12l-0.08,-0.34l-1.19,-1.03l-0.28,-2.31l-0.26,-0.26l-0.32,0.19l-0.48,1.27l-0.01,0.19l0.39,1.33l-1.14,-0.31l-0.36,0.18l0.14,0.38l1.57,0.85l0.1,2.58l0.22,0.28l0.55,0.15l0.21,0.81l0.33,2.72l-1.46,1.94l-2.5,0.81l-0.12,0.07l-1.58,1.58l-1.15,0.17l-0.15,0.06l-1.27,1.03l-0.09,0.13l-0.32,0.85l-2.71,1.79l-1.45,1.37l-1.18,1.64l-0.05,0.12l-0.39,1.96l0.0,0.13l0.44,1.91l0.85,2.37l1.1,1.91l0.03,1.2l1.16,3.07l-0.08,1.74l-0.1,0.99l-0.57,1.48l-0.54,0.24l-0.97,-0.26l-0.34,-1.02l-0.12,-0.16l-0.89,-0.58l-2.44,-4.28l-0.34,-0.94l0.49,-1.71l-0.02,-0.21l-0.7,-1.5l-2.0,-2.35l-0.11,-0.08l-0.98,-0.42l-0.25,0.01l-2.42,1.19l-0.26,-0.08l-1.26,-1.29l-1.57,-0.68l-0.16,-0.02l-2.79,0.34l-2.18,-0.3l-1.98,0.19l-1.12,0.45l-0.14,0.44l0.4,0.65l-0.04,1.02l0.09,0.22l0.29,0.3l-0.06,0.05l-0.77,-0.33l-0.26,0.01l-0.87,0.48l-1.64,-0.08l-1.79,-1.39l-0.23,-0.06l-2.11,0.33l-1.75,-0.61l-0.14,-0.01l-1.61,0.2l-2.11,0.64l-0.11,0.06l-2.25,1.99l-2.53,1.21l-1.43,1.38l-0.58,1.22l-0.03,0.12l-0.03,1.86l0.13,1.32l0.3,0.62l-0.46,0.04l-1.71,-0.57l-1.85,-0.79l-0.63,-1.14l-0.54,-1.85l-0.07,-0.12l-1.45,-1.51l-0.86,-1.58l-1.26,-1.87l-0.09,-0.09l-1.76,-1.09l-0.17,-0.04l-2.05,0.05l-0.23,0.12l-1.44,1.97l-1.84,-0.72l-1.19,-0.76l-0.6,-1.45l-0.9,-1.52l-1.49,-1.21l-1.27,-0.87l-0.89,-0.96l-0.22,-0.1l-4.34,-0.0l-0.3,0.3l-0.0,0.84l-6.62,0.02l-5.66,-1.93l-3.48,-1.24l0.11,-0.25l-0.3,-0.42l-3.18,0.3l-2.6,0.2l-0.35,-1.19l-0.08,-0.13l-1.62,-1.61l-0.13,-0.08l-1.02,-0.29l-0.22,-0.66l-0.25,-0.2l-1.31,-0.13l-0.82,-0.7l-0.16,-0.07l-2.25,-0.27l-0.48,-0.34l-0.28,-1.44l-0.07,-0.14l-2.41,-2.84l-2.03,-3.89l0.08,-0.58l-0.1,-0.27l-1.08,-0.94l-1.87,-2.36l-0.33,-2.31l-0.07,-0.15l-1.24,-1.5l0.52,-2.4l-0.09,-2.57l-0.78,-2.3l0.96,-2.83l0.61,-5.66l-0.46,-4.26l-0.79,-2.71l-0.68,-1.4l0.13,-0.26l3.24,0.97l1.28,2.88l0.52,0.06l0.62,-0.84l0.06,-0.22l-0.4,-2.61l-0.74,-2.29l68.9,-0.0l0.3,-0.3l0.01,-0.95l0.32,-0.01ZM32.5,67.43l1.75,1.99l0.41,0.04l1.02,-0.81l3.79,0.25l-0.1,0.72l0.24,0.34l3.83,0.77l2.6,-0.44l5.21,1.41l4.84,0.43l1.9,0.57l0.15,0.01l3.25,-0.71l3.72,1.32l2.52,0.58l-0.03,38.14l0.29,0.3l2.41,0.11l2.34,1.0l1.7,1.59l2.22,2.42l0.42,0.03l2.41,-2.04l2.25,-1.08l1.23,1.76l1.71,1.53l2.24,1.62l1.54,2.56l2.56,4.09l0.11,0.11l4.1,2.17l0.06,1.93l-1.12,1.35l-1.22,-1.14l-2.08,-1.05l-0.68,-2.94l-0.09,-0.16l-3.18,-2.84l-1.32,-3.35l-0.25,-0.19l-2.43,-0.24l-3.93,-0.09l-2.85,-1.02l-5.24,-3.85l-6.77,-2.04l-3.52,0.3l-4.84,-1.7l-2.96,-1.6l-0.23,-0.02l-2.78,0.8l-0.21,0.35l0.46,2.31l-1.11,0.19l-2.9,0.78l-2.24,1.26l-2.42,0.68l-0.29,-1.79l1.07,-3.49l2.54,-1.11l0.12,-0.45l-0.69,-0.96l-0.41,-0.07l-3.19,2.12l-1.76,2.54l-3.57,2.62l-0.03,0.46l1.63,1.59l-2.14,2.38l-2.64,1.49l-2.49,1.09l-0.16,0.17l-0.58,1.48l-3.8,1.79l-0.14,0.14l-0.75,1.57l-2.75,1.41l-1.62,-0.25l-0.16,0.02l-2.35,0.98l-2.54,1.19l-2.06,1.15l-4.05,0.93l-0.1,-0.15l2.45,-1.45l2.49,-1.1l2.61,-1.88l3.03,-0.39l0.19,-0.1l1.2,-1.41l3.43,-2.11l0.61,-0.75l1.81,-1.24l0.13,-0.2l0.42,-2.7l1.24,-2.12l-0.03,-0.35l-0.34,-0.09l-2.73,1.05l-0.67,-0.53l-0.39,0.02l-1.13,1.11l-1.43,-1.62l-0.49,0.06l-0.41,0.8l-0.67,-1.31l-0.42,-0.12l-2.43,1.43l-1.18,-0.0l-0.18,-1.86l0.43,-1.3l-0.09,-0.33l-1.61,-1.33l-0.26,-0.06l-3.11,0.68l-2.0,-1.66l-1.61,-0.85l-0.01,-1.97l-0.11,-0.23l-1.76,-1.48l0.86,-1.96l2.01,-2.13l0.88,-1.94l1.79,-0.25l1.65,0.6l0.31,-0.06l1.91,-1.8l1.67,0.31l0.22,-0.04l1.91,-1.23l0.13,-0.33l-0.47,-1.82l-0.15,-0.19l-1.0,-0.52l1.51,-1.27l0.09,-0.34l-0.29,-0.19l-1.62,0.06l-2.66,0.88l-0.13,0.09l-0.62,0.72l-1.77,-0.8l-0.16,-0.02l-3.48,0.44l-3.5,-0.92l-1.06,-1.61l-2.78,-2.09l3.07,-1.51l5.52,-2.01l1.65,0.0l-0.28,1.73l0.31,0.35l5.29,-0.16l0.23,-0.49l-2.03,-2.59l-0.1,-0.08l-3.03,-1.58l-1.79,-2.12l-2.4,-1.83l-3.18,-1.27l1.13,-1.84l4.28,-0.14l0.15,-0.05l3.16,-2.0l0.13,-0.17l0.57,-2.07l2.43,-2.02l2.42,-0.52l4.67,-1.98l2.22,0.29l0.2,-0.04l3.74,-2.37l3.57,0.91ZM37.66,123.49l-2.31,1.26l-1.04,-0.75l-0.31,-1.35l2.06,-1.16l1.24,-0.51l1.48,0.22l0.76,0.81l-1.89,1.49ZM30.89,233.84l1.2,0.57l0.35,0.3l0.48,0.69l-1.6,0.86l-0.3,0.31l-0.24,-0.14l0.05,-0.54l-0.02,-0.15l-0.36,-0.83l0.05,-0.12l0.39,-0.38l0.07,-0.31l-0.09,-0.27ZM29.06,231.89l0.5,0.14l0.31,0.19l-0.46,0.1l-0.34,-0.43ZM25.02,230.13l0.2,-0.11l0.4,0.47l-0.43,-0.05l-0.17,-0.31ZM21.29,228.68l0.1,-0.07l0.22,0.02l0.02,0.21l-0.02,0.02l-0.32,-0.18ZM6.0,113.33l-1.19,0.45l-1.5,-0.64l-0.94,-0.63l1.76,-0.46l1.71,0.29l0.16,0.98Z", "name": "United States of America"}, "LV": {"path": "M473.99,127.16l0.07,-2.15l1.15,-2.11l2.05,-1.07l1.84,2.48l0.25,0.12l2.01,-0.07l0.29,-0.25l0.45,-2.58l1.85,-0.56l0.98,0.4l2.13,1.33l0.16,0.05l1.97,0.01l1.02,0.7l0.21,1.67l0.71,1.84l-2.44,1.23l-1.36,0.53l-2.28,-1.62l-0.12,-0.05l-1.18,-0.2l-0.28,-0.6l-0.31,-0.17l-2.43,0.35l-4.17,-0.23l-0.12,0.02l-2.45,0.93Z", "name": "Latvia"}, "UY": {"path": "M276.9,363.17l1.3,-0.23l2.4,2.04l0.22,0.07l0.82,-0.07l2.48,1.7l1.93,1.5l1.28,1.67l-0.95,1.14l-0.04,0.31l0.63,1.45l-0.96,1.57l-2.65,1.47l-1.73,-0.53l-0.15,-0.01l-1.25,0.28l-2.22,-1.16l-0.16,-0.03l-1.56,0.08l-1.33,-1.36l0.17,-1.58l0.48,-0.55l0.07,-0.2l-0.02,-2.74l0.66,-2.8l0.57,-2.02Z", "name": "Uruguay"}, "LB": {"path": "M510.44,198.11l-0.48,0.03l-0.26,0.17l-0.15,0.32l-0.21,-0.0l0.72,-1.85l1.19,-1.9l0.74,0.09l0.27,0.73l-1.19,0.93l-0.09,0.13l-0.54,1.36Z", "name": "Lebanon"}, "LA": {"path": "M684.87,248.8l0.61,-0.86l0.05,-0.16l0.11,-2.17l-0.08,-0.22l-1.96,-2.16l-0.15,-2.44l-0.08,-0.18l-1.9,-2.1l-0.19,-0.1l-1.89,-0.18l-0.29,0.15l-0.42,0.76l-1.21,0.06l-0.67,-0.41l-0.31,-0.0l-2.2,1.29l-0.05,-1.77l0.61,-2.7l-0.27,-0.37l-1.44,-0.1l-0.12,-1.31l-0.12,-0.21l-0.87,-0.65l0.38,-0.68l1.76,-1.41l0.08,0.22l0.27,0.2l1.33,0.07l0.31,-0.34l-0.35,-2.75l0.85,-0.25l1.32,1.88l1.11,2.36l0.27,0.17l2.89,0.02l0.78,1.82l-1.32,0.56l-0.12,0.09l-0.72,0.93l0.1,0.45l2.93,1.52l3.62,5.27l1.88,1.78l0.58,1.67l-0.38,2.11l-1.87,-0.79l-0.37,0.11l-0.99,1.54l-1.51,-0.73Z", "name": "Laos"}, "TW": {"path": "M725.6,222.5l-1.5,4.22l-0.82,1.65l-1.01,-1.7l-0.26,-1.8l1.4,-2.48l1.8,-1.81l0.76,0.53l-0.38,1.39Z", "name": "Taiwan"}, "TT": {"path": "M266.35,259.46l0.41,-0.39l0.09,-0.23l-0.04,-0.75l1.14,-0.26l0.2,0.03l-0.07,1.37l-1.73,0.23Z", "name": "Trinidad and Tobago"}, "TR": {"path": "M513.25,175.38l3.63,1.17l0.14,0.01l2.88,-0.45l2.11,0.26l0.18,-0.03l2.9,-1.53l2.51,-0.13l2.25,1.37l0.36,0.88l-0.23,1.36l0.19,0.33l1.81,0.72l0.61,0.53l-1.31,0.64l-0.16,0.34l0.76,3.24l-0.44,0.8l0.01,0.3l1.19,2.02l-0.71,0.29l-0.74,-0.62l-0.15,-0.07l-2.91,-0.37l-0.15,0.02l-1.04,0.43l-2.78,0.44l-1.44,-0.03l-2.83,1.06l-1.95,0.01l-1.28,-0.52l-0.2,-0.01l-2.62,0.76l-0.7,-0.48l-0.47,0.22l-0.13,1.49l-1.01,0.94l-0.58,-0.82l0.79,-0.9l0.04,-0.34l-0.31,-0.15l-1.46,0.23l-2.03,-0.64l-0.3,0.07l-1.65,1.58l-3.58,0.3l-1.94,-1.47l-0.17,-0.06l-2.7,-0.1l-0.28,0.17l-0.51,1.06l-1.47,0.29l-2.32,-1.46l-0.17,-0.05l-2.55,0.05l-1.4,-2.7l-1.72,-1.54l1.11,-2.06l-0.07,-0.37l-1.35,-1.19l2.47,-2.51l3.74,-0.11l0.26,-0.17l0.96,-2.07l4.56,0.38l0.19,-0.05l2.97,-1.92l2.84,-0.83l4.03,-0.06l4.31,2.08ZM488.85,176.8l-1.81,1.38l-0.57,-1.01l0.02,-0.36l0.45,-0.25l0.13,-0.15l0.78,-1.87l-0.11,-0.37l-0.72,-0.47l1.91,-0.71l1.89,0.35l0.25,0.97l0.17,0.2l1.87,0.83l-0.19,0.31l-2.82,0.16l-0.18,0.07l-1.06,0.91Z", "name": "Turkey"}, "LK": {"path": "M625.44,266.07l-0.35,2.4l-0.9,0.61l-1.91,0.5l-1.04,-1.75l-0.43,-3.5l1.0,-3.6l1.34,1.09l1.13,1.72l1.16,2.52Z", "name": "Sri Lanka"}, "TN": {"path": "M444.91,206.18l-0.99,-4.57l-0.12,-0.18l-1.43,-1.04l-0.02,-0.53l-0.11,-0.22l-1.95,-1.59l-0.19,-1.85l1.44,-1.47l0.08,-0.14l0.59,-2.34l-0.38,-2.77l0.44,-1.28l2.52,-1.08l1.41,0.28l-0.06,1.2l0.43,0.28l1.81,-0.9l0.02,0.06l-1.14,1.28l-0.08,0.2l-0.02,1.32l0.11,0.24l0.74,0.6l-0.29,2.18l-1.56,1.35l-0.09,0.32l0.48,1.54l0.28,0.21l1.11,0.04l0.55,1.17l0.15,0.14l0.76,0.35l-0.12,1.79l-1.1,0.72l-0.8,0.91l-1.68,1.04l-0.13,0.32l0.25,1.08l-0.18,0.96l-0.74,0.39Z", "name": "Tunisia"}, "TL": {"path": "M734.21,307.22l0.17,-0.34l1.99,-0.52l1.72,-0.08l0.78,-0.3l0.29,0.1l-0.43,0.32l-2.57,1.09l-1.71,0.59l-0.05,-0.49l-0.19,-0.36Z", "name": "East Timor"}, "TM": {"path": "M553.16,173.51l-0.12,1.0l-0.26,-0.65l0.38,-0.34ZM553.54,173.16l0.13,-0.12l0.43,-0.09l-0.56,0.21ZM555.68,172.6l0.65,-0.14l1.53,0.76l1.71,2.29l0.27,0.12l1.27,-0.14l2.81,-0.04l0.29,-0.38l-0.35,-1.27l1.98,-0.97l1.96,-1.63l3.05,1.44l0.25,2.23l0.14,0.22l0.96,0.61l0.18,0.05l2.61,-0.13l0.68,0.44l1.2,2.97l0.1,0.13l2.85,2.03l1.67,1.41l2.66,1.45l3.13,1.17l-0.05,1.23l-0.36,-0.04l-1.12,-0.73l-0.44,0.14l-0.34,0.89l-1.96,0.52l-0.22,0.23l-0.47,2.17l-1.26,0.78l-1.93,0.42l-0.21,0.18l-0.46,1.14l-1.64,0.33l-2.3,-0.97l-0.2,-2.23l-0.28,-0.27l-1.76,-0.1l-2.78,-2.48l-0.15,-0.07l-1.95,-0.31l-2.82,-1.48l-1.78,-0.27l-0.18,0.03l-1.03,0.51l-1.6,-0.08l-0.22,0.08l-1.72,1.6l-1.83,0.46l-0.39,-1.7l0.36,-3.0l-0.16,-0.3l-1.73,-0.88l0.57,-1.77l-0.25,-0.39l-1.33,-0.14l0.41,-1.85l2.05,0.63l0.21,-0.01l2.2,-0.95l0.09,-0.49l-1.78,-1.75l-0.69,-1.66l-0.07,-0.03Z", "name": "Turkmenistan"}, "TJ": {"path": "M597.99,178.71l-0.23,0.23l-2.57,-0.47l-0.35,0.25l-0.24,1.7l0.32,0.34l2.66,-0.22l3.15,0.95l4.47,-0.42l0.58,2.45l0.39,0.21l0.71,-0.25l1.22,0.53l-0.06,1.01l0.29,1.28l-2.19,-0.0l-1.71,-0.21l-0.23,0.07l-1.51,1.25l-1.05,0.27l-0.77,0.51l-0.71,-0.67l0.22,-2.28l-0.24,-0.32l-0.43,-0.08l0.17,-0.57l-0.16,-0.36l-1.36,-0.66l-0.34,0.05l-1.08,1.01l-0.09,0.15l-0.25,1.09l-0.24,0.26l-1.36,-0.05l-0.27,0.14l-0.65,1.06l-0.58,-0.39l-0.3,-0.02l-1.68,0.86l-0.36,-0.16l1.28,-2.65l0.02,-0.2l-0.54,-2.17l-0.18,-0.21l-1.53,-0.58l0.41,-0.82l1.89,0.13l0.26,-0.12l1.19,-1.63l0.77,-1.82l2.66,-0.55l-0.33,0.87l0.01,0.23l0.36,0.82l0.3,0.18l0.23,-0.02Z", "name": "Tajikistan"}, "LS": {"path": "M493.32,359.69l0.69,0.65l-0.65,1.12l-0.38,0.8l-1.27,0.39l-0.18,0.15l-0.4,0.77l-0.59,0.18l-1.59,-1.78l1.16,-1.5l1.3,-1.02l0.97,-0.46l0.94,0.72Z", "name": "Lesotho"}, "TH": {"path": "M677.42,253.68l-1.7,-0.88l-0.14,-0.03l-1.77,0.04l0.3,-1.64l-0.3,-0.35l-2.21,0.01l-0.3,0.28l-0.2,2.76l-2.15,5.9l-0.02,0.13l0.17,1.83l0.28,0.27l1.45,0.07l0.93,2.1l0.44,2.15l0.08,0.15l1.4,1.44l0.16,0.09l1.43,0.27l1.04,1.05l-0.58,0.73l-1.24,0.22l-0.15,-0.99l-0.15,-0.22l-2.04,-1.1l-0.36,0.06l-0.23,0.23l-0.72,-0.71l-0.41,-1.18l-0.06,-0.11l-1.33,-1.42l-1.22,-1.2l-0.5,0.13l-0.15,0.54l-0.14,-0.41l0.26,-1.48l0.73,-2.38l1.2,-2.57l1.37,-2.35l0.02,-0.27l-0.95,-2.26l0.03,-1.19l-0.29,-1.42l-0.06,-0.13l-1.65,-2.0l-0.46,-0.99l0.62,-0.34l0.13,-0.15l0.92,-2.23l-0.02,-0.27l-1.05,-1.74l-1.57,-1.86l-1.04,-1.96l0.76,-0.34l0.16,-0.16l1.07,-2.63l1.58,-0.1l0.16,-0.06l1.43,-1.11l1.24,-0.52l0.84,0.62l0.13,1.43l0.28,0.27l1.34,0.09l-0.54,2.39l0.05,2.39l0.45,0.25l2.48,-1.45l0.6,0.36l0.17,0.04l1.47,-0.07l0.25,-0.15l0.41,-0.73l1.58,0.15l1.76,1.93l0.15,2.44l0.08,0.18l1.94,2.15l-0.1,1.96l-0.66,0.93l-2.25,-0.34l-3.24,0.49l-0.19,0.12l-1.6,2.12l-0.06,0.24l0.48,2.46Z", "name": "Thailand"}, "TF": {"path": "M593.76,417.73l1.38,0.84l2.15,0.37l0.04,0.31l-0.59,1.24l-3.36,0.19l-0.05,-1.38l0.43,-1.56Z", "name": "French Southern and Antarctic Lands"}, "TG": {"path": "M425.23,269.29l-1.49,0.4l-0.43,-0.68l-0.64,-1.54l-0.18,-1.16l0.54,-2.21l-0.04,-0.24l-0.59,-0.86l-0.23,-1.9l0.0,-1.82l-0.07,-0.19l-0.95,-1.19l0.1,-0.41l1.58,0.04l-0.23,0.97l0.08,0.28l1.55,1.55l0.09,1.13l0.08,0.19l0.42,0.43l-0.11,5.66l0.52,1.53Z", "name": "Togo"}, "TD": {"path": "M457.57,252.46l0.23,-1.08l-0.28,-0.36l-1.32,-0.05l0.0,-1.35l-0.1,-0.22l-0.9,-0.82l0.99,-3.1l3.12,-2.37l0.12,-0.23l0.13,-3.33l0.95,-5.2l0.53,-1.09l-0.07,-0.36l-0.94,-0.81l-0.03,-0.7l-0.12,-0.23l-0.84,-0.61l-0.57,-3.76l2.21,-1.26l19.67,9.88l0.12,9.74l-1.83,-0.15l-0.28,0.14l-1.14,1.89l-0.68,1.62l0.05,0.31l0.33,0.38l-0.61,0.58l-0.08,0.3l0.25,0.93l-0.58,0.95l-0.29,1.01l0.34,0.37l0.67,-0.11l0.39,0.73l0.03,1.4l0.11,0.23l0.8,0.65l-0.01,0.24l-1.38,0.37l-0.11,0.06l-1.27,1.03l-1.83,2.76l-2.21,1.1l-2.34,-0.15l-0.82,0.25l-0.2,0.37l0.19,0.68l-1.16,0.79l-1.01,0.94l-2.92,0.89l-0.5,-0.46l-0.17,-0.08l-0.41,-0.05l-0.28,0.12l-0.38,0.54l-1.36,0.12l0.1,-0.18l0.01,-0.27l-0.78,-1.72l-0.35,-1.03l-0.17,-0.18l-1.03,-0.41l-1.29,-1.28l0.36,-0.78l0.9,0.2l0.14,-0.0l0.67,-0.17l1.36,0.02l0.26,-0.45l-1.32,-2.22l0.09,-1.64l-0.17,-1.68l-0.04,-0.13l-0.93,-1.53Z", "name": "Chad"}, "LY": {"path": "M457.99,226.38l-1.57,0.87l-1.25,-1.28l-0.13,-0.08l-3.85,-1.11l-1.04,-1.57l-0.09,-0.09l-1.98,-1.23l-0.27,-0.02l-0.93,0.39l-0.72,-1.2l-0.09,-1.07l-0.06,-0.16l-1.33,-1.75l0.83,-0.94l0.07,-0.24l-0.21,-1.64l0.31,-1.43l-0.17,-1.29l0.43,-2.26l-0.15,-1.33l-0.73,-2.18l0.99,-0.52l0.16,-0.21l0.22,-1.16l-0.22,-1.06l1.54,-0.95l0.81,-0.92l1.19,-0.78l0.14,-0.23l0.12,-1.76l2.57,0.84l0.16,0.01l0.99,-0.23l2.01,0.45l3.19,1.2l1.12,2.36l0.2,0.16l2.24,0.53l3.5,1.14l2.65,1.36l0.29,-0.01l1.22,-0.71l1.27,-1.32l0.07,-0.29l-0.55,-2.0l0.69,-1.19l1.7,-1.23l1.61,-0.35l3.2,0.54l0.78,1.14l0.24,0.13l0.85,0.01l0.84,0.47l2.35,0.31l0.42,0.63l-0.79,1.16l-0.04,0.26l0.35,1.08l-0.61,1.6l-0.0,0.2l0.73,2.16l0.0,24.24l-2.58,0.01l-0.3,0.29l-0.02,0.62l-19.55,-9.83l-0.28,0.01l-2.53,1.44Z", "name": "Libya"}, "AE": {"path": "M550.59,223.8l0.12,0.08l1.92,-0.41l3.54,0.15l0.23,-0.09l1.71,-1.79l1.86,-1.7l1.31,-1.36l0.26,0.5l0.28,1.72l-0.93,0.01l-0.3,0.26l-0.21,1.73l0.11,0.27l0.08,0.06l-0.7,0.32l-0.17,0.27l-0.01,0.99l-0.68,1.02l-0.05,0.15l-0.06,0.96l-0.32,0.36l-7.19,-1.27l-0.79,-2.22Z", "name": "United Arab Emirates"}, "VE": {"path": "M240.66,256.5l0.65,0.91l-0.03,1.13l-1.05,1.39l-0.03,0.31l0.95,2.0l0.32,0.17l1.08,-0.16l0.24,-0.21l0.56,-1.83l-0.06,-0.29l-0.71,-0.81l-0.1,-1.58l2.9,-0.96l0.19,-0.37l-0.29,-1.02l0.45,-0.41l0.72,1.43l0.26,0.16l1.65,0.04l1.46,1.27l0.08,0.72l0.3,0.27l2.28,0.02l2.55,-0.25l1.34,1.06l0.14,0.06l1.92,0.31l0.2,-0.03l1.4,-0.79l0.15,-0.25l0.02,-0.36l2.82,-0.14l1.17,-0.01l-0.41,0.14l-0.14,0.46l0.86,1.19l0.22,0.12l1.93,0.18l1.73,1.13l0.37,1.9l0.31,0.24l1.21,-0.05l0.52,0.32l-1.63,1.21l-0.11,0.17l-0.22,0.92l0.07,0.27l0.63,0.69l-0.31,0.24l-1.48,0.39l-0.22,0.3l0.04,1.03l-0.59,0.6l-0.01,0.41l1.67,1.87l0.23,0.48l-0.72,0.76l-2.71,0.91l-1.78,0.39l-0.13,0.06l-0.6,0.49l-1.84,-0.58l-1.89,-0.33l-0.18,0.03l-0.47,0.23l-0.02,0.53l0.96,0.56l-0.08,1.58l0.35,1.58l0.26,0.23l1.91,0.19l0.02,0.07l-1.54,0.62l-0.18,0.2l-0.25,0.92l-0.88,0.35l-1.85,0.58l-0.16,0.13l-0.4,0.64l-1.66,0.14l-1.22,-1.18l-0.79,-2.52l-0.67,-0.88l-0.66,-0.43l0.99,-0.98l0.09,-0.26l-0.09,-0.56l-0.08,-0.16l-0.66,-0.69l-0.47,-1.54l0.18,-1.67l0.55,-0.85l0.45,-1.35l-0.15,-0.36l-0.89,-0.43l-0.19,-0.02l-1.39,0.28l-1.76,-0.13l-0.92,0.23l-1.64,-2.01l-0.17,-0.1l-1.54,-0.33l-3.05,0.23l-0.5,-0.73l-0.15,-0.12l-0.45,-0.15l-0.05,-0.28l0.28,-0.86l0.01,-0.15l-0.2,-1.01l-0.08,-0.15l-0.5,-0.5l-0.3,-1.08l-0.25,-0.22l-0.89,-0.12l0.54,-1.18l0.29,-1.73l0.66,-0.85l0.94,-0.7l0.09,-0.11l0.3,-0.6Z", "name": "Venezuela"}, "AF": {"path": "M574.42,192.1l2.24,0.95l0.18,0.02l1.89,-0.38l0.22,-0.18l0.46,-1.14l1.82,-0.4l1.5,-0.91l0.14,-0.19l0.46,-2.12l1.93,-0.51l0.2,-0.18l0.26,-0.68l0.87,0.57l0.13,0.05l0.79,0.09l1.35,0.02l1.83,0.59l0.75,0.34l0.26,-0.01l1.66,-0.85l0.7,0.46l0.42,-0.09l0.72,-1.17l1.32,0.05l0.23,-0.1l0.39,-0.43l0.07,-0.14l0.24,-1.08l0.86,-0.81l0.94,0.46l-0.2,0.64l0.23,0.38l0.49,0.09l-0.21,2.15l0.09,0.25l0.99,0.94l0.38,0.03l0.83,-0.57l1.06,-0.27l0.12,-0.06l1.46,-1.21l1.63,0.2l2.4,0.0l0.17,0.32l-1.12,0.25l-1.23,0.52l-2.86,0.33l-2.69,0.6l-0.13,0.06l-1.46,1.25l-0.07,0.36l0.58,1.18l0.25,1.21l-1.13,1.08l-0.09,0.25l0.09,0.98l-0.53,0.79l-2.22,-0.08l-0.28,0.44l0.83,1.57l-1.3,0.58l-0.13,0.11l-1.06,1.69l-0.05,0.18l0.13,1.51l-0.73,0.58l-0.78,-0.22l-0.14,-0.01l-1.91,0.36l-0.23,0.19l-0.2,0.57l-1.65,-0.0l-0.22,0.1l-1.4,1.56l-0.08,0.19l-0.08,2.13l-2.99,1.05l-1.67,-0.23l-0.27,0.1l-0.39,0.46l-1.43,-0.31l-2.43,0.4l-3.69,-1.23l1.96,-2.15l0.08,-0.24l-0.21,-1.78l-0.23,-0.26l-1.69,-0.42l-0.19,-1.62l-0.77,-2.08l0.98,-1.41l-0.14,-0.45l-0.82,-0.31l0.6,-1.79l0.93,-3.21Z", "name": "Afghanistan"}, "IQ": {"path": "M534.42,190.89l0.13,0.14l1.5,0.78l0.15,1.34l-1.13,0.87l-0.11,0.16l-0.58,2.2l0.04,0.24l1.73,2.67l0.12,0.1l2.99,1.49l1.18,1.94l-0.39,1.89l0.29,0.36l0.5,-0.0l0.02,1.17l0.08,0.2l0.83,0.86l-2.36,-0.29l-0.29,0.13l-1.74,2.49l-4.4,-0.21l-7.03,-5.49l-3.73,-1.94l-2.92,-0.74l-0.89,-3.0l5.33,-2.81l0.15,-0.19l0.95,-3.43l-0.2,-2.0l1.19,-0.61l0.11,-0.09l1.23,-1.73l0.92,-0.38l2.75,0.35l0.81,0.68l0.31,0.05l0.94,-0.38l1.5,3.17Z", "name": "Iraq"}, "IS": {"path": "M384.26,87.96l-0.51,2.35l0.08,0.28l2.61,2.58l-2.99,2.83l-7.16,2.72l-2.08,0.7l-9.51,-1.71l1.89,-1.36l-0.07,-0.53l-4.4,-1.59l3.33,-0.59l0.25,-0.32l-0.11,-1.2l-0.25,-0.27l-4.82,-0.88l1.38,-2.2l3.54,-0.57l3.8,2.74l0.33,0.01l3.68,-2.18l3.02,1.12l0.25,-0.02l4.01,-2.18l3.72,0.27Z", "name": "Iceland"}, "IR": {"path": "M556.2,187.5l2.05,-0.52l0.13,-0.07l1.69,-1.57l1.55,0.08l0.15,-0.03l1.02,-0.5l1.64,0.25l2.82,1.48l1.91,0.3l2.8,2.49l0.18,0.08l1.61,0.09l0.19,2.09l-1.0,3.47l-0.69,2.04l0.18,0.38l0.73,0.28l-0.85,1.22l-0.04,0.28l0.81,2.19l0.19,1.72l0.23,0.26l1.69,0.42l0.17,1.43l-2.18,2.39l-0.01,0.4l1.22,1.42l1.0,1.62l0.12,0.11l2.23,1.11l0.06,2.2l0.2,0.27l1.03,0.38l0.14,0.83l-3.38,1.3l-0.18,0.19l-0.87,2.85l-4.44,-0.76l-2.75,-0.62l-2.64,-0.32l-1.01,-3.11l-0.17,-0.19l-1.2,-0.48l-0.18,-0.01l-1.99,0.51l-2.42,1.25l-2.89,-0.84l-2.48,-2.03l-2.41,-0.79l-1.61,-2.47l-1.84,-3.63l-0.36,-0.15l-1.22,0.4l-1.48,-0.84l-0.37,0.06l-0.72,0.82l-1.08,-1.12l-0.02,-1.35l-0.3,-0.29l-0.43,0.0l0.34,-1.64l-0.04,-0.22l-1.29,-2.11l-0.12,-0.11l-3.0,-1.49l-1.62,-2.49l0.52,-1.98l1.18,-0.92l0.11,-0.27l-0.19,-1.66l-0.16,-0.23l-1.55,-0.81l-1.58,-3.33l-1.3,-2.2l0.41,-0.75l0.03,-0.21l-0.73,-3.12l1.2,-0.59l0.35,0.9l1.26,1.35l0.15,0.09l1.81,0.39l0.91,-0.09l0.15,-0.06l2.9,-2.13l0.7,-0.16l0.48,0.56l-0.75,1.26l0.05,0.37l1.56,1.53l0.28,0.08l0.37,-0.09l0.7,1.89l0.21,0.19l2.31,0.59l1.69,1.4l0.15,0.07l3.66,0.49l3.91,-0.76l0.23,-0.19l0.19,-0.52Z", "name": "Iran"}, "AM": {"path": "M530.51,176.08l2.91,-0.39l0.41,0.63l0.11,0.1l0.66,0.36l-0.32,0.47l0.07,0.41l1.1,0.84l-0.53,0.7l0.06,0.42l1.06,0.8l1.01,0.44l0.04,1.56l-0.44,0.04l-0.88,-1.46l0.01,-0.37l-0.3,-0.31l-0.98,0.01l-0.65,-0.69l-0.26,-0.09l-0.38,0.06l-0.97,-0.82l-1.64,-0.65l0.2,-1.2l-0.02,-0.16l-0.28,-0.69Z", "name": "Armenia"}, "IT": {"path": "M451.68,158.58l0.2,0.16l3.3,0.75l-0.22,1.26l0.02,0.18l0.35,0.78l-1.4,-0.32l-0.21,0.03l-2.04,1.1l-0.16,0.29l0.13,1.47l-0.29,0.82l0.02,0.24l0.82,1.57l0.1,0.11l2.28,1.5l1.29,2.53l2.79,2.43l0.2,0.07l1.83,-0.02l0.31,0.34l-0.46,0.39l0.06,0.5l4.06,1.97l2.06,1.49l0.17,0.36l-0.24,0.53l-1.08,-1.07l-0.15,-0.08l-2.18,-0.49l-0.33,0.15l-1.05,1.91l0.11,0.4l1.63,0.98l-0.22,1.12l-0.84,0.14l-0.22,0.15l-1.27,2.38l-0.54,0.12l0.01,-0.47l0.48,-1.46l0.5,-0.58l0.03,-0.35l-0.97,-1.69l-0.76,-1.48l-0.17,-0.15l-0.94,-0.33l-0.68,-1.18l-0.16,-0.13l-1.53,-0.52l-1.03,-1.14l-0.19,-0.1l-1.78,-0.19l-1.88,-1.3l-2.27,-1.94l-1.64,-1.68l-0.76,-2.94l-0.21,-0.21l-1.22,-0.35l-2.01,-1.0l-0.24,-0.01l-1.15,0.42l-0.11,0.07l-1.38,1.36l-0.5,0.11l0.19,-0.87l-0.21,-0.35l-1.19,-0.34l-0.56,-2.06l0.76,-0.82l0.03,-0.36l-0.68,-1.08l0.04,-0.31l0.68,0.42l0.19,0.04l1.21,-0.15l0.14,-0.06l1.18,-0.89l0.25,0.29l0.25,0.1l1.19,-0.1l0.25,-0.18l0.45,-1.04l1.61,0.34l0.19,-0.02l1.1,-0.53l0.17,-0.22l0.15,-0.95l1.19,0.35l0.35,-0.16l0.23,-0.47l2.11,-0.47l0.45,0.89ZM459.35,184.63l-0.71,1.81l0.0,0.23l0.33,0.79l-0.37,1.03l-1.6,-0.91l-1.33,-0.34l-3.24,-1.36l0.23,-0.99l2.73,0.24l3.95,-0.5ZM443.95,175.91l1.26,1.77l-0.31,3.47l-0.82,-0.13l-0.26,0.08l-0.83,0.79l-0.64,-0.52l-0.1,-3.42l-0.44,-1.34l0.91,0.1l0.21,-0.06l1.01,-0.74Z", "name": "Italy"}, "VN": {"path": "M690.8,230.21l-2.86,1.93l-2.09,2.46l-0.06,0.11l-0.55,1.8l0.04,0.26l4.26,6.1l2.31,1.63l1.46,1.97l1.12,4.62l-0.32,4.3l-1.97,1.57l-2.85,1.62l-2.09,2.14l-2.83,2.13l-0.67,-1.19l0.65,-1.58l-0.09,-0.35l-1.47,-1.14l1.67,-0.79l2.57,-0.18l0.22,-0.47l-0.89,-1.24l3.88,-1.8l0.17,-0.24l0.31,-3.05l-0.01,-0.13l-0.56,-1.63l0.44,-2.48l-0.01,-0.15l-0.63,-1.81l-0.08,-0.12l-1.87,-1.77l-3.64,-5.3l-0.11,-0.1l-2.68,-1.39l0.45,-0.59l1.53,-0.65l0.16,-0.39l-0.97,-2.27l-0.27,-0.18l-2.89,-0.02l-1.04,-2.21l-1.28,-1.83l0.96,-0.46l1.97,0.01l2.43,-0.3l0.13,-0.05l1.95,-1.29l1.04,0.85l0.13,0.06l1.98,0.42l-0.32,1.21l0.09,0.3l1.19,1.07l0.12,0.07l1.88,0.51Z", "name": "Vietnam"}, "AR": {"path": "M258.11,341.34l1.4,1.81l0.51,-0.06l0.89,-1.94l2.51,0.1l0.36,0.49l4.6,4.31l0.15,0.08l1.99,0.39l3.01,1.93l2.5,1.01l0.28,0.91l-2.4,3.97l0.17,0.44l2.57,0.74l2.81,0.41l2.09,-0.44l0.14,-0.07l2.27,-2.06l0.09,-0.17l0.38,-2.2l0.88,-0.36l1.05,1.29l-0.04,1.88l-1.98,1.4l-1.72,1.13l-2.84,2.65l-3.34,3.73l-0.07,0.12l-0.63,2.22l-0.67,2.85l0.02,2.73l-0.47,0.54l-0.07,0.17l-0.36,3.28l0.12,0.27l3.03,2.32l-0.31,1.78l0.11,0.29l1.44,1.15l-0.11,1.17l-2.32,3.57l-3.59,1.51l-4.95,0.6l-2.72,-0.29l-0.32,0.38l0.5,1.67l-0.49,2.13l0.01,0.16l0.4,1.29l-1.27,0.88l-2.41,0.39l-2.33,-1.05l-0.31,0.04l-0.97,0.78l-0.11,0.27l0.35,2.98l0.16,0.23l1.69,0.91l0.31,-0.02l1.08,-0.75l0.46,0.96l-2.1,0.88l-2.01,1.89l-0.09,0.18l-0.36,3.05l-0.51,1.42l-2.16,0.01l-0.19,0.07l-1.96,1.59l-0.1,0.15l-0.72,2.34l0.08,0.31l2.46,2.31l0.13,0.07l2.09,0.56l-0.74,2.45l-2.86,1.75l-0.12,0.14l-1.59,3.71l-2.2,1.24l-0.1,0.09l-1.03,1.54l-0.04,0.23l0.81,3.45l0.06,0.13l1.13,1.32l-2.59,-0.57l-5.89,-0.44l-0.92,-1.73l0.05,-2.4l-0.34,-0.3l-1.49,0.19l-0.72,-0.98l-0.2,-3.21l1.79,-1.33l0.1,-0.13l0.79,-2.04l0.02,-0.16l-0.27,-1.52l1.31,-2.69l0.91,-4.15l-0.23,-1.72l0.91,-0.49l0.15,-0.33l-0.27,-1.16l-0.15,-0.2l-0.87,-0.46l0.65,-1.01l-0.04,-0.37l-1.06,-1.09l-0.54,-3.2l0.83,-0.51l0.14,-0.29l-0.42,-3.6l0.58,-2.98l0.64,-2.5l1.41,-1.0l0.12,-0.32l-0.75,-2.8l-0.01,-2.48l1.81,-1.78l0.09,-0.22l-0.06,-2.3l1.39,-2.69l0.03,-0.14l0.01,-2.58l-0.11,-0.24l-0.57,-0.45l-1.1,-4.59l1.49,-2.73l0.04,-0.17l-0.23,-2.59l0.86,-2.38l1.6,-2.48l1.74,-1.65l0.04,-0.39l-0.64,-0.89l0.42,-0.7l0.04,-0.16l-0.08,-4.26l2.55,-1.23l0.16,-0.18l0.86,-2.75l-0.01,-0.22l-0.22,-0.48l1.84,-2.1l3.0,0.59ZM256.77,438.98l-2.1,0.15l-1.18,-1.14l-0.19,-0.08l-1.53,-0.09l-2.38,-0.0l-0.0,-6.28l0.4,0.65l1.25,2.55l0.11,0.12l3.26,2.07l3.19,0.8l-0.82,1.26Z", "name": "Argentina"}, "AU": {"path": "M705.55,353.06l0.09,0.09l0.37,0.05l0.13,-0.35l-0.57,-1.69l0.48,0.3l0.71,0.99l0.34,0.11l0.2,-0.29l-0.04,-1.37l-0.04,-0.14l-1.22,-2.07l-0.28,-0.9l-0.51,-0.69l0.24,-1.33l0.52,-0.7l0.34,-1.32l0.01,-0.13l-0.25,-1.44l0.51,-0.94l0.1,1.03l0.23,0.26l0.32,-0.14l1.01,-1.72l1.94,-0.84l1.27,-1.14l1.84,-0.92l1.0,-0.18l0.6,0.28l0.26,-0.0l1.94,-0.96l1.48,-0.28l0.19,-0.13l0.32,-0.49l0.51,-0.18l1.42,0.05l2.63,-0.76l0.11,-0.06l1.36,-1.15l0.08,-0.1l0.61,-1.33l1.42,-1.27l0.1,-0.19l0.11,-1.03l0.06,-1.32l1.39,-1.74l0.85,1.79l0.4,0.14l1.07,-0.51l0.11,-0.45l-0.77,-1.05l0.53,-0.84l0.86,0.43l0.43,-0.22l0.29,-1.85l1.29,-1.19l0.6,-0.98l1.16,-0.4l0.2,-0.27l0.02,-0.34l0.74,0.2l0.38,-0.27l0.03,-0.44l1.98,-0.61l1.7,1.08l1.36,1.48l0.22,0.1l1.55,0.02l1.57,0.24l0.33,-0.4l-0.48,-1.27l1.09,-1.86l1.06,-0.63l0.1,-0.42l-0.28,-0.46l0.93,-1.24l1.36,-0.8l1.16,0.27l0.14,0.0l2.1,-0.48l0.23,-0.3l-0.05,-1.3l-0.18,-0.26l-1.08,-0.49l0.44,-0.12l1.52,0.58l1.39,1.06l2.11,0.65l0.19,-0.0l0.59,-0.21l1.44,0.72l0.27,0.0l1.37,-0.68l0.84,0.2l0.26,-0.06l0.37,-0.3l0.82,0.89l-0.56,1.14l-0.84,0.91l-0.75,0.07l-0.26,0.38l0.26,0.9l-0.67,1.15l-0.88,1.24l-0.05,0.25l0.18,0.72l0.12,0.17l1.99,1.42l1.96,0.84l1.25,0.86l1.8,1.51l0.19,0.07l0.63,-0.0l1.15,0.58l0.34,0.7l0.17,0.15l2.39,0.88l0.24,-0.02l1.65,-0.88l0.14,-0.16l0.49,-1.37l0.52,-1.19l0.31,-1.39l0.75,-2.02l0.01,-0.19l-0.33,-1.16l0.16,-0.67l0.0,-0.13l-0.28,-1.41l0.3,-1.78l0.42,-0.45l0.05,-0.33l-0.33,-0.73l0.56,-1.25l0.48,-1.39l0.07,-0.69l0.58,-0.59l0.48,0.84l0.17,1.53l0.17,0.24l0.47,0.23l0.09,0.9l0.05,0.14l0.87,1.23l0.17,1.33l-0.09,0.89l0.03,0.15l0.9,2.0l0.43,0.13l1.38,-0.83l0.71,0.92l1.06,0.88l-0.22,0.96l0.0,0.14l0.53,2.2l0.38,1.3l0.15,0.18l0.52,0.26l0.62,2.01l-0.23,1.27l0.02,0.18l0.81,1.76l0.14,0.14l2.69,1.35l3.21,2.21l-0.2,0.4l0.04,0.34l1.39,1.6l0.95,2.78l0.43,0.16l0.79,-0.46l0.85,0.96l0.39,0.05l0.22,-0.15l0.36,2.33l0.09,0.18l1.78,1.63l1.16,1.01l1.9,2.1l0.67,2.05l0.06,1.47l-0.17,1.64l0.03,0.17l1.16,2.22l-0.14,2.28l-0.43,1.24l-0.68,2.44l0.04,1.63l-0.48,1.92l-1.06,2.43l-1.79,1.32l-0.1,0.12l-0.91,2.15l-0.82,1.37l-0.76,2.47l-0.98,1.46l-0.63,2.14l-0.33,2.02l0.1,0.82l-1.21,0.85l-2.71,0.1l-0.13,0.03l-2.31,1.19l-1.21,1.17l-1.34,1.11l-1.89,-1.18l-1.33,-0.46l0.32,-1.24l-0.4,-0.35l-1.46,0.61l-2.06,1.98l-1.99,-0.73l-1.43,-0.46l-1.45,-0.22l-2.32,-0.81l-1.51,-1.67l-0.45,-2.11l-0.6,-1.5l-0.07,-0.11l-1.23,-1.16l-0.16,-0.08l-1.96,-0.28l0.59,-0.99l0.03,-0.24l-0.61,-2.1l-0.54,-0.08l-1.16,1.85l-1.23,0.29l0.73,-0.88l0.06,-0.12l0.37,-1.57l0.93,-1.33l0.05,-0.2l-0.2,-2.07l-0.53,-0.17l-2.01,2.35l-1.52,0.94l-0.12,0.14l-0.82,1.93l-1.5,-0.9l0.07,-1.32l-0.06,-0.2l-1.57,-2.04l-1.15,-0.92l0.3,-0.41l-0.1,-0.44l-3.21,-1.69l-0.13,-0.03l-1.69,-0.08l-2.35,-1.31l-0.16,-0.04l-4.55,0.27l-3.24,0.99l-2.8,0.91l-2.33,-0.18l-0.17,0.03l-2.63,1.41l-2.14,0.64l-0.2,0.19l-0.47,1.42l-0.8,0.99l-1.99,0.06l-1.55,0.24l-2.27,-0.5l-1.79,0.3l-1.71,0.13l-0.19,0.09l-1.38,1.39l-0.58,-0.1l-0.21,0.04l-1.26,0.8l-1.13,0.85l-1.72,-0.1l-1.6,-0.0l-2.58,-1.76l-1.21,-0.49l0.04,-1.19l1.04,-0.32l0.16,-0.12l0.42,-0.64l0.05,-0.19l-0.09,-0.97l0.3,-2.0l-0.28,-1.64l-1.34,-2.84l-0.39,-1.49l0.1,-1.51l-0.04,-0.17l-0.96,-1.72l-0.06,-0.73l-0.09,-0.19l-1.04,-1.01l-0.3,-2.02l-0.05,-0.12l-1.23,-1.83ZM784.95,393.35l2.39,1.01l0.2,0.01l3.26,-0.96l1.19,0.16l0.16,3.19l-0.78,0.95l-0.07,0.16l-0.19,1.83l-0.43,-0.41l-0.44,0.03l-1.61,1.96l-0.4,-0.12l-1.38,-0.09l-1.43,-2.42l-0.37,-2.03l-1.4,-2.53l0.04,-0.94l1.27,0.2Z", "name": "Australia"}, "IL": {"path": "M509.04,199.22l0.71,0.0l0.27,-0.17l0.15,-0.33l0.19,-0.01l0.02,0.73l-0.27,0.34l0.02,0.08l-0.32,0.62l-0.65,-0.27l-0.41,0.19l-0.52,1.85l0.16,0.35l0.14,0.07l-0.17,0.1l-0.14,0.21l-0.11,0.73l0.39,0.33l0.81,-0.26l0.03,0.64l-0.97,3.43l-1.28,-3.67l0.62,-0.78l-0.03,-0.41l0.58,-1.16l0.5,-2.07l0.27,-0.54Z", "name": "Israel"}, "IN": {"path": "M615.84,192.58l2.4,2.97l-0.24,2.17l0.05,0.2l0.94,1.35l-0.06,0.97l-1.46,-0.3l-0.35,0.36l0.7,3.06l0.12,0.18l2.46,1.75l3.11,1.72l-1.23,0.96l-0.1,0.13l-0.97,2.55l0.16,0.38l2.41,1.02l2.37,1.33l3.27,1.52l3.43,0.37l1.37,1.3l0.17,0.08l1.92,0.25l3.0,0.62l2.15,-0.04l0.28,-0.22l0.29,-1.06l0.0,-0.13l-0.32,-1.66l0.16,-0.94l1.0,-0.37l0.23,2.28l0.18,0.24l2.28,1.02l0.2,0.02l1.52,-0.41l2.06,0.18l2.08,-0.08l0.29,-0.27l0.18,-1.66l-0.1,-0.26l-0.53,-0.44l1.38,-0.23l0.15,-0.07l2.26,-2.0l2.75,-1.65l1.97,0.63l0.25,-0.03l1.54,-0.99l0.89,1.28l-0.72,0.97l0.2,0.48l2.49,0.37l0.11,0.61l-0.69,0.39l-0.15,0.3l0.15,1.22l-1.36,-0.37l-0.23,0.03l-3.24,1.86l-0.15,0.28l0.07,1.44l-1.33,2.16l-0.04,0.13l-0.12,1.24l-0.98,1.91l-1.72,-0.53l-0.39,0.28l-0.09,2.66l-0.52,0.83l-0.04,0.23l0.21,0.89l-0.71,0.36l-1.21,-3.85l-0.29,-0.21l-0.69,0.01l-0.29,0.23l-0.28,1.17l-0.84,-0.84l0.6,-1.17l0.97,-0.13l0.23,-0.16l1.15,-2.25l-0.18,-0.42l-1.54,-0.47l-2.3,0.04l-2.13,-0.33l-0.19,-1.63l-0.26,-0.26l-1.13,-0.13l-1.93,-1.13l-0.42,0.13l-0.88,1.82l0.08,0.37l1.47,1.15l-1.21,0.77l-0.1,0.1l-0.56,0.97l0.13,0.42l1.31,0.61l-0.36,1.35l0.01,0.2l0.85,1.95l0.37,2.05l-0.26,0.68l-1.55,-0.02l-3.09,0.54l-0.25,0.32l0.13,1.84l-1.21,1.4l-3.64,1.79l-2.79,3.04l-1.86,1.61l-2.48,1.68l-0.13,0.25l-0.0,1.0l-1.07,0.55l-2.21,0.9l-1.13,0.13l-0.25,0.19l-0.75,1.96l-0.02,0.15l0.52,3.31l0.13,2.03l-1.03,2.35l-0.03,0.12l-0.01,4.03l-1.02,0.1l-0.23,0.15l-1.14,1.93l0.04,0.36l0.44,0.48l-1.83,0.57l-0.18,0.15l-0.81,1.65l-0.74,0.53l-2.14,-2.12l-1.14,-3.47l-0.96,-2.57l-0.9,-1.26l-1.3,-2.38l-0.61,-3.14l-0.44,-1.62l-2.29,-3.56l-1.03,-4.94l-0.74,-3.29l0.01,-3.12l-0.49,-2.51l-0.41,-0.22l-3.56,1.53l-1.59,-0.28l-2.96,-2.87l0.94,-0.74l0.06,-0.41l-0.74,-1.03l-2.73,-2.1l1.35,-1.43l5.38,0.01l0.29,-0.36l-0.5,-2.29l-0.09,-0.15l-1.33,-1.28l-0.27,-1.96l-0.12,-0.2l-1.36,-1.0l2.42,-2.48l2.77,0.2l0.24,-0.1l2.62,-2.85l1.59,-2.8l2.41,-2.74l0.07,-0.2l-0.04,-1.82l2.01,-1.51l-0.01,-0.49l-1.95,-1.33l-0.83,-1.81l-0.82,-2.27l0.98,-0.97l3.64,0.66l2.89,-0.42l0.17,-0.08l2.18,-2.15Z", "name": "India"}, "TZ": {"path": "M505.77,287.58l0.36,0.23l8.95,5.03l0.15,1.3l0.13,0.21l3.4,2.37l-1.07,2.88l-0.02,0.14l0.15,1.42l0.15,0.23l1.47,0.84l0.05,0.42l-0.66,1.44l-0.02,0.18l0.13,0.72l-0.16,1.16l0.03,0.19l0.87,1.57l1.03,2.48l0.12,0.14l0.53,0.32l-1.59,1.18l-2.64,0.95l-1.45,-0.04l-0.2,0.07l-0.81,0.69l-1.64,0.06l-0.68,0.3l-2.9,-0.69l-1.71,0.17l-0.65,-3.18l-0.05,-0.12l-1.35,-1.88l-0.19,-0.12l-2.41,-0.46l-1.38,-0.74l-1.63,-0.44l-0.96,-0.41l-0.95,-0.58l-1.31,-3.09l-1.47,-1.46l-0.45,-1.31l0.24,-1.34l-0.39,-1.99l0.71,-0.08l0.18,-0.09l0.91,-0.91l0.98,-1.31l0.59,-0.5l0.11,-0.24l-0.02,-0.81l-0.08,-0.2l-0.47,-0.5l-0.1,-0.67l0.51,-0.23l0.18,-0.25l0.14,-1.47l-0.05,-0.2l-0.76,-1.09l0.45,-0.15l2.71,0.03l5.01,-0.19Z", "name": "Tanzania"}, "AZ": {"path": "M539.36,175.66l0.16,0.09l1.11,0.2l0.32,-0.15l0.4,-0.71l1.22,-0.99l1.11,1.33l1.26,2.09l0.22,0.14l1.06,0.13l0.28,0.29l-1.46,0.17l-0.26,0.24l-0.43,2.26l-0.39,0.92l-0.85,0.63l-0.12,0.25l0.06,1.2l-0.22,0.05l-1.28,-1.25l0.74,-1.25l-0.03,-0.35l-0.74,-0.86l-0.3,-0.1l-1.05,0.27l-2.49,1.82l-0.04,-1.46l-0.18,-0.27l-1.09,-0.47l-0.8,-0.6l0.53,-0.7l-0.06,-0.42l-1.11,-0.84l0.34,-0.51l-0.11,-0.43l-0.89,-0.48l-0.33,-0.49l0.25,-0.2l1.78,0.81l1.35,0.18l0.25,-0.09l0.34,-0.35l0.02,-0.39l-1.04,-1.36l0.28,-0.18l0.49,0.07l1.65,1.74ZM533.53,180.16l0.63,0.67l0.22,0.09l0.8,-0.0l0.04,0.31l0.66,1.09l-0.94,-0.21l-1.16,-1.24l-0.25,-0.71Z", "name": "Azerbaijan"}, "IE": {"path": "M405.17,135.35l0.36,2.16l-1.78,2.84l-4.28,1.91l-3.02,-0.43l1.81,-3.13l0.02,-0.26l-1.23,-3.26l3.24,-2.56l1.54,-1.32l0.37,1.33l-0.49,1.77l0.3,0.38l1.49,-0.05l1.68,0.63Z", "name": "Ireland"}, "ID": {"path": "M756.56,287.86l0.69,4.02l0.15,0.21l2.59,1.5l0.39,-0.07l2.05,-2.61l2.75,-1.45l2.09,-0.0l2.08,0.85l1.85,0.89l2.52,0.46l0.08,15.44l-1.72,-1.6l-0.15,-0.07l-2.54,-0.51l-0.29,0.1l-0.53,0.62l-2.53,0.06l0.78,-1.51l1.48,-0.66l0.17,-0.34l-0.65,-2.74l-1.23,-2.19l-0.14,-0.13l-4.85,-2.13l-2.09,-0.23l-3.7,-2.28l-0.41,0.1l-0.67,1.11l-0.63,0.14l-0.41,-0.67l-0.01,-1.01l-0.14,-0.25l-1.39,-0.89l2.05,-0.69l1.73,0.05l0.29,-0.39l-0.21,-0.66l-0.29,-0.21l-3.5,-0.0l-0.9,-1.36l-0.19,-0.13l-2.14,-0.44l-0.65,-0.76l2.86,-0.51l1.28,-0.79l3.75,0.96l0.32,0.76ZM758.01,300.37l-0.79,1.04l-0.14,-1.07l0.4,-0.81l0.29,-0.47l0.24,0.31l-0.0,1.0ZM747.45,292.9l0.48,1.02l-1.45,-0.69l-2.09,-0.21l-1.45,0.16l-1.28,-0.07l0.35,-0.81l2.86,-0.1l2.58,0.68ZM741.15,285.69l-0.16,-0.25l-0.72,-3.08l0.47,-1.86l0.35,-0.38l0.1,0.73l0.25,0.26l1.28,0.19l0.18,0.78l-0.11,1.8l-0.96,-0.18l-0.35,0.22l-0.38,1.52l0.05,0.24ZM741.19,285.75l0.76,0.97l-0.11,0.05l-0.65,-1.02ZM739.18,293.52l-0.61,0.54l-1.44,-0.38l-0.25,-0.55l1.93,-0.09l0.36,0.48ZM728.4,295.87l-0.27,-0.07l-2.26,0.89l-0.37,-0.41l0.27,-0.8l-0.09,-0.33l-1.68,-1.37l0.17,-2.29l-0.42,-0.3l-1.67,0.76l-0.17,0.29l0.21,2.92l0.09,3.34l-1.22,0.28l-0.78,-0.54l0.65,-2.1l0.01,-0.14l-0.39,-2.42l-0.29,-0.25l-0.86,-0.02l-0.63,-1.4l0.99,-1.61l0.35,-1.97l1.24,-3.73l0.49,-0.96l1.95,-1.7l1.86,0.69l3.16,0.35l2.92,-0.1l0.17,-0.06l2.24,-1.65l0.11,0.14l-1.8,2.22l-1.72,0.44l-2.41,-0.48l-4.21,0.13l-2.19,0.36l-0.25,0.24l-0.36,1.9l0.08,0.27l2.24,2.23l0.4,0.02l1.29,-1.08l3.19,-0.58l-0.19,0.06l-1.04,1.4l-2.13,0.94l-0.12,0.45l2.26,3.06l-0.37,0.69l0.03,0.32l1.51,1.95ZM728.48,295.97l0.59,0.76l-0.02,1.37l-1.0,0.55l-0.64,-0.58l1.09,-1.84l-0.02,-0.26ZM728.64,286.95l0.79,-0.14l-0.07,0.39l-0.72,-0.24ZM732.38,310.1l-1.89,0.49l-0.06,-0.06l0.17,-0.64l1.0,-1.42l2.14,-0.87l0.1,0.2l0.04,0.58l-1.49,1.72ZM728.26,305.71l-0.17,0.63l-3.53,0.67l-3.02,-0.28l-0.0,-0.42l1.66,-0.44l1.47,0.71l0.16,0.03l1.75,-0.21l1.69,-0.69ZM722.98,310.33l-0.74,0.03l-2.52,-1.35l1.42,-0.3l1.19,0.7l0.72,0.63l-0.06,0.28ZM716.24,305.63l0.66,0.49l0.22,0.06l1.35,-0.18l0.31,0.53l-4.18,0.77l-0.8,-0.01l0.51,-0.86l1.2,-0.02l0.24,-0.12l0.49,-0.65ZM715.84,280.21l0.09,0.34l2.25,1.86l-2.25,0.22l-0.24,0.17l-0.84,1.71l-0.03,0.15l0.1,2.11l-2.27,1.62l-0.13,0.24l-0.06,2.46l-0.74,2.92l-0.02,-0.05l-0.39,-0.16l-2.62,1.04l-0.86,-1.33l-0.23,-0.14l-1.71,-0.14l-1.19,-0.76l-0.25,-0.03l-2.78,0.84l-0.79,-1.05l-0.26,-0.12l-1.61,0.13l-1.8,-0.25l-0.36,-3.13l-0.15,-0.23l-1.18,-0.65l-1.13,-2.02l-0.33,-2.1l0.27,-2.19l1.05,-1.17l0.28,1.12l0.1,0.16l1.71,1.41l0.28,0.05l1.55,-0.49l1.54,0.17l0.23,-0.07l1.4,-1.21l1.05,-0.19l2.3,0.68l0.16,0.0l2.04,-0.53l0.21,-0.19l1.26,-3.41l0.91,-0.82l0.09,-0.14l0.8,-2.64l2.63,0.0l1.71,0.33l-1.19,1.89l0.02,0.34l1.74,2.24l-0.37,1.0ZM692.67,302.0l0.26,0.19l4.8,0.25l0.28,-0.16l0.44,-0.83l4.29,1.12l0.85,1.52l0.23,0.15l3.71,0.45l2.37,1.15l-2.06,0.69l-2.77,-1.0l-2.25,0.07l-2.57,-0.18l-2.31,-0.45l-2.94,-0.97l-1.84,-0.25l-0.13,0.01l-0.97,0.29l-4.34,-0.98l-0.38,-0.94l-0.25,-0.19l-1.76,-0.14l1.31,-1.84l2.81,0.14l1.97,0.96l0.95,0.19l0.28,0.74ZM685.63,299.27l-2.36,0.04l-2.07,-2.05l-3.17,-2.02l-1.06,-1.5l-1.88,-2.02l-1.22,-1.85l-1.9,-3.49l-2.2,-2.11l-0.71,-2.08l-0.94,-1.99l-0.1,-0.12l-2.21,-1.54l-1.35,-2.17l-1.86,-1.39l-2.53,-2.68l-0.14,-0.81l1.22,0.08l3.76,0.47l2.16,2.4l1.94,1.7l1.37,1.04l2.35,2.67l0.22,0.1l2.44,0.04l1.99,1.62l1.42,2.06l0.09,0.09l1.67,1.0l-0.88,1.8l0.11,0.39l1.44,0.87l0.13,0.04l0.68,0.05l0.41,1.62l0.87,1.4l0.22,0.14l1.71,0.21l1.06,1.38l-0.61,3.04l-0.09,3.6Z", "name": "Indonesia"}, "UA": {"path": "M500.54,141.42l0.9,0.13l0.27,-0.11l0.52,-0.62l0.68,0.13l2.43,-0.3l1.32,1.57l-0.45,0.48l-0.07,0.26l0.21,1.03l0.27,0.24l1.85,0.15l0.76,1.22l-0.05,0.55l0.2,0.31l3.18,1.15l0.18,0.01l1.75,-0.47l1.42,1.41l0.22,0.09l1.42,-0.03l3.44,0.99l0.02,0.65l-0.97,1.62l-0.03,0.24l0.52,1.67l-0.29,0.79l-2.24,0.22l-0.14,0.05l-1.29,0.89l-0.13,0.23l-0.07,1.16l-1.75,0.22l-0.12,0.04l-1.6,0.98l-2.27,0.16l-0.12,0.04l-2.16,1.17l-0.16,0.29l0.15,1.94l0.14,0.23l1.23,0.75l0.18,0.04l2.06,-0.15l-0.22,0.51l-2.67,0.54l-3.27,1.72l-1.0,-0.45l0.45,-1.19l-0.19,-0.39l-2.34,-0.78l0.15,-0.2l2.32,-1.0l0.09,-0.49l-0.73,-0.72l-0.15,-0.08l-3.69,-0.75l-0.14,-0.96l-0.35,-0.25l-2.32,0.39l-0.21,0.15l-0.91,1.7l-1.77,2.1l-0.93,-0.44l-0.24,-0.0l-1.05,0.45l-0.48,-0.25l0.13,-0.07l0.14,-0.15l0.43,-1.04l0.67,-0.97l0.04,-0.26l-0.1,-0.31l0.04,-0.02l0.11,0.19l0.24,0.15l1.48,0.09l0.78,-0.25l0.07,-0.53l-0.27,-0.19l0.09,-0.25l-0.08,-0.33l-0.81,-0.74l-0.34,-1.24l-0.14,-0.18l-0.73,-0.42l0.15,-0.87l-0.11,-0.29l-1.13,-0.86l-0.15,-0.06l-0.97,-0.11l-1.79,-0.97l-0.2,-0.03l-1.66,0.32l-0.13,0.06l-0.52,0.41l-0.95,-0.0l-0.23,0.11l-0.56,0.66l-1.74,0.29l-0.79,0.43l-1.01,-0.68l-0.16,-0.05l-1.57,-0.01l-1.52,-0.35l-0.23,0.04l-0.71,0.45l-0.09,-0.43l-0.13,-0.19l-1.18,-0.74l0.38,-1.02l0.53,-0.64l0.35,0.12l0.37,-0.41l-0.57,-1.29l2.1,-2.5l1.16,-0.36l0.2,-0.2l0.27,-0.92l-0.01,-0.2l-1.1,-2.52l0.79,-0.09l0.13,-0.05l1.3,-0.86l1.83,-0.07l2.48,0.26l2.84,0.8l1.91,0.06l0.88,0.45l0.29,-0.01l0.72,-0.44l0.49,0.58l0.25,0.11l2.2,-0.16l0.94,0.3l0.39,-0.26l0.15,-1.57l0.61,-0.59l2.01,-0.19Z", "name": "Ukraine"}, "QA": {"path": "M548.47,221.47l-0.15,-1.72l0.59,-1.23l0.38,-0.16l0.54,0.6l0.04,1.4l-0.47,1.37l-0.41,0.11l-0.53,-0.37Z", "name": "Qatar"}, "MZ": {"path": "M507.71,314.14l1.65,-0.18l2.96,0.7l0.2,-0.02l0.6,-0.29l1.68,-0.06l0.18,-0.07l0.8,-0.69l1.5,0.02l2.74,-0.98l1.74,-1.27l0.25,0.7l-0.1,2.47l0.31,2.27l0.1,3.97l0.42,1.24l-0.7,1.71l-0.94,1.73l-1.52,1.52l-5.06,2.21l-2.88,2.8l-1.01,0.51l-1.72,1.81l-0.99,0.58l-0.15,0.23l-0.21,1.86l0.04,0.19l1.17,1.95l0.47,1.47l0.03,0.74l0.39,0.28l0.05,-0.01l-0.06,2.13l-0.39,1.19l0.1,0.33l0.42,0.32l-0.28,0.83l-0.95,0.86l-2.03,0.88l-3.08,1.49l-1.1,0.99l-0.09,0.28l0.21,1.13l0.21,0.23l0.38,0.11l-0.14,0.89l-1.39,-0.02l-0.17,-0.94l-0.38,-1.23l-0.2,-0.89l0.44,-2.91l-0.01,-0.14l-0.65,-1.88l-1.15,-3.55l2.52,-2.85l0.68,-1.89l0.29,-0.18l0.14,-0.2l0.28,-1.53l-0.03,-0.19l-0.36,-0.7l0.1,-1.83l0.49,-1.84l-0.01,-3.26l-0.14,-0.25l-1.3,-0.83l-0.11,-0.04l-1.08,-0.17l-0.47,-0.55l-0.1,-0.08l-1.16,-0.54l-0.13,-0.03l-1.83,0.04l-0.32,-2.25l7.19,-1.99l1.32,1.12l0.29,0.06l0.55,-0.19l0.75,0.49l0.11,0.81l-0.49,1.11l-0.02,0.15l0.19,1.81l0.09,0.18l1.63,1.59l0.48,-0.1l0.72,-1.68l0.99,-0.49l0.17,-0.29l-0.21,-3.29l-0.04,-0.13l-1.11,-1.92l-0.9,-0.82l-0.21,-0.08l-0.62,0.03l-0.63,-2.98l0.61,-1.67Z", "name": "Mozambique"}}, "height": 440.7063107441331, "projection": {"type": "mill", "centralMeridian": 11.5}, "width": 900.0}); \ No newline at end of file diff --git a/resources/javascript/coursera/worthit-followup.png b/resources/javascript/coursera/worthit-followup.png deleted file mode 100644 index 9be83b9b11..0000000000 Binary files a/resources/javascript/coursera/worthit-followup.png and /dev/null differ diff --git a/resources/javascript/coursera/worthit.png b/resources/javascript/coursera/worthit.png deleted file mode 100644 index c15e3535f4..0000000000 Binary files a/resources/javascript/coursera/worthit.png and /dev/null differ diff --git a/resources/javascript/expandingImageMenu.js b/resources/javascript/expandingImageMenu.js deleted file mode 100644 index 8ebdac8b03..0000000000 --- a/resources/javascript/expandingImageMenu.js +++ /dev/null @@ -1,139 +0,0 @@ - $(function() { - var $menu = $('#ei_menu > ul'), - $menuItems = $menu.children('li'), - $menuItemsImgWrapper= $menuItems.children('a'), - $menuItemsPreview = $menuItemsImgWrapper.children('.ei_preview'), - totalMenuItems = $menuItems.length, - - ExpandingMenu = (function(){ - /* - @current - set it to the index of the element you want to be opened by default, - or -1 if you want the menu to be closed initially - */ - var current = 3, - /* - @anim - if we want the default opened item to animate initialy set this to true - */ - anim = true, - /* - checks if the current value is valid - - between 0 and the number of items - */ - validCurrent = function() { - return (current >= 0 && current < totalMenuItems); - }, - init = function() { - /* show default item if current is set to a valid index */ - if(validCurrent()) - configureMenu(); - - initEventsHandler(); - }, - configureMenu = function() { - /* get the item for the current */ - var $item = $menuItems.eq(current); - /* if anim is true slide out the item */ - if(anim) - slideOutItem($item, true, 900, 'easeInQuint'); - else{ - /* if not just show it */ - $item.css({width : '400px'}) - .find('.ei_image') - .css({left:'0px', opacity:1}); - - /* decrease the opacity of the others */ - $menuItems.not($item) - .children('.ei_preview') - .css({opacity:0.2}); - } - }, - initEventsHandler = function() { - /* - when we click an item the following can happen: - 1) The item is already opened - close it! - 2) The item is closed - open it! (if another one is opened, close it!) - */ - $menuItemsImgWrapper.bind('click.ExpandingMenu', function(e) { - var $this = $(this).parent(), - idx = $this.index(); - - if(current === idx) { - slideOutItem($menuItems.eq(current), false, 1500, 'easeOutQuint', true); - current = -1; - } - else{ - if(validCurrent() && current !== idx) - slideOutItem($menuItems.eq(current), false, 250, 'jswing'); - - current = idx; - slideOutItem($this, true, 250, 'jswing'); - } - return false; - }); - }, - /* if you want to trigger the action to open a specific item */ - openItem = function(idx) { - $menuItemsImgWrapper.eq(idx).click(); - }, - /* - opens or closes an item - note that "mLeave" is just true when all the items close, - in which case we want that all of them get opacity 1 again. - "dir" tells us if we are opening or closing an item (true | false) - */ - slideOutItem = function($item, dir, speed, easing, mLeave) { - var $ei_image = $item.find('.ei_image'), - - itemParam = (dir) ? {width : '400px'} : {width : '75px'}, - imageParam = (dir) ? {left : '0px'} : {left : '75px'}; - - /* - if opening, we animate the opacity of all the elements to 0.1. - this is to give focus on the opened item.. - */ - if(dir) - /* - alternative: - $menuItemsPreview.not($menuItemsPreview.eq(current)) - .stop() - .animate({opacity:0.1}, 500); - */ - $menuItemsPreview.stop() - .animate({opacity:0.1}, 1000); - else if(mLeave) - $menuItemsPreview.stop() - .animate({opacity:1}, 1500); - - /* the
      • expands or collapses */ - $item.stop().animate(itemParam, speed, easing); - /* the image (color) slides in or out */ - $ei_image.stop().animate(imageParam, speed, easing, function() { - /* - if opening, we animate the opacity to 1, - otherwise we reset it. - */ - if(dir) - $ei_image.animate({opacity:1}, 2000); - else - $ei_image.css('opacity', 0.2); - }); - }; - - return { - init : init, - openItem : openItem - }; - })(); - - /* - call the init method of ExpandingMenu - */ - ExpandingMenu.init(); - - /* - if later on you want to open / close a specific item you could do it like so: - ExpandingMenu.openItem(3); // toggles item 3 (zero-based indexing) - */ - }); \ No newline at end of file diff --git a/resources/javascript/frontpage.js b/resources/javascript/frontpage.js deleted file mode 100644 index e5780e994b..0000000000 --- a/resources/javascript/frontpage.js +++ /dev/null @@ -1,26 +0,0 @@ -$(document).ready(function() { - - // Accordion Demo #2 - $('#accordion2').accordionza({ - autoPlay: true, - autoRestartDelay: 4500, - onSlideClose: function() { - this.children('p').stop(true).animate({left: 470, opacity: 0}, 500); - }, - onSlideOpen: function() { - var properties = {left: 100, opacity: 1}; - var duration = 250; - var easing = 'easeOutBack'; - this.children('p').stop(true) - .filter(':eq(0)').animate({opacity: 0}, 000).animate(properties, duration, easing).end() - .filter(':eq(1)').animate({opacity: 0}, 000).animate(properties, duration, easing).end() - .filter(':eq(2)').animate({opacity: 0}, 000).animate(properties, duration, easing); - }, - slideDelay: 3000, - slideEasing: 'easeOutCirc', - slideSpeed: 250, - slideTrigger: 'mouseover', - slideWidthClosed: 60 - }); - -}); \ No newline at end of file diff --git a/resources/javascript/prettify/prettify.css b/resources/javascript/prettify/prettify.css deleted file mode 100644 index d44b3a2282..0000000000 --- a/resources/javascript/prettify/prettify.css +++ /dev/null @@ -1 +0,0 @@ -.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} \ No newline at end of file diff --git a/resources/javascript/bootstrap-dropdown-app.js b/resources/js/bootstrap-dropdown-app.js similarity index 100% rename from resources/javascript/bootstrap-dropdown-app.js rename to resources/js/bootstrap-dropdown-app.js diff --git a/resources/javascript/bootstrap-dropdown.js b/resources/js/bootstrap-dropdown.js similarity index 100% rename from resources/javascript/bootstrap-dropdown.js rename to resources/js/bootstrap-dropdown.js diff --git a/resources/javascript/bootstrap-popover.js b/resources/js/bootstrap-popover.js similarity index 100% rename from resources/javascript/bootstrap-popover.js rename to resources/js/bootstrap-popover.js diff --git a/resources/javascript/bootstrap-twipsy.js b/resources/js/bootstrap-twipsy.js similarity index 100% rename from resources/javascript/bootstrap-twipsy.js rename to resources/js/bootstrap-twipsy.js diff --git a/resources/js/functions.js b/resources/js/functions.js new file mode 100644 index 0000000000..307a48d418 --- /dev/null +++ b/resources/js/functions.js @@ -0,0 +1,508 @@ + +// Sliding Panel and scala in a nutshell +$(document).ready(function() { + $('.navigation-panel-button,.navigation-fade-screen,.navigation-panel-close').on('click touchstart', function(e) { + $('.navigation-menu,.navigation-fade-screen').toggleClass('is-visible'); + e.preventDefault(); + }); + + var menus = $('.items-menu'); + var allContents = $('.items-code'); + var allButtons = $('.scala-item'); + + menus.each(function(index1, row) { + var row = $(row); + var items = row.find('.scala-item'); + var content = row.children('.items-content'); + var contents = content.children('.items-code'); + + items.each(function(index2, button) { + var jButton = $(button); + jButton.click(function(event) { + var activeCode = contents.eq(index2); + var others = allContents.not(activeCode); + allButtons.removeClass('active'); + others.hide(); + + if (activeCode.is(":visible")) { + activeCode.hide(); + } else { + jButton.addClass('active') + activeCode.show(); + } + + }); + }); + }); +}); + +// Tooltip +$(document).ready(function() { + // Tooltip only Text + $('.masterTooltip').hover(function() { + // Hover over code + var title = $(this).attr('title'); + $(this).data('tipText', title).removeAttr('title'); + $('

        ') + .text(title) + .appendTo('body') + .fadeIn('slow'); + }, function() { + // Hover out code + $(this).attr('title', $(this).data('tipText')); + $('.tooltip').remove(); + }).mousemove(function(e) { + var mousex = e.pageX + 20; //Get X coordinates + var mousey = e.pageY + 10; //Get Y coordinates + $('.tooltip') + .css({ + top: mousey, + left: mousex + }) + }); +}); + +// Highlight +$(document).ready(function() { + hljs.configure({ + languages: ["scala", "bash"] + }) + hljs.initHighlighting(); +}); + +// Show Blog +$(".hide").click(function() { + $(".new-on-the-blog").hide(); +}); + +// Documentation menu dropdown toggle +$(document).ready(function() { // DOM ready + // If a link has a dropdown, add sub menu toggle. + $('nav ul li a:not(:only-child)').click(function(e) { + + // if mobile... + if ($(".navigation-ellipsis").css("display") == "block") { + + // toggle the submenu associated with the clicked id + var submenuId = $(this).attr('id'); + $(".doc-navigation-submenus #" + submenuId).toggle(); + + // Close one dropdown when selecting another + $('.navigation-submenu:not(#' + submenuId + ')').hide(); + + } else { // not mobile + + // toggle the dropdown associted with the clicked li element + $(this).siblings('.navigation-dropdown').toggle(); + + // Close one dropdown when selecting another + $('.navigation-dropdown').not($(this).siblings()).hide(); + } + e.stopPropagation(); + }); + // Clicking away from dropdown will remove the dropdown class + $('html').click(function() { + $('.navigation-dropdown').hide(); + $('.navigation-submenu:not(.ellipsis-menu)').hide(); + // $('.ellipsis-menu').hide(); + }); + + // expands doc menu on mobile + $('.navigation-ellipsis').click(function(e) { + $(".navigation-submenu.ellipsis-menu").toggle(); + }); +}); // end DOM ready + +// Expand button on cards (guides & overviews page) +$(document).ready(function() { + $.fn.hasOverflow = function() { + var $this = $(this); + var $children = $this.find('*'); + var len = $children.length; + + if (len) { + var maxWidth = 0; + var maxHeight = 0 + $children.map(function() { + maxWidth = Math.max(maxWidth, $(this).outerWidth(true)); + maxHeight = Math.max(maxHeight, $(this).outerHeight(true)); + }); + + return maxWidth > $this.width() || (maxHeight + 66) > $this.height(); + } + + return false; + }; + + $('.white-card').each(function(index) { + if (!$(this).hasOverflow()) { + $(this).find(".expand-btn").hide(); + $(this).find(".go-btn").show(); + } + }); + + $(".card-footer").click(function() { + // if we clicked on the expand button, expand the box + if ($(this).find(".expand-btn").is(':visible')) { + + // height mangling becasue flexbox align-self: self-end; doesn't work :( + $(this).parent().css('max-height', 'none'); + var cardWrapHeight = $(this).parent().find(".card-wrap").outerHeight(); + var cardFooterHeight = $(this).outerHeight() + $(this).parent().outerHeight(cardWrapHeight + cardFooterHeight); + + $(this).find(".expand-btn").hide(); + $(this).find(".go-btn").show(); + } else { + window.location.href = $(this).find(".go-btn").attr("href"); + } + }); +}); + + +// populate language dropdown +$(document).ready(function() { + var old = $("#available-languages"); + var items = $("#available-languages li"); + var newList = $("#dd .dropdown"); + items.each(function(index, value){ + newList.append(value); + }); + old.empty(); + + // if there are no translations, hide language dropdown box + if (items.length <= 1) { + $("#dd").hide(); + } +}); + + +//Tweet feed in frontpage +$('#tweet-feed').tweetMachine('', { + backendScript: '/webscripts/ajax/getFromTwitter.php', + endpoint: 'statuses/user_timeline', + user_name: 'scala_lang', + include_retweets: true, + exclude_replies: false, + limit: 6, + pageLimit: 3, + autoRefresh: false, + animateIn: false, + tweetFormat: ` +
        + +
        +
        +
          +
        • +
        • +
        + +
        +
        +
        +
        + ` +}, function(tweets, tweetsDisplayed) { + $('.slider-twitter').unslider({}); +}); + +// Scaladex autocomplete search +var prevResult = ""; +var lastElementClicked; + +$(document).mousedown(function(e) { + lastElementClicked = $(e.target); +}); + +$(document).mouseup(function(e) { + lastElementClicked = null; +}); + +function hideSuggestions() { + $('.autocomplete-suggestions').hide(); + $('.autocomplete-suggestion').hide(); +} + +function showSuggestions() { + $('.autocomplete-suggestions').show(); + $('.autocomplete-suggestion').show(); +} + +hideSuggestions(); +$('#scaladex-search').on('input', function(e) { + if ($("#scaladex-search").val() == "") hideSuggestions(); +}); + +$('#scaladex-search').on('focus', function(e) { + if ($("#scaladex-search").val() != "") showSuggestions(); +}); + +$('#scaladex-search').on('blur', function(e) { + if (!$(e.target).is('.autocomplete-suggestion')) { + if (lastElementClicked != null && !lastElementClicked.is('.autocomplete-suggestion')) { + hideSuggestions(); + } + } else { + hideSuggestions(); + } +}); + +$('#scaladex-search').autocomplete({ + paramName: 'q', + serviceUrl: 'https://index.scala-lang.org/api/autocomplete', + dataType: 'json', + beforeRender: function() { + showSuggestions(); + }, + onSearchStart: function(query) { + if (query == "") { + hideSuggestions() + } else { + showSuggestions(); + } + }, + transformResult: function(response) { + return { + suggestions: $.map(response, function(dataItem) { + return { + value: dataItem.repository, + data: 'https://scaladex.scala-lang.org/' + dataItem.organization + "/" + dataItem.repository + }; + }) + }; + }, + onSearchComplete: function(query, suggestions) { + suggestions.length > 0 ? showSuggestions() : hideSuggestions(); + }, + onSelect: function(suggestion) { + if (suggestion.data != prevResult) { + prevResult = suggestion.data; + hideSuggestions(); + $("#scaladex-search").blur(); + window.open(suggestion.data, '_blank'); + } + } + +}); + +$(document).ready(function() { + $(window).on("blur", function() { + if ($("#scaladex-search").length) { + $("#scaladex-search").blur(); + $("#scaladex-search").autocomplete().clear(); + } + }); +}); + +// TOC: +$(document).ready(function() { + if ($("#sidebar-toc").length) { + $('#toc').toc({ + exclude: 'h1, h5, h6', + context: '.inner-box', + autoId: true, + numerate: false + }); + toggleStickyToc(); + } +}) + +$(window).resize(function() { + toggleStickyToc(); +}); + +var toggleStickyToc = function() { + if ($("#sidebar-toc").length) { + if ($(window).width() <= 992) { + $(".sidebar-toc-wrapper").unstick(); + } else { + $(".sidebar-toc-wrapper").sticky({ + topSpacing: 0, + bottomSpacing: 500 + }); + } + } +} + +// Language dropdown +function DropDown(el) { + this.dd = el; + this.placeholder = this.dd.children('span'); + this.opts = this.dd.find('ul.dropdown > li'); + this.val = ''; + this.index = -1; + this.href = ''; + this.initEvents(); +} +DropDown.prototype = { + initEvents: function() { + var obj = this; + + obj.dd.on('click', function(event) { + $(this).toggleClass('active'); + return false; + }); + + obj.opts.on('click', function() { + var opt = $(this); + obj.val = opt.text(); + obj.index = opt.index(); + obj.placeholder.text(obj.val); + obj.href = opt.find('a').attr("href"); + window.location.href = obj.href; + }); + }, + getValue: function() { + return this.val; + }, + getIndex: function() { + return this.index; + } +} + +$(function() { + + var dd = new DropDown($('#dd')); + + $(document).click(function() { + // all dropdowns + $('.wrapper-dropdown').removeClass('active'); + }); + +}); + +// Blog search +$(document).ready(function() { + if ($("#blog-search-bar").length) { + SimpleJekyllSearch({ + searchInput: document.getElementById('blog-search-bar'), + resultsContainer: document.getElementById('result-container'), + json: '/resources/json/search.json', + searchResultTemplate: '
      • {title}
      • ', + limit: 5, + }); + + $("#blog-search-bar").on("change paste keyup", function() { + if ($(this).val()) { + $("#result-container").show(); + } else { + $("#result-container").hide(); + } + }); + } +}); + +// Scala in the browser +$(document).ready(function() { + if ($("#scastie-textarea").length) { + var editor = CodeMirror.fromTextArea(document.getElementById("scastie-textarea"), { + lineNumbers: true, + matchBrackets: true, + theme: "monokai", + mode: "text/x-scala", + autoRefresh: true, + fixedGutter: false + }); + editor.setSize("100%", ($("#scastie-code-container").height())); + + var codeSnippet = "List(\"Hello\", \"World\").mkString(\"\", \", \", \"!\")"; + editor.getDoc().setValue(codeSnippet); + editor.refresh(); + + $('.btn-run').click(function() { + // TODO: Code to connect to the scastie server would be here, what follows is just a simulation for the UI elements: + $('.btn-run').addClass("inactive"); + $('.btn-run i').removeClass("fa fa-play").addClass("fa fa-spinner fa-spin"); + setTimeout(function() { + var currentCodeSnippet = editor.getDoc().getValue(); + console.log("Current code snippet: " + currentCodeSnippet); + $('.btn-run').removeClass("inactive"); + $('.btn-run i').removeClass("fa-spinner fa-spin").addClass("fa fa-play"); + }, 2000); + }) + } +}); + +// OS detection +function getOS() { + var osname = "linux"; + if (navigator.appVersion.indexOf("Win") != -1) osname = "windows"; + if (navigator.appVersion.indexOf("Mac") != -1) osname = "osx"; + if (navigator.appVersion.indexOf("Linux") != -1) osname = "linux"; + if (navigator.appVersion.indexOf("X11") != -1) osname = "unix"; + return osname; +} + +$(document).ready(function() { + if ($(".main-download").length) { + var os = getOS(); + var intelliJlink = $("#intellij-" + os).text(); + var sbtLink = $("#sbt-" + os).text(); + var stepOneContent = $("#stepOne-" + os).html() + $("#download-intellij-link").attr("href", intelliJlink); + $("#download-sbt-link").attr("href", sbtLink); + $("#download-step-one").html(stepOneContent); + } +}); + +var image = { + width: 1680, + height: 1100 +}; +var target = { + x: 1028, + y: 290 +}; + +var pointer = $('#position-marker'); + +$(document).ready(updatePointer); +$(window).resize(updatePointer); + +function updatePointer() { + + var windowWidth = $(window).width(); + var windowHeight = $(window).height(); + + var xScale = windowWidth / image.width; + var yScale = windowHeight / image.height; + + pointer.css('top', (target.y)); + pointer.css('left', (target.x) * xScale); +} + + +// Glossary search +$(document).ready(function() { + +$('#filter').focus(); + + $("#filter").keyup(function(){ + + // Retrieve the input field text and reset the count to zero + var filter = $(this).val(), count = 0; + + // Loop through the comment list + $(".glossary > .inner-box > ul li").each(function(){ + // If the name of the glossary term does not contain the text phrase fade it out + if (jQuery(this).find("h4").text().search(new RegExp(filter, "i")) < 0) { + $(this).fadeOut(); + + // Show the list item if the phrase matches and increase the count by 1 + } else { + $(this).show(); + count++; + } + }); + + // Update the count + var numberItems = count; + $("#filter-count").text("Found "+count+" occurrences.").css('visibility', 'visible'); + + // check if input is empty, and if so, hide filter count + if (!filter.trim()) { + $("#filter-count").css('visibility', 'hidden'); + } + }); +}); diff --git a/resources/javascript/jquery.accordionza.js b/resources/js/jquery.accordionza.js similarity index 100% rename from resources/javascript/jquery.accordionza.js rename to resources/js/jquery.accordionza.js diff --git a/resources/javascript/jquery.easing.1.3.js b/resources/js/jquery.easing.1.3.js similarity index 100% rename from resources/javascript/jquery.easing.1.3.js rename to resources/js/jquery.easing.1.3.js diff --git a/resources/javascript/jquery.easing.js b/resources/js/jquery.easing.js similarity index 100% rename from resources/javascript/jquery.easing.js rename to resources/js/jquery.easing.js diff --git a/resources/js/tweetMachine-update.js b/resources/js/tweetMachine-update.js new file mode 100755 index 0000000000..a7604d2ba7 --- /dev/null +++ b/resources/js/tweetMachine-update.js @@ -0,0 +1,414 @@ +/* + * jQuery TweetMachine v0.2.1b + * GitHub: https://github.com/ryangiglio/jquery-tweetMachine + * Copyright (c) 2013 Ryan Giglio (@ryangiglio) + */ +(function ($) { + // Plugin body + $.fn.tweetMachine = function (query, options, callback) { + // For each instance of the plugin + $(this).each(function () { + var settings, tweetMachine; + // If this.tweetMachine is already initialized, just change the settings + if (this.tweetMachine) { + // Overwrite the initialized/default settings + settings = $.extend(this.tweetMachine.settings, options); + this.tweetMachine.settings = settings; + + // If a new query has been passed + if (query) { + // Replace the old query + this.tweetMachine.query = query; + } + // If a tweet interval is already set up + if (this.tweetMachine.interval) { + // Refresh now so the new settings can kick in + this.tweetMachine.refresh(); + } + // If a new callback was passed + if (callback) { + // Replace the old callback + this.tweetMachine.callback = callback; + } + } else { // It's not initialized, so let's do that + settings = $.extend({ + backendScript: '/webscripts/ajax/getFromTwitter.php', // Path to your backend script that holds your Twitter credentials and calls the API + endpoint: 'statuses/user_timeline', // Twitter API endpoint to call. Currently only search/tweets is supported + user_name: 'scala_lang', // Set your username + include_retweets: true, // Set to true or false if you want to include retweets + exclude_replies: false, // Set to true or false if you want to exclude replies + rate: 5000, // Rate in ms to refresh the tweets. Any higher than 5000 for search/tweets will get you rate limited + limit: 5, // Number of tweets to display at a time + autoRefresh: true, // CURRENTLY REQUIRED. Auto-refresh the tweets + animateOut: false, // NOT YET SUPPORTED. Animate out old tweets. + animateIn: true, // Fade in new tweets. + pageLimit: 0, // Number of tweets per page. If equals 0, tweets won't be paginated. + tweetFormat: "
      • ", // Format for each tweet + localization: { // Verbiage to use for timestamps + seconds: 'seconds ago', + minute: 'a minute ago', + minutes: 'minutes ago', + hour: 'an hour ago', + hours: 'hours ago', + day: 'a day ago', + days: 'days ago' + }, + filter: false // Function to filter tweet results. + }, options); + this.tweetMachine = { + settings: settings, // Set the settings object + query: query, // Set the query to search for + interval: false, // This will hold the refresh interval when it is created + container: this, // Set the object that contains the tweets + lastTweetID: null, // This will hold the ID of the last tweet displayed + callback: callback, // This callback will run after each refresh + + /* + * Function to generate a relative timestamp from Twitter's time string + */ + relativeTime: function (timeString) { + var delta, parsedDate, r; + + // Create a Date object + parsedDate = Date.parse(timeString); + + // Get the number of seconds ago that the tweet was created + delta = (Date.parse(Date()) - parsedDate) / 1000; + + // String to hold the relative time + r = ''; + + // If it was less than a minute ago + if (delta < 60) { + r = delta + " " + settings.localization.seconds; + // If it was less than 2 minutes ago + } else if (delta < 120) { + r = settings.localization.minute; + // If it was less than 45 minutes ago + } else if (delta < (45 * 60)) { + r = (parseInt(delta / 60, 10)).toString() + " " + settings.localization.minutes; + // If it was less than 90 minutes ago + } else if (delta < (90 * 60)) { + r = settings.localization.hour; + // If it was less than a day ago + } else if (delta < (24 * 60 * 60)) { + r = '' + (parseInt(delta / 3600, 10)).toString() + " " + settings.localization.hours; + // If it was less than 2 days ago + } else if (delta < (48 * 60 * 60)) { + r = settings.localization.day; + } else { + r = (parseInt(delta / 86400, 10)).toString() + " " + settings.localization.days; + } + return r; + }, + + /* + * Function to update the timestamps of each tweet + */ + updateTimestamps: function () { + var tweetMachine; + tweetMachine = this; + // Loop over each timestamp + $(tweetMachine.container).find('.time').each(function () { + var originalTime, timeElement; + + // Save a reference to the time element + timeElement = $(this); + + // Get the original time from the data stored on the timestamp + originalTime = timeElement.data('timestamp'); + + // Generate and show a new time based on the original time + timeElement.html(tweetMachine.relativeTime(originalTime)); + }); + }, + + /* + * Function to parse the text of a tweet and and add links to links, hashtags, and usernames + */ + parseText: function (text) { + // Links + text = text.replace(/[A-Za-z]+:\/\/[A-Za-z0-9-_]+\.[A-Za-z0-9-_:%&\?\/.=]+/g, function (m) { + return '' + m + ''; + }); + // Usernames + text = text.replace(/@[A-Za-z0-9_]+/g, function (u) { + return '' + u + ''; + }); + // Hashtags + text = text.replace(/#[A-Za-z0-9_\-]+/g, function (u) { + return '' + u + ''; + }); + return text; + }, + + /* + * Function to build the tweet as a jQuery object + */ + buildTweet: function (tweet) { + var tweetMachine, tweetObj; + tweetMachine = this; + + var actualTweet = /^RT/.test(tweet.text) ? tweet.retweeted_status : tweet; + + // Create the tweet from the tweetFormat setting + tweetObj = $(tweetMachine.settings.tweetFormat); + + // Set the avatar. NOTE: reasonably_small is Twitter's suffix for the largest square avatar that they store + tweetObj.find('.avatar') + .attr('src', actualTweet.user.profile_image_url_https.replace("normal", "reasonably_small")); + + // Set the user screen name + var usernameLink = "" + + "@" + + actualTweet.user.screen_name + + ""; + tweetObj.find('.username').html("" + usernameLink); + + // Set the username: + var userLink = "" + + actualTweet.user.name + + ""; + tweetObj.find('.user').html("" + userLink); + + // Set the timestamp + var dateLink = "" + + tweetMachine.relativeTime(actualTweet.created_at) + + ""; + tweetObj.find('.date') + .html("" + dateLink) + // Save the created_at time as jQuery data so we can update it later + .data('timestamp', actualTweet.created_at); + + // Set the text + tweetObj.find('.main-tweet') + .html("

        " + tweetMachine.parseText(actualTweet.text) + "

        "); + + // If we are animating in the new tweets + if (tweetMachine.settings.animateIn) { + // Set the opacity to 0 so it can fade in + tweetObj.css('opacity', '0'); + } + + return tweetObj; + }, + + /* + * Function to handle the reloading of tweets + */ + refresh: function (firstLoad) { + var queryParams, tweetMachine; + tweetMachine = this; + + // If it is the first load or we're refreshing automatically + if (firstLoad || tweetMachine.settings.autoRefresh) { + // Set the query parameters that the endpoint needs + + /* + * Twitter feed for search through tweets only + * API Reference: https://dev.twitter.com/docs/api/1.1/get/search/tweets + */ + if (tweetMachine.settings.endpoint === "search/tweets") { + queryParams = { + q: tweetMachine.query, + count: (this.settings.requestLimit) ? this.settings.requestLimit: this.settings.limit, + since_id: tweetMachine.lastTweetID + }; + } + + /* + * Twitter feed for username only + * API Reference: https://dev.twitter.com/docs/api/1.1/get/statuses/user_timeline + */ + if (tweetMachine.settings.endpoint === "statuses/user_timeline") { + queryParams = { + screen_name: settings.user_name, + count: (this.settings.requestLimit) ? this.settings.requestLimit: this.settings.limit, + include_rts: settings.include_retweets, + exclude_replies: settings.exclude_replies + }; + } + + // Call your backend script to get JSON back from Twitter + $.getJSON(tweetMachine.settings.backendScript, { + endpoint: tweetMachine.settings.endpoint, + queryParams: queryParams + }, function (tweets) { + var tweetsDisplayed; + var pagesDisplayed; + // If we got a response from Twitter + if ( tweets[0] ) { + // If there is an error message + if ( tweets[0].message ) { + // If there is already an error displayed + if ( $('.twitter-error').length ) { + // Update the error message + $('.twitter-error').html('

        Error ' + tweets[0].code + ': ' + tweets[0].message + '

        '); + } + else { // There isn't an error displayed yet + // Display an error message above the container + $(tweetMachine.container).before(''); + } + } + // There are tweets + else { + // If there was an error before + if ( $('.twitter-error').length ) { + // Remove it + $('.twitter-error').remove(); + } + + // Reverse them so they are added in the correct order + tweets.reverse(); + + // Count the number of tweets displayed + tweetsDisplayed = 0; + + // Count the pages: + pagesDisplayed = 0; + + // Loop through each tweet + $.each(tweets, function () { + var tweet, tweetObj; + tweet = this; + + // If there is no filter, or this tweet passes the filter + if (!tweetMachine.settings.filter || tweetMachine.settings.filter(this)) { + // Build the tweet as a jQuery object + tweetObj = tweetMachine.buildTweet(tweet); + + if (tweetMachine.settings.pageLimit > 0) { + tweetObj.addClass("page" + pagesDisplayed); + } + + //// If there are already tweets on the screen + //if (!firstLoad) { + // + // // If we are animating out the old tweets + // if (tweetMachine.settings.animateOut) { + // /* + // * TODO Support this feature + // */ + // } else { // We are not animating the old tweets + // // Remove them + // $(tweetMachine.container).children(':last-child').remove(); + // } + //} + + // Prepend the new tweet + $(tweetMachine.container).prepend(tweetObj); + + // If we are animating in the new tweets + //if (tweetMachine.settings.animateIn) { + // // Fade in the new tweet + // /* + // * TODO Figure out why .fadeIn() doesn't work + // */ + // $(tweetMachine.container).children(':first-child').animate({ + // opacity: 1 + // }); + //} + + // Increment the tweets diplayed + tweetsDisplayed++; + + // Increase page number and wrap tweets if pagination is enabled and we're finishing a page: + if (tweetMachine.settings.pageLimit > 0 && tweetsDisplayed % tweetMachine.settings.pageLimit == 0) { + $(".page" + pagesDisplayed).wrapAll("
      • "); + pagesDisplayed++; + } + + // Save this tweet ID so we only get newer noes + tweetMachine.lastTweetID = tweet.id_str; + + // If we've reached the limit of tweets to display + if (tweetsDisplayed > tweetMachine.settings.limit) { + // Quit the loop + return false; + } + } + }); + } + } + //Callback function + if (typeof tweetMachine.callback === "function") { + if(typeof tweets === 'undefined' || typeof tweetsDisplayed === 'undefined' ) { + tweets = null; + tweetsDisplayed = 0; + } + tweetMachine.callback(tweets, tweetsDisplayed); + } + }); + } + /* TODO: Implement an "x new Tweets, click to refresh" link if auto refresh is turned off + else { + } + */ + }, + + // Start refreshing + start: function () { + var tweetMachine; + tweetMachine = this; + + // If there's no interval yet + if (!this.interval) { + // Create an interval to refresh after the rate has passed + this.interval = setInterval(function () { + tweetMachine.refresh(); + }, tweetMachine.settings.rate); + // Start refreshing with the firstLoad flag = true + this.refresh(true); + } + }, + + // Stop refreshing + stop: function () { + var tweetMachine; + tweetMachine = this; + + // If there is an interval + if (tweetMachine.interval) { + // Clear it + clearInterval(tweetMachine.interval); + + // Remove the reference to it + tweetMachine.interval = false; + } + }, + + // Clear all tweets + clear: function () { + var tweetMachine; + tweetMachine = this; + + // Remove all tweets + $(tweetMachine.container).find('.tweet').remove(); + + // Set the lastTweetID to null so we start clean next time + tweetMachine.lastTweetID = null; + } + }; + + // Save a global tweetMachine object + tweetMachine = this.tweetMachine; + + // Create an interval to update the timestamps + this.timeInterval = setInterval(function () { + tweetMachine.updateTimestamps(); + }, tweetMachine.settings.rate); + + // Start the Machine! + this.tweetMachine.start(); + } + }); + }; +})(jQuery); \ No newline at end of file diff --git a/resources/js/vendor/.tweetMachine.js.kate-swp b/resources/js/vendor/.tweetMachine.js.kate-swp new file mode 100644 index 0000000000..4b47b11cdd Binary files /dev/null and b/resources/js/vendor/.tweetMachine.js.kate-swp differ diff --git a/resources/js/vendor/codemirror/clike.js b/resources/js/vendor/codemirror/clike.js new file mode 100644 index 0000000000..bba2ec9056 --- /dev/null +++ b/resources/js/vendor/codemirror/clike.js @@ -0,0 +1,788 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +function Context(indented, column, type, info, align, prev) { + this.indented = indented; + this.column = column; + this.type = type; + this.info = info; + this.align = align; + this.prev = prev; +} +function pushContext(state, col, type, info) { + var indent = state.indented; + if (state.context && state.context.type == "statement" && type != "statement") + indent = state.context.indented; + return state.context = new Context(indent, col, type, info, null, state.context); +} +function popContext(state) { + var t = state.context.type; + if (t == ")" || t == "]" || t == "}") + state.indented = state.context.indented; + return state.context = state.context.prev; +} + +function typeBefore(stream, state, pos) { + if (state.prevToken == "variable" || state.prevToken == "variable-3") return true; + if (/\S(?:[^- ]>|[*\]])\s*$|\*$/.test(stream.string.slice(0, pos))) return true; + if (state.typeAtEndOfLine && stream.column() == stream.indentation()) return true; +} + +function isTopScope(context) { + for (;;) { + if (!context || context.type == "top") return true; + if (context.type == "}" && context.prev.info != "namespace") return false; + context = context.prev; + } +} + +CodeMirror.defineMode("clike", function(config, parserConfig) { + var indentUnit = config.indentUnit, + statementIndentUnit = parserConfig.statementIndentUnit || indentUnit, + dontAlignCalls = parserConfig.dontAlignCalls, + keywords = parserConfig.keywords || {}, + types = parserConfig.types || {}, + builtin = parserConfig.builtin || {}, + blockKeywords = parserConfig.blockKeywords || {}, + defKeywords = parserConfig.defKeywords || {}, + atoms = parserConfig.atoms || {}, + hooks = parserConfig.hooks || {}, + multiLineStrings = parserConfig.multiLineStrings, + indentStatements = parserConfig.indentStatements !== false, + indentSwitch = parserConfig.indentSwitch !== false, + namespaceSeparator = parserConfig.namespaceSeparator, + isPunctuationChar = parserConfig.isPunctuationChar || /[\[\]{}\(\),;\:\.]/, + numberStart = parserConfig.numberStart || /[\d\.]/, + number = parserConfig.number || /^(?:0x[a-f\d]+|0b[01]+|(?:\d+\.?\d*|\.\d+)(?:e[-+]?\d+)?)(u|ll?|l|f)?/i, + isOperatorChar = parserConfig.isOperatorChar || /[+\-*&%=<>!?|\/]/; + + var curPunc, isDefKeyword; + + function tokenBase(stream, state) { + var ch = stream.next(); + if (hooks[ch]) { + var result = hooks[ch](stream, state); + if (result !== false) return result; + } + if (ch == '"' || ch == "'") { + state.tokenize = tokenString(ch); + return state.tokenize(stream, state); + } + if (isPunctuationChar.test(ch)) { + curPunc = ch; + return null; + } + if (numberStart.test(ch)) { + stream.backUp(1) + if (stream.match(number)) return "number" + stream.next() + } + if (ch == "/") { + if (stream.eat("*")) { + state.tokenize = tokenComment; + return tokenComment(stream, state); + } + if (stream.eat("/")) { + stream.skipToEnd(); + return "comment"; + } + } + if (isOperatorChar.test(ch)) { + while (!stream.match(/^\/[\/*]/, false) && stream.eat(isOperatorChar)) {} + return "operator"; + } + stream.eatWhile(/[\w\$_\xa1-\uffff]/); + if (namespaceSeparator) while (stream.match(namespaceSeparator)) + stream.eatWhile(/[\w\$_\xa1-\uffff]/); + + var cur = stream.current(); + if (contains(keywords, cur)) { + if (contains(blockKeywords, cur)) curPunc = "newstatement"; + if (contains(defKeywords, cur)) isDefKeyword = true; + return "keyword"; + } + if (contains(types, cur)) return "variable-3"; + if (contains(builtin, cur)) { + if (contains(blockKeywords, cur)) curPunc = "newstatement"; + return "builtin"; + } + if (contains(atoms, cur)) return "atom"; + return "variable"; + } + + function tokenString(quote) { + return function(stream, state) { + var escaped = false, next, end = false; + while ((next = stream.next()) != null) { + if (next == quote && !escaped) {end = true; break;} + escaped = !escaped && next == "\\"; + } + if (end || !(escaped || multiLineStrings)) + state.tokenize = null; + return "string"; + }; + } + + function tokenComment(stream, state) { + var maybeEnd = false, ch; + while (ch = stream.next()) { + if (ch == "/" && maybeEnd) { + state.tokenize = null; + break; + } + maybeEnd = (ch == "*"); + } + return "comment"; + } + + function maybeEOL(stream, state) { + if (parserConfig.typeFirstDefinitions && stream.eol() && isTopScope(state.context)) + state.typeAtEndOfLine = typeBefore(stream, state, stream.pos) + } + + // Interface + + return { + startState: function(basecolumn) { + return { + tokenize: null, + context: new Context((basecolumn || 0) - indentUnit, 0, "top", null, false), + indented: 0, + startOfLine: true, + prevToken: null + }; + }, + + token: function(stream, state) { + var ctx = state.context; + if (stream.sol()) { + if (ctx.align == null) ctx.align = false; + state.indented = stream.indentation(); + state.startOfLine = true; + } + if (stream.eatSpace()) { maybeEOL(stream, state); return null; } + curPunc = isDefKeyword = null; + var style = (state.tokenize || tokenBase)(stream, state); + if (style == "comment" || style == "meta") return style; + if (ctx.align == null) ctx.align = true; + + if (curPunc == ";" || curPunc == ":" || (curPunc == "," && stream.match(/^\s*(?:\/\/.*)?$/, false))) + while (state.context.type == "statement") popContext(state); + else if (curPunc == "{") pushContext(state, stream.column(), "}"); + else if (curPunc == "[") pushContext(state, stream.column(), "]"); + else if (curPunc == "(") pushContext(state, stream.column(), ")"); + else if (curPunc == "}") { + while (ctx.type == "statement") ctx = popContext(state); + if (ctx.type == "}") ctx = popContext(state); + while (ctx.type == "statement") ctx = popContext(state); + } + else if (curPunc == ctx.type) popContext(state); + else if (indentStatements && + (((ctx.type == "}" || ctx.type == "top") && curPunc != ";") || + (ctx.type == "statement" && curPunc == "newstatement"))) { + pushContext(state, stream.column(), "statement", stream.current()); + } + + if (style == "variable" && + ((state.prevToken == "def" || + (parserConfig.typeFirstDefinitions && typeBefore(stream, state, stream.start) && + isTopScope(state.context) && stream.match(/^\s*\(/, false))))) + style = "def"; + + if (hooks.token) { + var result = hooks.token(stream, state, style); + if (result !== undefined) style = result; + } + + if (style == "def" && parserConfig.styleDefs === false) style = "variable"; + + state.startOfLine = false; + state.prevToken = isDefKeyword ? "def" : style || curPunc; + maybeEOL(stream, state); + return style; + }, + + indent: function(state, textAfter) { + if (state.tokenize != tokenBase && state.tokenize != null || state.typeAtEndOfLine) return CodeMirror.Pass; + var ctx = state.context, firstChar = textAfter && textAfter.charAt(0); + if (ctx.type == "statement" && firstChar == "}") ctx = ctx.prev; + if (parserConfig.dontIndentStatements) + while (ctx.type == "statement" && parserConfig.dontIndentStatements.test(ctx.info)) + ctx = ctx.prev + if (hooks.indent) { + var hook = hooks.indent(state, ctx, textAfter); + if (typeof hook == "number") return hook + } + var closing = firstChar == ctx.type; + var switchBlock = ctx.prev && ctx.prev.info == "switch"; + if (parserConfig.allmanIndentation && /[{(]/.test(firstChar)) { + while (ctx.type != "top" && ctx.type != "}") ctx = ctx.prev + return ctx.indented + } + if (ctx.type == "statement") + return ctx.indented + (firstChar == "{" ? 0 : statementIndentUnit); + if (ctx.align && (!dontAlignCalls || ctx.type != ")")) + return ctx.column + (closing ? 0 : 1); + if (ctx.type == ")" && !closing) + return ctx.indented + statementIndentUnit; + + return ctx.indented + (closing ? 0 : indentUnit) + + (!closing && switchBlock && !/^(?:case|default)\b/.test(textAfter) ? indentUnit : 0); + }, + + electricInput: indentSwitch ? /^\s*(?:case .*?:|default:|\{\}?|\})$/ : /^\s*[{}]$/, + blockCommentStart: "/*", + blockCommentEnd: "*/", + lineComment: "//", + fold: "brace" + }; +}); + + function words(str) { + var obj = {}, words = str.split(" "); + for (var i = 0; i < words.length; ++i) obj[words[i]] = true; + return obj; + } + function contains(words, word) { + if (typeof words === "function") { + return words(word); + } else { + return words.propertyIsEnumerable(word); + } + } + var cKeywords = "auto if break case register continue return default do sizeof " + + "static else struct switch extern typedef union for goto while enum const volatile"; + var cTypes = "int long char short double float unsigned signed void size_t ptrdiff_t"; + + function cppHook(stream, state) { + if (!state.startOfLine) return false + for (var ch, next = null; ch = stream.peek();) { + if (ch == "\\" && stream.match(/^.$/)) { + next = cppHook + break + } else if (ch == "/" && stream.match(/^\/[\/\*]/, false)) { + break + } + stream.next() + } + state.tokenize = next + return "meta" + } + + function pointerHook(_stream, state) { + if (state.prevToken == "variable-3") return "variable-3"; + return false; + } + + function cpp14Literal(stream) { + stream.eatWhile(/[\w\.']/); + return "number"; + } + + function cpp11StringHook(stream, state) { + stream.backUp(1); + // Raw strings. + if (stream.match(/(R|u8R|uR|UR|LR)/)) { + var match = stream.match(/"([^\s\\()]{0,16})\(/); + if (!match) { + return false; + } + state.cpp11RawStringDelim = match[1]; + state.tokenize = tokenRawString; + return tokenRawString(stream, state); + } + // Unicode strings/chars. + if (stream.match(/(u8|u|U|L)/)) { + if (stream.match(/["']/, /* eat */ false)) { + return "string"; + } + return false; + } + // Ignore this hook. + stream.next(); + return false; + } + + function cppLooksLikeConstructor(word) { + var lastTwo = /(\w+)::(\w+)$/.exec(word); + return lastTwo && lastTwo[1] == lastTwo[2]; + } + + // C#-style strings where "" escapes a quote. + function tokenAtString(stream, state) { + var next; + while ((next = stream.next()) != null) { + if (next == '"' && !stream.eat('"')) { + state.tokenize = null; + break; + } + } + return "string"; + } + + // C++11 raw string literal is "( anything )", where + // can be a string up to 16 characters long. + function tokenRawString(stream, state) { + // Escape characters that have special regex meanings. + var delim = state.cpp11RawStringDelim.replace(/[^\w\s]/g, '\\$&'); + var match = stream.match(new RegExp(".*?\\)" + delim + '"')); + if (match) + state.tokenize = null; + else + stream.skipToEnd(); + return "string"; + } + + function def(mimes, mode) { + if (typeof mimes == "string") mimes = [mimes]; + var words = []; + function add(obj) { + if (obj) for (var prop in obj) if (obj.hasOwnProperty(prop)) + words.push(prop); + } + add(mode.keywords); + add(mode.types); + add(mode.builtin); + add(mode.atoms); + if (words.length) { + mode.helperType = mimes[0]; + CodeMirror.registerHelper("hintWords", mimes[0], words); + } + + for (var i = 0; i < mimes.length; ++i) + CodeMirror.defineMIME(mimes[i], mode); + } + + def(["text/x-csrc", "text/x-c", "text/x-chdr"], { + name: "clike", + keywords: words(cKeywords), + types: words(cTypes + " bool _Complex _Bool float_t double_t intptr_t intmax_t " + + "int8_t int16_t int32_t int64_t uintptr_t uintmax_t uint8_t uint16_t " + + "uint32_t uint64_t"), + blockKeywords: words("case do else for if switch while struct"), + defKeywords: words("struct"), + typeFirstDefinitions: true, + atoms: words("null true false"), + hooks: {"#": cppHook, "*": pointerHook}, + modeProps: {fold: ["brace", "include"]} + }); + + def(["text/x-c++src", "text/x-c++hdr"], { + name: "clike", + keywords: words(cKeywords + " asm dynamic_cast namespace reinterpret_cast try explicit new " + + "static_cast typeid catch operator template typename class friend private " + + "this using const_cast inline public throw virtual delete mutable protected " + + "alignas alignof constexpr decltype nullptr noexcept thread_local final " + + "static_assert override"), + types: words(cTypes + " bool wchar_t"), + blockKeywords: words("catch class do else finally for if struct switch try while"), + defKeywords: words("class namespace struct enum union"), + typeFirstDefinitions: true, + atoms: words("true false null"), + dontIndentStatements: /^template$/, + hooks: { + "#": cppHook, + "*": pointerHook, + "u": cpp11StringHook, + "U": cpp11StringHook, + "L": cpp11StringHook, + "R": cpp11StringHook, + "0": cpp14Literal, + "1": cpp14Literal, + "2": cpp14Literal, + "3": cpp14Literal, + "4": cpp14Literal, + "5": cpp14Literal, + "6": cpp14Literal, + "7": cpp14Literal, + "8": cpp14Literal, + "9": cpp14Literal, + token: function(stream, state, style) { + if (style == "variable" && stream.peek() == "(" && + (state.prevToken == ";" || state.prevToken == null || + state.prevToken == "}") && + cppLooksLikeConstructor(stream.current())) + return "def"; + } + }, + namespaceSeparator: "::", + modeProps: {fold: ["brace", "include"]} + }); + + def("text/x-java", { + name: "clike", + keywords: words("abstract assert break case catch class const continue default " + + "do else enum extends final finally float for goto if implements import " + + "instanceof interface native new package private protected public " + + "return static strictfp super switch synchronized this throw throws transient " + + "try volatile while @interface"), + types: words("byte short int long float double boolean char void Boolean Byte Character Double Float " + + "Integer Long Number Object Short String StringBuffer StringBuilder Void"), + blockKeywords: words("catch class do else finally for if switch try while"), + defKeywords: words("class interface package enum @interface"), + typeFirstDefinitions: true, + atoms: words("true false null"), + number: /^(?:0x[a-f\d_]+|0b[01_]+|(?:[\d_]+\.?\d*|\.\d+)(?:e[-+]?[\d_]+)?)(u|ll?|l|f)?/i, + hooks: { + "@": function(stream) { + // Don't match the @interface keyword. + if (stream.match('interface', false)) return false; + + stream.eatWhile(/[\w\$_]/); + return "meta"; + } + }, + modeProps: {fold: ["brace", "import"]} + }); + + def("text/x-csharp", { + name: "clike", + keywords: words("abstract as async await base break case catch checked class const continue" + + " default delegate do else enum event explicit extern finally fixed for" + + " foreach goto if implicit in interface internal is lock namespace new" + + " operator out override params private protected public readonly ref return sealed" + + " sizeof stackalloc static struct switch this throw try typeof unchecked" + + " unsafe using virtual void volatile while add alias ascending descending dynamic from get" + + " global group into join let orderby partial remove select set value var yield"), + types: words("Action Boolean Byte Char DateTime DateTimeOffset Decimal Double Func" + + " Guid Int16 Int32 Int64 Object SByte Single String Task TimeSpan UInt16 UInt32" + + " UInt64 bool byte char decimal double short int long object" + + " sbyte float string ushort uint ulong"), + blockKeywords: words("catch class do else finally for foreach if struct switch try while"), + defKeywords: words("class interface namespace struct var"), + typeFirstDefinitions: true, + atoms: words("true false null"), + hooks: { + "@": function(stream, state) { + if (stream.eat('"')) { + state.tokenize = tokenAtString; + return tokenAtString(stream, state); + } + stream.eatWhile(/[\w\$_]/); + return "meta"; + } + } + }); + + function tokenTripleString(stream, state) { + var escaped = false; + while (!stream.eol()) { + if (!escaped && stream.match('"""')) { + state.tokenize = null; + break; + } + escaped = stream.next() == "\\" && !escaped; + } + return "string"; + } + + def("text/x-scala", { + name: "clike", + keywords: words( + + /* scala */ + "abstract case catch class def do else extends final finally for forSome if " + + "implicit import lazy match new null object override package private protected return " + + "sealed super this throw trait try type val var while with yield _ : = => <- <: " + + "<% >: # @ " + + + /* package scala */ + "assert assume require print println printf readLine readBoolean readByte readShort " + + "readChar readInt readLong readFloat readDouble " + + + ":: #:: " + ), + types: words( + "AnyVal App Application Array BufferedIterator BigDecimal BigInt Char Console Either " + + "Enumeration Equiv Error Exception Fractional Function IndexedSeq Int Integral Iterable " + + "Iterator List Map Numeric Nil NotNull Option Ordered Ordering PartialFunction PartialOrdering " + + "Product Proxy Range Responder Seq Serializable Set Specializable Stream StringBuilder " + + "StringContext Symbol Throwable Traversable TraversableOnce Tuple Unit Vector " + + + /* package java.lang */ + "Boolean Byte Character CharSequence Class ClassLoader Cloneable Comparable " + + "Compiler Double Exception Float Integer Long Math Number Object Package Pair Process " + + "Runtime Runnable SecurityManager Short StackTraceElement StrictMath String " + + "StringBuffer System Thread ThreadGroup ThreadLocal Throwable Triple Void" + ), + multiLineStrings: true, + blockKeywords: words("catch class do else finally for forSome if match switch try while"), + defKeywords: words("class def object package trait type val var"), + atoms: words("true false null"), + indentStatements: false, + indentSwitch: false, + hooks: { + "@": function(stream) { + stream.eatWhile(/[\w\$_]/); + return "meta"; + }, + '"': function(stream, state) { + if (!stream.match('""')) return false; + state.tokenize = tokenTripleString; + return state.tokenize(stream, state); + }, + "'": function(stream) { + stream.eatWhile(/[\w\$_\xa1-\uffff]/); + return "atom"; + }, + "=": function(stream, state) { + var cx = state.context + if (cx.type == "}" && cx.align && stream.eat(">")) { + state.context = new Context(cx.indented, cx.column, cx.type, cx.info, null, cx.prev) + return "operator" + } else { + return false + } + } + }, + modeProps: {closeBrackets: {triples: '"'}} + }); + + function tokenKotlinString(tripleString){ + return function (stream, state) { + var escaped = false, next, end = false; + while (!stream.eol()) { + if (!tripleString && !escaped && stream.match('"') ) {end = true; break;} + if (tripleString && stream.match('"""')) {end = true; break;} + next = stream.next(); + if(!escaped && next == "$" && stream.match('{')) + stream.skipTo("}"); + escaped = !escaped && next == "\\" && !tripleString; + } + if (end || !tripleString) + state.tokenize = null; + return "string"; + } + } + + def("text/x-kotlin", { + name: "clike", + keywords: words( + /*keywords*/ + "package as typealias class interface this super val " + + "var fun for is in This throw return " + + "break continue object if else while do try when !in !is as? " + + + /*soft keywords*/ + "file import where by get set abstract enum open inner override private public internal " + + "protected catch finally out final vararg reified dynamic companion constructor init " + + "sealed field property receiver param sparam lateinit data inline noinline tailrec " + + "external annotation crossinline const operator infix" + ), + types: words( + /* package java.lang */ + "Boolean Byte Character CharSequence Class ClassLoader Cloneable Comparable " + + "Compiler Double Exception Float Integer Long Math Number Object Package Pair Process " + + "Runtime Runnable SecurityManager Short StackTraceElement StrictMath String " + + "StringBuffer System Thread ThreadGroup ThreadLocal Throwable Triple Void" + ), + intendSwitch: false, + indentStatements: false, + multiLineStrings: true, + blockKeywords: words("catch class do else finally for if where try while enum"), + defKeywords: words("class val var object package interface fun"), + atoms: words("true false null this"), + hooks: { + '"': function(stream, state) { + state.tokenize = tokenKotlinString(stream.match('""')); + return state.tokenize(stream, state); + } + }, + modeProps: {closeBrackets: {triples: '"'}} + }); + + def(["x-shader/x-vertex", "x-shader/x-fragment"], { + name: "clike", + keywords: words("sampler1D sampler2D sampler3D samplerCube " + + "sampler1DShadow sampler2DShadow " + + "const attribute uniform varying " + + "break continue discard return " + + "for while do if else struct " + + "in out inout"), + types: words("float int bool void " + + "vec2 vec3 vec4 ivec2 ivec3 ivec4 bvec2 bvec3 bvec4 " + + "mat2 mat3 mat4"), + blockKeywords: words("for while do if else struct"), + builtin: words("radians degrees sin cos tan asin acos atan " + + "pow exp log exp2 sqrt inversesqrt " + + "abs sign floor ceil fract mod min max clamp mix step smoothstep " + + "length distance dot cross normalize ftransform faceforward " + + "reflect refract matrixCompMult " + + "lessThan lessThanEqual greaterThan greaterThanEqual " + + "equal notEqual any all not " + + "texture1D texture1DProj texture1DLod texture1DProjLod " + + "texture2D texture2DProj texture2DLod texture2DProjLod " + + "texture3D texture3DProj texture3DLod texture3DProjLod " + + "textureCube textureCubeLod " + + "shadow1D shadow2D shadow1DProj shadow2DProj " + + "shadow1DLod shadow2DLod shadow1DProjLod shadow2DProjLod " + + "dFdx dFdy fwidth " + + "noise1 noise2 noise3 noise4"), + atoms: words("true false " + + "gl_FragColor gl_SecondaryColor gl_Normal gl_Vertex " + + "gl_MultiTexCoord0 gl_MultiTexCoord1 gl_MultiTexCoord2 gl_MultiTexCoord3 " + + "gl_MultiTexCoord4 gl_MultiTexCoord5 gl_MultiTexCoord6 gl_MultiTexCoord7 " + + "gl_FogCoord gl_PointCoord " + + "gl_Position gl_PointSize gl_ClipVertex " + + "gl_FrontColor gl_BackColor gl_FrontSecondaryColor gl_BackSecondaryColor " + + "gl_TexCoord gl_FogFragCoord " + + "gl_FragCoord gl_FrontFacing " + + "gl_FragData gl_FragDepth " + + "gl_ModelViewMatrix gl_ProjectionMatrix gl_ModelViewProjectionMatrix " + + "gl_TextureMatrix gl_NormalMatrix gl_ModelViewMatrixInverse " + + "gl_ProjectionMatrixInverse gl_ModelViewProjectionMatrixInverse " + + "gl_TexureMatrixTranspose gl_ModelViewMatrixInverseTranspose " + + "gl_ProjectionMatrixInverseTranspose " + + "gl_ModelViewProjectionMatrixInverseTranspose " + + "gl_TextureMatrixInverseTranspose " + + "gl_NormalScale gl_DepthRange gl_ClipPlane " + + "gl_Point gl_FrontMaterial gl_BackMaterial gl_LightSource gl_LightModel " + + "gl_FrontLightModelProduct gl_BackLightModelProduct " + + "gl_TextureColor gl_EyePlaneS gl_EyePlaneT gl_EyePlaneR gl_EyePlaneQ " + + "gl_FogParameters " + + "gl_MaxLights gl_MaxClipPlanes gl_MaxTextureUnits gl_MaxTextureCoords " + + "gl_MaxVertexAttribs gl_MaxVertexUniformComponents gl_MaxVaryingFloats " + + "gl_MaxVertexTextureImageUnits gl_MaxTextureImageUnits " + + "gl_MaxFragmentUniformComponents gl_MaxCombineTextureImageUnits " + + "gl_MaxDrawBuffers"), + indentSwitch: false, + hooks: {"#": cppHook}, + modeProps: {fold: ["brace", "include"]} + }); + + def("text/x-nesc", { + name: "clike", + keywords: words(cKeywords + "as atomic async call command component components configuration event generic " + + "implementation includes interface module new norace nx_struct nx_union post provides " + + "signal task uses abstract extends"), + types: words(cTypes), + blockKeywords: words("case do else for if switch while struct"), + atoms: words("null true false"), + hooks: {"#": cppHook}, + modeProps: {fold: ["brace", "include"]} + }); + + def("text/x-objectivec", { + name: "clike", + keywords: words(cKeywords + "inline restrict _Bool _Complex _Imaginary BOOL Class bycopy byref id IMP in " + + "inout nil oneway out Protocol SEL self super atomic nonatomic retain copy readwrite readonly"), + types: words(cTypes), + atoms: words("YES NO NULL NILL ON OFF true false"), + hooks: { + "@": function(stream) { + stream.eatWhile(/[\w\$]/); + return "keyword"; + }, + "#": cppHook, + indent: function(_state, ctx, textAfter) { + if (ctx.type == "statement" && /^@\w/.test(textAfter)) return ctx.indented + } + }, + modeProps: {fold: "brace"} + }); + + def("text/x-squirrel", { + name: "clike", + keywords: words("base break clone continue const default delete enum extends function in class" + + " foreach local resume return this throw typeof yield constructor instanceof static"), + types: words(cTypes), + blockKeywords: words("case catch class else for foreach if switch try while"), + defKeywords: words("function local class"), + typeFirstDefinitions: true, + atoms: words("true false null"), + hooks: {"#": cppHook}, + modeProps: {fold: ["brace", "include"]} + }); + + // Ceylon Strings need to deal with interpolation + var stringTokenizer = null; + function tokenCeylonString(type) { + return function(stream, state) { + var escaped = false, next, end = false; + while (!stream.eol()) { + if (!escaped && stream.match('"') && + (type == "single" || stream.match('""'))) { + end = true; + break; + } + if (!escaped && stream.match('``')) { + stringTokenizer = tokenCeylonString(type); + end = true; + break; + } + next = stream.next(); + escaped = type == "single" && !escaped && next == "\\"; + } + if (end) + state.tokenize = null; + return "string"; + } + } + + def("text/x-ceylon", { + name: "clike", + keywords: words("abstracts alias assembly assert assign break case catch class continue dynamic else" + + " exists extends finally for function given if import in interface is let module new" + + " nonempty object of out outer package return satisfies super switch then this throw" + + " try value void while"), + types: function(word) { + // In Ceylon all identifiers that start with an uppercase are types + var first = word.charAt(0); + return (first === first.toUpperCase() && first !== first.toLowerCase()); + }, + blockKeywords: words("case catch class dynamic else finally for function if interface module new object switch try while"), + defKeywords: words("class dynamic function interface module object package value"), + builtin: words("abstract actual aliased annotation by default deprecated doc final formal late license" + + " native optional sealed see serializable shared suppressWarnings tagged throws variable"), + isPunctuationChar: /[\[\]{}\(\),;\:\.`]/, + isOperatorChar: /[+\-*&%=<>!?|^~:\/]/, + numberStart: /[\d#$]/, + number: /^(?:#[\da-fA-F_]+|\$[01_]+|[\d_]+[kMGTPmunpf]?|[\d_]+\.[\d_]+(?:[eE][-+]?\d+|[kMGTPmunpf]|)|)/i, + multiLineStrings: true, + typeFirstDefinitions: true, + atoms: words("true false null larger smaller equal empty finished"), + indentSwitch: false, + styleDefs: false, + hooks: { + "@": function(stream) { + stream.eatWhile(/[\w\$_]/); + return "meta"; + }, + '"': function(stream, state) { + state.tokenize = tokenCeylonString(stream.match('""') ? "triple" : "single"); + return state.tokenize(stream, state); + }, + '`': function(stream, state) { + if (!stringTokenizer || !stream.match('`')) return false; + state.tokenize = stringTokenizer; + stringTokenizer = null; + return state.tokenize(stream, state); + }, + "'": function(stream) { + stream.eatWhile(/[\w\$_\xa1-\uffff]/); + return "atom"; + }, + token: function(_stream, state, style) { + if ((style == "variable" || style == "variable-3") && + state.prevToken == ".") { + return "variable-2"; + } + } + }, + modeProps: { + fold: ["brace", "import"], + closeBrackets: {triples: '"'} + } + }); + +}); diff --git a/resources/js/vendor/codemirror/codemirror.js b/resources/js/vendor/codemirror/codemirror.js new file mode 100644 index 0000000000..3e0cc2b240 --- /dev/null +++ b/resources/js/vendor/codemirror/codemirror.js @@ -0,0 +1,9113 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +// This is CodeMirror (http://codemirror.net), a code editor +// implemented in JavaScript on top of the browser's DOM. +// +// You can find some technical background for some of the code below +// at http://marijnhaverbeke.nl/blog/#cm-internals . + +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global.CodeMirror = factory()); +}(this, (function () { 'use strict'; + +// Kludges for bugs and behavior differences that can't be feature +// detected are enabled based on userAgent etc sniffing. +var userAgent = navigator.userAgent +var platform = navigator.platform + +var gecko = /gecko\/\d/i.test(userAgent) +var ie_upto10 = /MSIE \d/.test(userAgent) +var ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(userAgent) +var ie = ie_upto10 || ie_11up +var ie_version = ie && (ie_upto10 ? document.documentMode || 6 : ie_11up[1]) +var webkit = /WebKit\//.test(userAgent) +var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(userAgent) +var chrome = /Chrome\//.test(userAgent) +var presto = /Opera\//.test(userAgent) +var safari = /Apple Computer/.test(navigator.vendor) +var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(userAgent) +var phantom = /PhantomJS/.test(userAgent) + +var ios = /AppleWebKit/.test(userAgent) && /Mobile\/\w+/.test(userAgent) +// This is woefully incomplete. Suggestions for alternative methods welcome. +var mobile = ios || /Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(userAgent) +var mac = ios || /Mac/.test(platform) +var chromeOS = /\bCrOS\b/.test(userAgent) +var windows = /win/i.test(platform) + +var presto_version = presto && userAgent.match(/Version\/(\d*\.\d*)/) +if (presto_version) { presto_version = Number(presto_version[1]) } +if (presto_version && presto_version >= 15) { presto = false; webkit = true } +// Some browsers use the wrong event properties to signal cmd/ctrl on OS X +var flipCtrlCmd = mac && (qtwebkit || presto && (presto_version == null || presto_version < 12.11)) +var captureRightClick = gecko || (ie && ie_version >= 9) + +function classTest(cls) { return new RegExp("(^|\\s)" + cls + "(?:$|\\s)\\s*") } + +var rmClass = function(node, cls) { + var current = node.className + var match = classTest(cls).exec(current) + if (match) { + var after = current.slice(match.index + match[0].length) + node.className = current.slice(0, match.index) + (after ? match[1] + after : "") + } +} + +function removeChildren(e) { + for (var count = e.childNodes.length; count > 0; --count) + { e.removeChild(e.firstChild) } + return e +} + +function removeChildrenAndAdd(parent, e) { + return removeChildren(parent).appendChild(e) +} + +function elt(tag, content, className, style) { + var e = document.createElement(tag) + if (className) { e.className = className } + if (style) { e.style.cssText = style } + if (typeof content == "string") { e.appendChild(document.createTextNode(content)) } + else if (content) { for (var i = 0; i < content.length; ++i) { e.appendChild(content[i]) } } + return e +} + +var range +if (document.createRange) { range = function(node, start, end, endNode) { + var r = document.createRange() + r.setEnd(endNode || node, end) + r.setStart(node, start) + return r +} } +else { range = function(node, start, end) { + var r = document.body.createTextRange() + try { r.moveToElementText(node.parentNode) } + catch(e) { return r } + r.collapse(true) + r.moveEnd("character", end) + r.moveStart("character", start) + return r +} } + +function contains(parent, child) { + if (child.nodeType == 3) // Android browser always returns false when child is a textnode + { child = child.parentNode } + if (parent.contains) + { return parent.contains(child) } + do { + if (child.nodeType == 11) { child = child.host } + if (child == parent) { return true } + } while (child = child.parentNode) +} + +function activeElt() { + // IE and Edge may throw an "Unspecified Error" when accessing document.activeElement. + // IE < 10 will throw when accessed while the page is loading or in an iframe. + // IE > 9 and Edge will throw when accessed in an iframe if document.body is unavailable. + var activeElement + try { + activeElement = document.activeElement + } catch(e) { + activeElement = document.body || null + } + while (activeElement && activeElement.root && activeElement.root.activeElement) + { activeElement = activeElement.root.activeElement } + return activeElement +} + +function addClass(node, cls) { + var current = node.className + if (!classTest(cls).test(current)) { node.className += (current ? " " : "") + cls } +} +function joinClasses(a, b) { + var as = a.split(" ") + for (var i = 0; i < as.length; i++) + { if (as[i] && !classTest(as[i]).test(b)) { b += " " + as[i] } } + return b +} + +var selectInput = function(node) { node.select() } +if (ios) // Mobile Safari apparently has a bug where select() is broken. + { selectInput = function(node) { node.selectionStart = 0; node.selectionEnd = node.value.length } } +else if (ie) // Suppress mysterious IE10 errors + { selectInput = function(node) { try { node.select() } catch(_e) {} } } + +function bind(f) { + var args = Array.prototype.slice.call(arguments, 1) + return function(){return f.apply(null, args)} +} + +function copyObj(obj, target, overwrite) { + if (!target) { target = {} } + for (var prop in obj) + { if (obj.hasOwnProperty(prop) && (overwrite !== false || !target.hasOwnProperty(prop))) + { target[prop] = obj[prop] } } + return target +} + +// Counts the column offset in a string, taking tabs into account. +// Used mostly to find indentation. +function countColumn(string, end, tabSize, startIndex, startValue) { + if (end == null) { + end = string.search(/[^\s\u00a0]/) + if (end == -1) { end = string.length } + } + for (var i = startIndex || 0, n = startValue || 0;;) { + var nextTab = string.indexOf("\t", i) + if (nextTab < 0 || nextTab >= end) + { return n + (end - i) } + n += nextTab - i + n += tabSize - (n % tabSize) + i = nextTab + 1 + } +} + +function Delayed() {this.id = null} +Delayed.prototype.set = function(ms, f) { + clearTimeout(this.id) + this.id = setTimeout(f, ms) +} + +function indexOf(array, elt) { + for (var i = 0; i < array.length; ++i) + { if (array[i] == elt) { return i } } + return -1 +} + +// Number of pixels added to scroller and sizer to hide scrollbar +var scrollerGap = 30 + +// Returned or thrown by various protocols to signal 'I'm not +// handling this'. +var Pass = {toString: function(){return "CodeMirror.Pass"}} + +// Reused option objects for setSelection & friends +var sel_dontScroll = {scroll: false}; +var sel_mouse = {origin: "*mouse"}; +var sel_move = {origin: "+move"}; +// The inverse of countColumn -- find the offset that corresponds to +// a particular column. +function findColumn(string, goal, tabSize) { + for (var pos = 0, col = 0;;) { + var nextTab = string.indexOf("\t", pos) + if (nextTab == -1) { nextTab = string.length } + var skipped = nextTab - pos + if (nextTab == string.length || col + skipped >= goal) + { return pos + Math.min(skipped, goal - col) } + col += nextTab - pos + col += tabSize - (col % tabSize) + pos = nextTab + 1 + if (col >= goal) { return pos } + } +} + +var spaceStrs = [""] +function spaceStr(n) { + while (spaceStrs.length <= n) + { spaceStrs.push(lst(spaceStrs) + " ") } + return spaceStrs[n] +} + +function lst(arr) { return arr[arr.length-1] } + +function map(array, f) { + var out = [] + for (var i = 0; i < array.length; i++) { out[i] = f(array[i], i) } + return out +} + +function insertSorted(array, value, score) { + var pos = 0, priority = score(value) + while (pos < array.length && score(array[pos]) <= priority) { pos++ } + array.splice(pos, 0, value) +} + +function nothing() {} + +function createObj(base, props) { + var inst + if (Object.create) { + inst = Object.create(base) + } else { + nothing.prototype = base + inst = new nothing() + } + if (props) { copyObj(props, inst) } + return inst +} + +var nonASCIISingleCaseWordChar = /[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/ +function isWordCharBasic(ch) { + return /\w/.test(ch) || ch > "\x80" && + (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch)) +} +function isWordChar(ch, helper) { + if (!helper) { return isWordCharBasic(ch) } + if (helper.source.indexOf("\\w") > -1 && isWordCharBasic(ch)) { return true } + return helper.test(ch) +} + +function isEmpty(obj) { + for (var n in obj) { if (obj.hasOwnProperty(n) && obj[n]) { return false } } + return true +} + +// Extending unicode characters. A series of a non-extending char + +// any number of extending chars is treated as a single unit as far +// as editing and measuring is concerned. This is not fully correct, +// since some scripts/fonts/browsers also treat other configurations +// of code points as a group. +var extendingChars = /[\u0300-\u036f\u0483-\u0489\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u065e\u0670\u06d6-\u06dc\u06de-\u06e4\u06e7\u06e8\u06ea-\u06ed\u0711\u0730-\u074a\u07a6-\u07b0\u07eb-\u07f3\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0900-\u0902\u093c\u0941-\u0948\u094d\u0951-\u0955\u0962\u0963\u0981\u09bc\u09be\u09c1-\u09c4\u09cd\u09d7\u09e2\u09e3\u0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a70\u0a71\u0a75\u0a81\u0a82\u0abc\u0ac1-\u0ac5\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3e\u0b3f\u0b41-\u0b44\u0b4d\u0b56\u0b57\u0b62\u0b63\u0b82\u0bbe\u0bc0\u0bcd\u0bd7\u0c3e-\u0c40\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0cbc\u0cbf\u0cc2\u0cc6\u0ccc\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0d3e\u0d41-\u0d44\u0d4d\u0d57\u0d62\u0d63\u0dca\u0dcf\u0dd2-\u0dd4\u0dd6\u0ddf\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0eb1\u0eb4-\u0eb9\u0ebb\u0ebc\u0ec8-\u0ecd\u0f18\u0f19\u0f35\u0f37\u0f39\u0f71-\u0f7e\u0f80-\u0f84\u0f86\u0f87\u0f90-\u0f97\u0f99-\u0fbc\u0fc6\u102d-\u1030\u1032-\u1037\u1039\u103a\u103d\u103e\u1058\u1059\u105e-\u1060\u1071-\u1074\u1082\u1085\u1086\u108d\u109d\u135f\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17b7-\u17bd\u17c6\u17c9-\u17d3\u17dd\u180b-\u180d\u18a9\u1920-\u1922\u1927\u1928\u1932\u1939-\u193b\u1a17\u1a18\u1a56\u1a58-\u1a5e\u1a60\u1a62\u1a65-\u1a6c\u1a73-\u1a7c\u1a7f\u1b00-\u1b03\u1b34\u1b36-\u1b3a\u1b3c\u1b42\u1b6b-\u1b73\u1b80\u1b81\u1ba2-\u1ba5\u1ba8\u1ba9\u1c2c-\u1c33\u1c36\u1c37\u1cd0-\u1cd2\u1cd4-\u1ce0\u1ce2-\u1ce8\u1ced\u1dc0-\u1de6\u1dfd-\u1dff\u200c\u200d\u20d0-\u20f0\u2cef-\u2cf1\u2de0-\u2dff\u302a-\u302f\u3099\u309a\ua66f-\ua672\ua67c\ua67d\ua6f0\ua6f1\ua802\ua806\ua80b\ua825\ua826\ua8c4\ua8e0-\ua8f1\ua926-\ua92d\ua947-\ua951\ua980-\ua982\ua9b3\ua9b6-\ua9b9\ua9bc\uaa29-\uaa2e\uaa31\uaa32\uaa35\uaa36\uaa43\uaa4c\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uabe5\uabe8\uabed\udc00-\udfff\ufb1e\ufe00-\ufe0f\ufe20-\ufe26\uff9e\uff9f]/ +function isExtendingChar(ch) { return ch.charCodeAt(0) >= 768 && extendingChars.test(ch) } + +// The display handles the DOM integration, both for input reading +// and content drawing. It holds references to DOM nodes and +// display-related state. + +function Display(place, doc, input) { + var d = this + this.input = input + + // Covers bottom-right square when both scrollbars are present. + d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler") + d.scrollbarFiller.setAttribute("cm-not-content", "true") + // Covers bottom of gutter when coverGutterNextToScrollbar is on + // and h scrollbar is present. + d.gutterFiller = elt("div", null, "CodeMirror-gutter-filler") + d.gutterFiller.setAttribute("cm-not-content", "true") + // Will contain the actual code, positioned to cover the viewport. + d.lineDiv = elt("div", null, "CodeMirror-code") + // Elements are added to these to represent selection and cursors. + d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1") + d.cursorDiv = elt("div", null, "CodeMirror-cursors") + // A visibility: hidden element used to find the size of things. + d.measure = elt("div", null, "CodeMirror-measure") + // When lines outside of the viewport are measured, they are drawn in this. + d.lineMeasure = elt("div", null, "CodeMirror-measure") + // Wraps everything that needs to exist inside the vertically-padded coordinate system + d.lineSpace = elt("div", [d.measure, d.lineMeasure, d.selectionDiv, d.cursorDiv, d.lineDiv], + null, "position: relative; outline: none") + // Moved around its parent to cover visible view. + d.mover = elt("div", [elt("div", [d.lineSpace], "CodeMirror-lines")], null, "position: relative") + // Set to the height of the document, allowing scrolling. + d.sizer = elt("div", [d.mover], "CodeMirror-sizer") + d.sizerWidth = null + // Behavior of elts with overflow: auto and padding is + // inconsistent across browsers. This is used to ensure the + // scrollable area is big enough. + d.heightForcer = elt("div", null, null, "position: absolute; height: " + scrollerGap + "px; width: 1px;") + // Will contain the gutters, if any. + d.gutters = elt("div", null, "CodeMirror-gutters") + d.lineGutter = null + // Actual scrollable element. + d.scroller = elt("div", [d.sizer, d.heightForcer, d.gutters], "CodeMirror-scroll") + d.scroller.setAttribute("tabIndex", "-1") + // The element in which the editor lives. + d.wrapper = elt("div", [d.scrollbarFiller, d.gutterFiller, d.scroller], "CodeMirror") + + // Work around IE7 z-index bug (not perfect, hence IE7 not really being supported) + if (ie && ie_version < 8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0 } + if (!webkit && !(gecko && mobile)) { d.scroller.draggable = true } + + if (place) { + if (place.appendChild) { place.appendChild(d.wrapper) } + else { place(d.wrapper) } + } + + // Current rendered range (may be bigger than the view window). + d.viewFrom = d.viewTo = doc.first + d.reportedViewFrom = d.reportedViewTo = doc.first + // Information about the rendered lines. + d.view = [] + d.renderedView = null + // Holds info about a single rendered line when it was rendered + // for measurement, while not in view. + d.externalMeasured = null + // Empty space (in pixels) above the view + d.viewOffset = 0 + d.lastWrapHeight = d.lastWrapWidth = 0 + d.updateLineNumbers = null + + d.nativeBarWidth = d.barHeight = d.barWidth = 0 + d.scrollbarsClipped = false + + // Used to only resize the line number gutter when necessary (when + // the amount of lines crosses a boundary that makes its width change) + d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null + // Set to true when a non-horizontal-scrolling line widget is + // added. As an optimization, line widget aligning is skipped when + // this is false. + d.alignWidgets = false + + d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null + + // Tracks the maximum line length so that the horizontal scrollbar + // can be kept static when scrolling. + d.maxLine = null + d.maxLineLength = 0 + d.maxLineChanged = false + + // Used for measuring wheel scrolling granularity + d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null + + // True when shift is held down. + d.shift = false + + // Used to track whether anything happened since the context menu + // was opened. + d.selForContextMenu = null + + d.activeTouch = null + + input.init(d) +} + +// Find the line object corresponding to the given line number. +function getLine(doc, n) { + n -= doc.first + if (n < 0 || n >= doc.size) { throw new Error("There is no line " + (n + doc.first) + " in the document.") } + var chunk = doc + while (!chunk.lines) { + for (var i = 0;; ++i) { + var child = chunk.children[i], sz = child.chunkSize() + if (n < sz) { chunk = child; break } + n -= sz + } + } + return chunk.lines[n] +} + +// Get the part of a document between two positions, as an array of +// strings. +function getBetween(doc, start, end) { + var out = [], n = start.line + doc.iter(start.line, end.line + 1, function (line) { + var text = line.text + if (n == end.line) { text = text.slice(0, end.ch) } + if (n == start.line) { text = text.slice(start.ch) } + out.push(text) + ++n + }) + return out +} +// Get the lines between from and to, as array of strings. +function getLines(doc, from, to) { + var out = [] + doc.iter(from, to, function (line) { out.push(line.text) }) // iter aborts when callback returns truthy value + return out +} + +// Update the height of a line, propagating the height change +// upwards to parent nodes. +function updateLineHeight(line, height) { + var diff = height - line.height + if (diff) { for (var n = line; n; n = n.parent) { n.height += diff } } +} + +// Given a line object, find its line number by walking up through +// its parent links. +function lineNo(line) { + if (line.parent == null) { return null } + var cur = line.parent, no = indexOf(cur.lines, line) + for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) { + for (var i = 0;; ++i) { + if (chunk.children[i] == cur) { break } + no += chunk.children[i].chunkSize() + } + } + return no + cur.first +} + +// Find the line at the given vertical position, using the height +// information in the document tree. +function lineAtHeight(chunk, h) { + var n = chunk.first + outer: do { + for (var i$1 = 0; i$1 < chunk.children.length; ++i$1) { + var child = chunk.children[i$1], ch = child.height + if (h < ch) { chunk = child; continue outer } + h -= ch + n += child.chunkSize() + } + return n + } while (!chunk.lines) + var i = 0 + for (; i < chunk.lines.length; ++i) { + var line = chunk.lines[i], lh = line.height + if (h < lh) { break } + h -= lh + } + return n + i +} + +function isLine(doc, l) {return l >= doc.first && l < doc.first + doc.size} + +function lineNumberFor(options, i) { + return String(options.lineNumberFormatter(i + options.firstLineNumber)) +} + +// A Pos instance represents a position within the text. +function Pos (line, ch) { + if (!(this instanceof Pos)) { return new Pos(line, ch) } + this.line = line; this.ch = ch +} + +// Compare two positions, return 0 if they are the same, a negative +// number when a is less, and a positive number otherwise. +function cmp(a, b) { return a.line - b.line || a.ch - b.ch } + +function copyPos(x) {return Pos(x.line, x.ch)} +function maxPos(a, b) { return cmp(a, b) < 0 ? b : a } +function minPos(a, b) { return cmp(a, b) < 0 ? a : b } + +// Most of the external API clips given positions to make sure they +// actually exist within the document. +function clipLine(doc, n) {return Math.max(doc.first, Math.min(n, doc.first + doc.size - 1))} +function clipPos(doc, pos) { + if (pos.line < doc.first) { return Pos(doc.first, 0) } + var last = doc.first + doc.size - 1 + if (pos.line > last) { return Pos(last, getLine(doc, last).text.length) } + return clipToLen(pos, getLine(doc, pos.line).text.length) +} +function clipToLen(pos, linelen) { + var ch = pos.ch + if (ch == null || ch > linelen) { return Pos(pos.line, linelen) } + else if (ch < 0) { return Pos(pos.line, 0) } + else { return pos } +} +function clipPosArray(doc, array) { + var out = [] + for (var i = 0; i < array.length; i++) { out[i] = clipPos(doc, array[i]) } + return out +} + +// Optimize some code when these features are not used. +var sawReadOnlySpans = false; +var sawCollapsedSpans = false; +function seeReadOnlySpans() { + sawReadOnlySpans = true +} + +function seeCollapsedSpans() { + sawCollapsedSpans = true +} + +// TEXTMARKER SPANS + +function MarkedSpan(marker, from, to) { + this.marker = marker + this.from = from; this.to = to +} + +// Search an array of spans for a span matching the given marker. +function getMarkedSpanFor(spans, marker) { + if (spans) { for (var i = 0; i < spans.length; ++i) { + var span = spans[i] + if (span.marker == marker) { return span } + } } +} +// Remove a span from an array, returning undefined if no spans are +// left (we don't store arrays for lines without spans). +function removeMarkedSpan(spans, span) { + var r + for (var i = 0; i < spans.length; ++i) + { if (spans[i] != span) { (r || (r = [])).push(spans[i]) } } + return r +} +// Add a span to a line. +function addMarkedSpan(line, span) { + line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [span] + span.marker.attachLine(line) +} + +// Used for the algorithm that adjusts markers for a change in the +// document. These functions cut an array of spans at a given +// character position, returning an array of remaining chunks (or +// undefined if nothing remains). +function markedSpansBefore(old, startCh, isInsert) { + var nw + if (old) { for (var i = 0; i < old.length; ++i) { + var span = old[i], marker = span.marker + var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh) + if (startsBefore || span.from == startCh && marker.type == "bookmark" && (!isInsert || !span.marker.insertLeft)) { + var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh) + ;(nw || (nw = [])).push(new MarkedSpan(marker, span.from, endsAfter ? null : span.to)) + } + } } + return nw +} +function markedSpansAfter(old, endCh, isInsert) { + var nw + if (old) { for (var i = 0; i < old.length; ++i) { + var span = old[i], marker = span.marker + var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh) + if (endsAfter || span.from == endCh && marker.type == "bookmark" && (!isInsert || span.marker.insertLeft)) { + var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh) + ;(nw || (nw = [])).push(new MarkedSpan(marker, startsBefore ? null : span.from - endCh, + span.to == null ? null : span.to - endCh)) + } + } } + return nw +} + +// Given a change object, compute the new set of marker spans that +// cover the line in which the change took place. Removes spans +// entirely within the change, reconnects spans belonging to the +// same marker that appear on both sides of the change, and cuts off +// spans partially within the change. Returns an array of span +// arrays with one element for each line in (after) the change. +function stretchSpansOverChange(doc, change) { + if (change.full) { return null } + var oldFirst = isLine(doc, change.from.line) && getLine(doc, change.from.line).markedSpans + var oldLast = isLine(doc, change.to.line) && getLine(doc, change.to.line).markedSpans + if (!oldFirst && !oldLast) { return null } + + var startCh = change.from.ch, endCh = change.to.ch, isInsert = cmp(change.from, change.to) == 0 + // Get the spans that 'stick out' on both sides + var first = markedSpansBefore(oldFirst, startCh, isInsert) + var last = markedSpansAfter(oldLast, endCh, isInsert) + + // Next, merge those two ends + var sameLine = change.text.length == 1, offset = lst(change.text).length + (sameLine ? startCh : 0) + if (first) { + // Fix up .to properties of first + for (var i = 0; i < first.length; ++i) { + var span = first[i] + if (span.to == null) { + var found = getMarkedSpanFor(last, span.marker) + if (!found) { span.to = startCh } + else if (sameLine) { span.to = found.to == null ? null : found.to + offset } + } + } + } + if (last) { + // Fix up .from in last (or move them into first in case of sameLine) + for (var i$1 = 0; i$1 < last.length; ++i$1) { + var span$1 = last[i$1] + if (span$1.to != null) { span$1.to += offset } + if (span$1.from == null) { + var found$1 = getMarkedSpanFor(first, span$1.marker) + if (!found$1) { + span$1.from = offset + if (sameLine) { (first || (first = [])).push(span$1) } + } + } else { + span$1.from += offset + if (sameLine) { (first || (first = [])).push(span$1) } + } + } + } + // Make sure we didn't create any zero-length spans + if (first) { first = clearEmptySpans(first) } + if (last && last != first) { last = clearEmptySpans(last) } + + var newMarkers = [first] + if (!sameLine) { + // Fill gap with whole-line-spans + var gap = change.text.length - 2, gapMarkers + if (gap > 0 && first) + { for (var i$2 = 0; i$2 < first.length; ++i$2) + { if (first[i$2].to == null) + { (gapMarkers || (gapMarkers = [])).push(new MarkedSpan(first[i$2].marker, null, null)) } } } + for (var i$3 = 0; i$3 < gap; ++i$3) + { newMarkers.push(gapMarkers) } + newMarkers.push(last) + } + return newMarkers +} + +// Remove spans that are empty and don't have a clearWhenEmpty +// option of false. +function clearEmptySpans(spans) { + for (var i = 0; i < spans.length; ++i) { + var span = spans[i] + if (span.from != null && span.from == span.to && span.marker.clearWhenEmpty !== false) + { spans.splice(i--, 1) } + } + if (!spans.length) { return null } + return spans +} + +// Used to 'clip' out readOnly ranges when making a change. +function removeReadOnlyRanges(doc, from, to) { + var markers = null + doc.iter(from.line, to.line + 1, function (line) { + if (line.markedSpans) { for (var i = 0; i < line.markedSpans.length; ++i) { + var mark = line.markedSpans[i].marker + if (mark.readOnly && (!markers || indexOf(markers, mark) == -1)) + { (markers || (markers = [])).push(mark) } + } } + }) + if (!markers) { return null } + var parts = [{from: from, to: to}] + for (var i = 0; i < markers.length; ++i) { + var mk = markers[i], m = mk.find(0) + for (var j = 0; j < parts.length; ++j) { + var p = parts[j] + if (cmp(p.to, m.from) < 0 || cmp(p.from, m.to) > 0) { continue } + var newParts = [j, 1], dfrom = cmp(p.from, m.from), dto = cmp(p.to, m.to) + if (dfrom < 0 || !mk.inclusiveLeft && !dfrom) + { newParts.push({from: p.from, to: m.from}) } + if (dto > 0 || !mk.inclusiveRight && !dto) + { newParts.push({from: m.to, to: p.to}) } + parts.splice.apply(parts, newParts) + j += newParts.length - 1 + } + } + return parts +} + +// Connect or disconnect spans from a line. +function detachMarkedSpans(line) { + var spans = line.markedSpans + if (!spans) { return } + for (var i = 0; i < spans.length; ++i) + { spans[i].marker.detachLine(line) } + line.markedSpans = null +} +function attachMarkedSpans(line, spans) { + if (!spans) { return } + for (var i = 0; i < spans.length; ++i) + { spans[i].marker.attachLine(line) } + line.markedSpans = spans +} + +// Helpers used when computing which overlapping collapsed span +// counts as the larger one. +function extraLeft(marker) { return marker.inclusiveLeft ? -1 : 0 } +function extraRight(marker) { return marker.inclusiveRight ? 1 : 0 } + +// Returns a number indicating which of two overlapping collapsed +// spans is larger (and thus includes the other). Falls back to +// comparing ids when the spans cover exactly the same range. +function compareCollapsedMarkers(a, b) { + var lenDiff = a.lines.length - b.lines.length + if (lenDiff != 0) { return lenDiff } + var aPos = a.find(), bPos = b.find() + var fromCmp = cmp(aPos.from, bPos.from) || extraLeft(a) - extraLeft(b) + if (fromCmp) { return -fromCmp } + var toCmp = cmp(aPos.to, bPos.to) || extraRight(a) - extraRight(b) + if (toCmp) { return toCmp } + return b.id - a.id +} + +// Find out whether a line ends or starts in a collapsed span. If +// so, return the marker for that span. +function collapsedSpanAtSide(line, start) { + var sps = sawCollapsedSpans && line.markedSpans, found + if (sps) { for (var sp = (void 0), i = 0; i < sps.length; ++i) { + sp = sps[i] + if (sp.marker.collapsed && (start ? sp.from : sp.to) == null && + (!found || compareCollapsedMarkers(found, sp.marker) < 0)) + { found = sp.marker } + } } + return found +} +function collapsedSpanAtStart(line) { return collapsedSpanAtSide(line, true) } +function collapsedSpanAtEnd(line) { return collapsedSpanAtSide(line, false) } + +// Test whether there exists a collapsed span that partially +// overlaps (covers the start or end, but not both) of a new span. +// Such overlap is not allowed. +function conflictingCollapsedRange(doc, lineNo, from, to, marker) { + var line = getLine(doc, lineNo) + var sps = sawCollapsedSpans && line.markedSpans + if (sps) { for (var i = 0; i < sps.length; ++i) { + var sp = sps[i] + if (!sp.marker.collapsed) { continue } + var found = sp.marker.find(0) + var fromCmp = cmp(found.from, from) || extraLeft(sp.marker) - extraLeft(marker) + var toCmp = cmp(found.to, to) || extraRight(sp.marker) - extraRight(marker) + if (fromCmp >= 0 && toCmp <= 0 || fromCmp <= 0 && toCmp >= 0) { continue } + if (fromCmp <= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cmp(found.to, from) >= 0 : cmp(found.to, from) > 0) || + fromCmp >= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cmp(found.from, to) <= 0 : cmp(found.from, to) < 0)) + { return true } + } } +} + +// A visual line is a line as drawn on the screen. Folding, for +// example, can cause multiple logical lines to appear on the same +// visual line. This finds the start of the visual line that the +// given line is part of (usually that is the line itself). +function visualLine(line) { + var merged + while (merged = collapsedSpanAtStart(line)) + { line = merged.find(-1, true).line } + return line +} + +// Returns an array of logical lines that continue the visual line +// started by the argument, or undefined if there are no such lines. +function visualLineContinued(line) { + var merged, lines + while (merged = collapsedSpanAtEnd(line)) { + line = merged.find(1, true).line + ;(lines || (lines = [])).push(line) + } + return lines +} + +// Get the line number of the start of the visual line that the +// given line number is part of. +function visualLineNo(doc, lineN) { + var line = getLine(doc, lineN), vis = visualLine(line) + if (line == vis) { return lineN } + return lineNo(vis) +} + +// Get the line number of the start of the next visual line after +// the given line. +function visualLineEndNo(doc, lineN) { + if (lineN > doc.lastLine()) { return lineN } + var line = getLine(doc, lineN), merged + if (!lineIsHidden(doc, line)) { return lineN } + while (merged = collapsedSpanAtEnd(line)) + { line = merged.find(1, true).line } + return lineNo(line) + 1 +} + +// Compute whether a line is hidden. Lines count as hidden when they +// are part of a visual line that starts with another line, or when +// they are entirely covered by collapsed, non-widget span. +function lineIsHidden(doc, line) { + var sps = sawCollapsedSpans && line.markedSpans + if (sps) { for (var sp = (void 0), i = 0; i < sps.length; ++i) { + sp = sps[i] + if (!sp.marker.collapsed) { continue } + if (sp.from == null) { return true } + if (sp.marker.widgetNode) { continue } + if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(doc, line, sp)) + { return true } + } } +} +function lineIsHiddenInner(doc, line, span) { + if (span.to == null) { + var end = span.marker.find(1, true) + return lineIsHiddenInner(doc, end.line, getMarkedSpanFor(end.line.markedSpans, span.marker)) + } + if (span.marker.inclusiveRight && span.to == line.text.length) + { return true } + for (var sp = (void 0), i = 0; i < line.markedSpans.length; ++i) { + sp = line.markedSpans[i] + if (sp.marker.collapsed && !sp.marker.widgetNode && sp.from == span.to && + (sp.to == null || sp.to != span.from) && + (sp.marker.inclusiveLeft || span.marker.inclusiveRight) && + lineIsHiddenInner(doc, line, sp)) { return true } + } +} + +// Find the height above the given line. +function heightAtLine(lineObj) { + lineObj = visualLine(lineObj) + + var h = 0, chunk = lineObj.parent + for (var i = 0; i < chunk.lines.length; ++i) { + var line = chunk.lines[i] + if (line == lineObj) { break } + else { h += line.height } + } + for (var p = chunk.parent; p; chunk = p, p = chunk.parent) { + for (var i$1 = 0; i$1 < p.children.length; ++i$1) { + var cur = p.children[i$1] + if (cur == chunk) { break } + else { h += cur.height } + } + } + return h +} + +// Compute the character length of a line, taking into account +// collapsed ranges (see markText) that might hide parts, and join +// other lines onto it. +function lineLength(line) { + if (line.height == 0) { return 0 } + var len = line.text.length, merged, cur = line + while (merged = collapsedSpanAtStart(cur)) { + var found = merged.find(0, true) + cur = found.from.line + len += found.from.ch - found.to.ch + } + cur = line + while (merged = collapsedSpanAtEnd(cur)) { + var found$1 = merged.find(0, true) + len -= cur.text.length - found$1.from.ch + cur = found$1.to.line + len += cur.text.length - found$1.to.ch + } + return len +} + +// Find the longest line in the document. +function findMaxLine(cm) { + var d = cm.display, doc = cm.doc + d.maxLine = getLine(doc, doc.first) + d.maxLineLength = lineLength(d.maxLine) + d.maxLineChanged = true + doc.iter(function (line) { + var len = lineLength(line) + if (len > d.maxLineLength) { + d.maxLineLength = len + d.maxLine = line + } + }) +} + +// BIDI HELPERS + +function iterateBidiSections(order, from, to, f) { + if (!order) { return f(from, to, "ltr") } + var found = false + for (var i = 0; i < order.length; ++i) { + var part = order[i] + if (part.from < to && part.to > from || from == to && part.to == from) { + f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? "rtl" : "ltr") + found = true + } + } + if (!found) { f(from, to, "ltr") } +} + +function bidiLeft(part) { return part.level % 2 ? part.to : part.from } +function bidiRight(part) { return part.level % 2 ? part.from : part.to } + +function lineLeft(line) { var order = getOrder(line); return order ? bidiLeft(order[0]) : 0 } +function lineRight(line) { + var order = getOrder(line) + if (!order) { return line.text.length } + return bidiRight(lst(order)) +} + +function compareBidiLevel(order, a, b) { + var linedir = order[0].level + if (a == linedir) { return true } + if (b == linedir) { return false } + return a < b +} + +var bidiOther = null +function getBidiPartAt(order, pos) { + var found + bidiOther = null + for (var i = 0; i < order.length; ++i) { + var cur = order[i] + if (cur.from < pos && cur.to > pos) { return i } + if ((cur.from == pos || cur.to == pos)) { + if (found == null) { + found = i + } else if (compareBidiLevel(order, cur.level, order[found].level)) { + if (cur.from != cur.to) { bidiOther = found } + return i + } else { + if (cur.from != cur.to) { bidiOther = i } + return found + } + } + } + return found +} + +function moveInLine(line, pos, dir, byUnit) { + if (!byUnit) { return pos + dir } + do { pos += dir } + while (pos > 0 && isExtendingChar(line.text.charAt(pos))) + return pos +} + +// This is needed in order to move 'visually' through bi-directional +// text -- i.e., pressing left should make the cursor go left, even +// when in RTL text. The tricky part is the 'jumps', where RTL and +// LTR text touch each other. This often requires the cursor offset +// to move more than one unit, in order to visually move one unit. +function moveVisually(line, start, dir, byUnit) { + var bidi = getOrder(line) + if (!bidi) { return moveLogically(line, start, dir, byUnit) } + var pos = getBidiPartAt(bidi, start), part = bidi[pos] + var target = moveInLine(line, start, part.level % 2 ? -dir : dir, byUnit) + + for (;;) { + if (target > part.from && target < part.to) { return target } + if (target == part.from || target == part.to) { + if (getBidiPartAt(bidi, target) == pos) { return target } + part = bidi[pos += dir] + return (dir > 0) == part.level % 2 ? part.to : part.from + } else { + part = bidi[pos += dir] + if (!part) { return null } + if ((dir > 0) == part.level % 2) + { target = moveInLine(line, part.to, -1, byUnit) } + else + { target = moveInLine(line, part.from, 1, byUnit) } + } + } +} + +function moveLogically(line, start, dir, byUnit) { + var target = start + dir + if (byUnit) { while (target > 0 && isExtendingChar(line.text.charAt(target))) { target += dir } } + return target < 0 || target > line.text.length ? null : target +} + +// Bidirectional ordering algorithm +// See http://unicode.org/reports/tr9/tr9-13.html for the algorithm +// that this (partially) implements. + +// One-char codes used for character types: +// L (L): Left-to-Right +// R (R): Right-to-Left +// r (AL): Right-to-Left Arabic +// 1 (EN): European Number +// + (ES): European Number Separator +// % (ET): European Number Terminator +// n (AN): Arabic Number +// , (CS): Common Number Separator +// m (NSM): Non-Spacing Mark +// b (BN): Boundary Neutral +// s (B): Paragraph Separator +// t (S): Segment Separator +// w (WS): Whitespace +// N (ON): Other Neutrals + +// Returns null if characters are ordered as they appear +// (left-to-right), or an array of sections ({from, to, level} +// objects) in the order in which they occur visually. +var bidiOrdering = (function() { + // Character types for codepoints 0 to 0xff + var lowTypes = "bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLN" + // Character types for codepoints 0x600 to 0x6f9 + var arabicTypes = "nnnnnnNNr%%r,rNNmmmmmmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmmmnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmnNmmmmmmrrmmNmmmmrr1111111111" + function charType(code) { + if (code <= 0xf7) { return lowTypes.charAt(code) } + else if (0x590 <= code && code <= 0x5f4) { return "R" } + else if (0x600 <= code && code <= 0x6f9) { return arabicTypes.charAt(code - 0x600) } + else if (0x6ee <= code && code <= 0x8ac) { return "r" } + else if (0x2000 <= code && code <= 0x200b) { return "w" } + else if (code == 0x200c) { return "b" } + else { return "L" } + } + + var bidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/ + var isNeutral = /[stwN]/, isStrong = /[LRr]/, countsAsLeft = /[Lb1n]/, countsAsNum = /[1n]/ + // Browsers seem to always treat the boundaries of block elements as being L. + var outerType = "L" + + function BidiSpan(level, from, to) { + this.level = level + this.from = from; this.to = to + } + + return function(str) { + if (!bidiRE.test(str)) { return false } + var len = str.length, types = [] + for (var i = 0; i < len; ++i) + { types.push(charType(str.charCodeAt(i))) } + + // W1. Examine each non-spacing mark (NSM) in the level run, and + // change the type of the NSM to the type of the previous + // character. If the NSM is at the start of the level run, it will + // get the type of sor. + for (var i$1 = 0, prev = outerType; i$1 < len; ++i$1) { + var type = types[i$1] + if (type == "m") { types[i$1] = prev } + else { prev = type } + } + + // W2. Search backwards from each instance of a European number + // until the first strong type (R, L, AL, or sor) is found. If an + // AL is found, change the type of the European number to Arabic + // number. + // W3. Change all ALs to R. + for (var i$2 = 0, cur = outerType; i$2 < len; ++i$2) { + var type$1 = types[i$2] + if (type$1 == "1" && cur == "r") { types[i$2] = "n" } + else if (isStrong.test(type$1)) { cur = type$1; if (type$1 == "r") { types[i$2] = "R" } } + } + + // W4. A single European separator between two European numbers + // changes to a European number. A single common separator between + // two numbers of the same type changes to that type. + for (var i$3 = 1, prev$1 = types[0]; i$3 < len - 1; ++i$3) { + var type$2 = types[i$3] + if (type$2 == "+" && prev$1 == "1" && types[i$3+1] == "1") { types[i$3] = "1" } + else if (type$2 == "," && prev$1 == types[i$3+1] && + (prev$1 == "1" || prev$1 == "n")) { types[i$3] = prev$1 } + prev$1 = type$2 + } + + // W5. A sequence of European terminators adjacent to European + // numbers changes to all European numbers. + // W6. Otherwise, separators and terminators change to Other + // Neutral. + for (var i$4 = 0; i$4 < len; ++i$4) { + var type$3 = types[i$4] + if (type$3 == ",") { types[i$4] = "N" } + else if (type$3 == "%") { + var end = (void 0) + for (end = i$4 + 1; end < len && types[end] == "%"; ++end) {} + var replace = (i$4 && types[i$4-1] == "!") || (end < len && types[end] == "1") ? "1" : "N" + for (var j = i$4; j < end; ++j) { types[j] = replace } + i$4 = end - 1 + } + } + + // W7. Search backwards from each instance of a European number + // until the first strong type (R, L, or sor) is found. If an L is + // found, then change the type of the European number to L. + for (var i$5 = 0, cur$1 = outerType; i$5 < len; ++i$5) { + var type$4 = types[i$5] + if (cur$1 == "L" && type$4 == "1") { types[i$5] = "L" } + else if (isStrong.test(type$4)) { cur$1 = type$4 } + } + + // N1. A sequence of neutrals takes the direction of the + // surrounding strong text if the text on both sides has the same + // direction. European and Arabic numbers act as if they were R in + // terms of their influence on neutrals. Start-of-level-run (sor) + // and end-of-level-run (eor) are used at level run boundaries. + // N2. Any remaining neutrals take the embedding direction. + for (var i$6 = 0; i$6 < len; ++i$6) { + if (isNeutral.test(types[i$6])) { + var end$1 = (void 0) + for (end$1 = i$6 + 1; end$1 < len && isNeutral.test(types[end$1]); ++end$1) {} + var before = (i$6 ? types[i$6-1] : outerType) == "L" + var after = (end$1 < len ? types[end$1] : outerType) == "L" + var replace$1 = before || after ? "L" : "R" + for (var j$1 = i$6; j$1 < end$1; ++j$1) { types[j$1] = replace$1 } + i$6 = end$1 - 1 + } + } + + // Here we depart from the documented algorithm, in order to avoid + // building up an actual levels array. Since there are only three + // levels (0, 1, 2) in an implementation that doesn't take + // explicit embedding into account, we can build up the order on + // the fly, without following the level-based algorithm. + var order = [], m + for (var i$7 = 0; i$7 < len;) { + if (countsAsLeft.test(types[i$7])) { + var start = i$7 + for (++i$7; i$7 < len && countsAsLeft.test(types[i$7]); ++i$7) {} + order.push(new BidiSpan(0, start, i$7)) + } else { + var pos = i$7, at = order.length + for (++i$7; i$7 < len && types[i$7] != "L"; ++i$7) {} + for (var j$2 = pos; j$2 < i$7;) { + if (countsAsNum.test(types[j$2])) { + if (pos < j$2) { order.splice(at, 0, new BidiSpan(1, pos, j$2)) } + var nstart = j$2 + for (++j$2; j$2 < i$7 && countsAsNum.test(types[j$2]); ++j$2) {} + order.splice(at, 0, new BidiSpan(2, nstart, j$2)) + pos = j$2 + } else { ++j$2 } + } + if (pos < i$7) { order.splice(at, 0, new BidiSpan(1, pos, i$7)) } + } + } + if (order[0].level == 1 && (m = str.match(/^\s+/))) { + order[0].from = m[0].length + order.unshift(new BidiSpan(0, 0, m[0].length)) + } + if (lst(order).level == 1 && (m = str.match(/\s+$/))) { + lst(order).to -= m[0].length + order.push(new BidiSpan(0, len - m[0].length, len)) + } + if (order[0].level == 2) + { order.unshift(new BidiSpan(1, order[0].to, order[0].to)) } + if (order[0].level != lst(order).level) + { order.push(new BidiSpan(order[0].level, len, len)) } + + return order + } +})() + +// Get the bidi ordering for the given line (and cache it). Returns +// false for lines that are fully left-to-right, and an array of +// BidiSpan objects otherwise. +function getOrder(line) { + var order = line.order + if (order == null) { order = line.order = bidiOrdering(line.text) } + return order +} + +// EVENT HANDLING + +// Lightweight event framework. on/off also work on DOM nodes, +// registering native DOM handlers. + +var noHandlers = [] + +var on = function(emitter, type, f) { + if (emitter.addEventListener) { + emitter.addEventListener(type, f, false) + } else if (emitter.attachEvent) { + emitter.attachEvent("on" + type, f) + } else { + var map = emitter._handlers || (emitter._handlers = {}) + map[type] = (map[type] || noHandlers).concat(f) + } +} + +function getHandlers(emitter, type) { + return emitter._handlers && emitter._handlers[type] || noHandlers +} + +function off(emitter, type, f) { + if (emitter.removeEventListener) { + emitter.removeEventListener(type, f, false) + } else if (emitter.detachEvent) { + emitter.detachEvent("on" + type, f) + } else { + var map = emitter._handlers, arr = map && map[type] + if (arr) { + var index = indexOf(arr, f) + if (index > -1) + { map[type] = arr.slice(0, index).concat(arr.slice(index + 1)) } + } + } +} + +function signal(emitter, type /*, values...*/) { + var handlers = getHandlers(emitter, type) + if (!handlers.length) { return } + var args = Array.prototype.slice.call(arguments, 2) + for (var i = 0; i < handlers.length; ++i) { handlers[i].apply(null, args) } +} + +// The DOM events that CodeMirror handles can be overridden by +// registering a (non-DOM) handler on the editor for the event name, +// and preventDefault-ing the event in that handler. +function signalDOMEvent(cm, e, override) { + if (typeof e == "string") + { e = {type: e, preventDefault: function() { this.defaultPrevented = true }} } + signal(cm, override || e.type, cm, e) + return e_defaultPrevented(e) || e.codemirrorIgnore +} + +function signalCursorActivity(cm) { + var arr = cm._handlers && cm._handlers.cursorActivity + if (!arr) { return } + var set = cm.curOp.cursorActivityHandlers || (cm.curOp.cursorActivityHandlers = []) + for (var i = 0; i < arr.length; ++i) { if (indexOf(set, arr[i]) == -1) + { set.push(arr[i]) } } +} + +function hasHandler(emitter, type) { + return getHandlers(emitter, type).length > 0 +} + +// Add on and off methods to a constructor's prototype, to make +// registering events on such objects more convenient. +function eventMixin(ctor) { + ctor.prototype.on = function(type, f) {on(this, type, f)} + ctor.prototype.off = function(type, f) {off(this, type, f)} +} + +// Due to the fact that we still support jurassic IE versions, some +// compatibility wrappers are needed. + +function e_preventDefault(e) { + if (e.preventDefault) { e.preventDefault() } + else { e.returnValue = false } +} +function e_stopPropagation(e) { + if (e.stopPropagation) { e.stopPropagation() } + else { e.cancelBubble = true } +} +function e_defaultPrevented(e) { + return e.defaultPrevented != null ? e.defaultPrevented : e.returnValue == false +} +function e_stop(e) {e_preventDefault(e); e_stopPropagation(e)} + +function e_target(e) {return e.target || e.srcElement} +function e_button(e) { + var b = e.which + if (b == null) { + if (e.button & 1) { b = 1 } + else if (e.button & 2) { b = 3 } + else if (e.button & 4) { b = 2 } + } + if (mac && e.ctrlKey && b == 1) { b = 3 } + return b +} + +// Detect drag-and-drop +var dragAndDrop = function() { + // There is *some* kind of drag-and-drop support in IE6-8, but I + // couldn't get it to work yet. + if (ie && ie_version < 9) { return false } + var div = elt('div') + return "draggable" in div || "dragDrop" in div +}() + +var zwspSupported +function zeroWidthElement(measure) { + if (zwspSupported == null) { + var test = elt("span", "\u200b") + removeChildrenAndAdd(measure, elt("span", [test, document.createTextNode("x")])) + if (measure.firstChild.offsetHeight != 0) + { zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !(ie && ie_version < 8) } + } + var node = zwspSupported ? elt("span", "\u200b") : + elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right: -1px") + node.setAttribute("cm-text", "") + return node +} + +// Feature-detect IE's crummy client rect reporting for bidi text +var badBidiRects +function hasBadBidiRects(measure) { + if (badBidiRects != null) { return badBidiRects } + var txt = removeChildrenAndAdd(measure, document.createTextNode("A\u062eA")) + var r0 = range(txt, 0, 1).getBoundingClientRect() + var r1 = range(txt, 1, 2).getBoundingClientRect() + removeChildren(measure) + if (!r0 || r0.left == r0.right) { return false } // Safari returns null in some cases (#2780) + return badBidiRects = (r1.right - r0.right < 3) +} + +// See if "".split is the broken IE version, if so, provide an +// alternative way to split lines. +var splitLinesAuto = "\n\nb".split(/\n/).length != 3 ? function (string) { + var pos = 0, result = [], l = string.length + while (pos <= l) { + var nl = string.indexOf("\n", pos) + if (nl == -1) { nl = string.length } + var line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl) + var rt = line.indexOf("\r") + if (rt != -1) { + result.push(line.slice(0, rt)) + pos += rt + 1 + } else { + result.push(line) + pos = nl + 1 + } + } + return result +} : function (string) { return string.split(/\r\n?|\n/); } + +var hasSelection = window.getSelection ? function (te) { + try { return te.selectionStart != te.selectionEnd } + catch(e) { return false } +} : function (te) { + var range + try {range = te.ownerDocument.selection.createRange()} + catch(e) {} + if (!range || range.parentElement() != te) { return false } + return range.compareEndPoints("StartToEnd", range) != 0 +} + +var hasCopyEvent = (function () { + var e = elt("div") + if ("oncopy" in e) { return true } + e.setAttribute("oncopy", "return;") + return typeof e.oncopy == "function" +})() + +var badZoomedRects = null +function hasBadZoomedRects(measure) { + if (badZoomedRects != null) { return badZoomedRects } + var node = removeChildrenAndAdd(measure, elt("span", "x")) + var normal = node.getBoundingClientRect() + var fromRange = range(node, 0, 1).getBoundingClientRect() + return badZoomedRects = Math.abs(normal.left - fromRange.left) > 1 +} + +var modes = {}; +var mimeModes = {}; +// Extra arguments are stored as the mode's dependencies, which is +// used by (legacy) mechanisms like loadmode.js to automatically +// load a mode. (Preferred mechanism is the require/define calls.) +function defineMode(name, mode) { + if (arguments.length > 2) + { mode.dependencies = Array.prototype.slice.call(arguments, 2) } + modes[name] = mode +} + +function defineMIME(mime, spec) { + mimeModes[mime] = spec +} + +// Given a MIME type, a {name, ...options} config object, or a name +// string, return a mode config object. +function resolveMode(spec) { + if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) { + spec = mimeModes[spec] + } else if (spec && typeof spec.name == "string" && mimeModes.hasOwnProperty(spec.name)) { + var found = mimeModes[spec.name] + if (typeof found == "string") { found = {name: found} } + spec = createObj(found, spec) + spec.name = found.name + } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) { + return resolveMode("application/xml") + } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+json$/.test(spec)) { + return resolveMode("application/json") + } + if (typeof spec == "string") { return {name: spec} } + else { return spec || {name: "null"} } +} + +// Given a mode spec (anything that resolveMode accepts), find and +// initialize an actual mode object. +function getMode(options, spec) { + spec = resolveMode(spec) + var mfactory = modes[spec.name] + if (!mfactory) { return getMode(options, "text/plain") } + var modeObj = mfactory(options, spec) + if (modeExtensions.hasOwnProperty(spec.name)) { + var exts = modeExtensions[spec.name] + for (var prop in exts) { + if (!exts.hasOwnProperty(prop)) { continue } + if (modeObj.hasOwnProperty(prop)) { modeObj["_" + prop] = modeObj[prop] } + modeObj[prop] = exts[prop] + } + } + modeObj.name = spec.name + if (spec.helperType) { modeObj.helperType = spec.helperType } + if (spec.modeProps) { for (var prop$1 in spec.modeProps) + { modeObj[prop$1] = spec.modeProps[prop$1] } } + + return modeObj +} + +// This can be used to attach properties to mode objects from +// outside the actual mode definition. +var modeExtensions = {} +function extendMode(mode, properties) { + var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {}) + copyObj(properties, exts) +} + +function copyState(mode, state) { + if (state === true) { return state } + if (mode.copyState) { return mode.copyState(state) } + var nstate = {} + for (var n in state) { + var val = state[n] + if (val instanceof Array) { val = val.concat([]) } + nstate[n] = val + } + return nstate +} + +// Given a mode and a state (for that mode), find the inner mode and +// state at the position that the state refers to. +function innerMode(mode, state) { + var info + while (mode.innerMode) { + info = mode.innerMode(state) + if (!info || info.mode == mode) { break } + state = info.state + mode = info.mode + } + return info || {mode: mode, state: state} +} + +function startState(mode, a1, a2) { + return mode.startState ? mode.startState(a1, a2) : true +} + +// STRING STREAM + +// Fed to the mode parsers, provides helper functions to make +// parsers more succinct. + +var StringStream = function(string, tabSize) { + this.pos = this.start = 0 + this.string = string + this.tabSize = tabSize || 8 + this.lastColumnPos = this.lastColumnValue = 0 + this.lineStart = 0 +} + +StringStream.prototype = { + eol: function() {return this.pos >= this.string.length}, + sol: function() {return this.pos == this.lineStart}, + peek: function() {return this.string.charAt(this.pos) || undefined}, + next: function() { + if (this.pos < this.string.length) + { return this.string.charAt(this.pos++) } + }, + eat: function(match) { + var ch = this.string.charAt(this.pos) + var ok + if (typeof match == "string") { ok = ch == match } + else { ok = ch && (match.test ? match.test(ch) : match(ch)) } + if (ok) {++this.pos; return ch} + }, + eatWhile: function(match) { + var start = this.pos + while (this.eat(match)){} + return this.pos > start + }, + eatSpace: function() { + var this$1 = this; + + var start = this.pos + while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) { ++this$1.pos } + return this.pos > start + }, + skipToEnd: function() {this.pos = this.string.length}, + skipTo: function(ch) { + var found = this.string.indexOf(ch, this.pos) + if (found > -1) {this.pos = found; return true} + }, + backUp: function(n) {this.pos -= n}, + column: function() { + if (this.lastColumnPos < this.start) { + this.lastColumnValue = countColumn(this.string, this.start, this.tabSize, this.lastColumnPos, this.lastColumnValue) + this.lastColumnPos = this.start + } + return this.lastColumnValue - (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0) + }, + indentation: function() { + return countColumn(this.string, null, this.tabSize) - + (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0) + }, + match: function(pattern, consume, caseInsensitive) { + if (typeof pattern == "string") { + var cased = function (str) { return caseInsensitive ? str.toLowerCase() : str; } + var substr = this.string.substr(this.pos, pattern.length) + if (cased(substr) == cased(pattern)) { + if (consume !== false) { this.pos += pattern.length } + return true + } + } else { + var match = this.string.slice(this.pos).match(pattern) + if (match && match.index > 0) { return null } + if (match && consume !== false) { this.pos += match[0].length } + return match + } + }, + current: function(){return this.string.slice(this.start, this.pos)}, + hideFirstChars: function(n, inner) { + this.lineStart += n + try { return inner() } + finally { this.lineStart -= n } + } +} + +// Compute a style array (an array starting with a mode generation +// -- for invalidation -- followed by pairs of end positions and +// style strings), which is used to highlight the tokens on the +// line. +function highlightLine(cm, line, state, forceToEnd) { + // A styles array always starts with a number identifying the + // mode/overlays that it is based on (for easy invalidation). + var st = [cm.state.modeGen], lineClasses = {} + // Compute the base array of styles + runMode(cm, line.text, cm.doc.mode, state, function (end, style) { return st.push(end, style); }, + lineClasses, forceToEnd) + + // Run overlays, adjust style array. + var loop = function ( o ) { + var overlay = cm.state.overlays[o], i = 1, at = 0 + runMode(cm, line.text, overlay.mode, true, function (end, style) { + var start = i + // Ensure there's a token end at the current position, and that i points at it + while (at < end) { + var i_end = st[i] + if (i_end > end) + { st.splice(i, 1, end, st[i+1], i_end) } + i += 2 + at = Math.min(end, i_end) + } + if (!style) { return } + if (overlay.opaque) { + st.splice(start, i - start, end, "overlay " + style) + i = start + 2 + } else { + for (; start < i; start += 2) { + var cur = st[start+1] + st[start+1] = (cur ? cur + " " : "") + "overlay " + style + } + } + }, lineClasses) + }; + + for (var o = 0; o < cm.state.overlays.length; ++o) loop( o ); + + return {styles: st, classes: lineClasses.bgClass || lineClasses.textClass ? lineClasses : null} +} + +function getLineStyles(cm, line, updateFrontier) { + if (!line.styles || line.styles[0] != cm.state.modeGen) { + var state = getStateBefore(cm, lineNo(line)) + var result = highlightLine(cm, line, line.text.length > cm.options.maxHighlightLength ? copyState(cm.doc.mode, state) : state) + line.stateAfter = state + line.styles = result.styles + if (result.classes) { line.styleClasses = result.classes } + else if (line.styleClasses) { line.styleClasses = null } + if (updateFrontier === cm.doc.frontier) { cm.doc.frontier++ } + } + return line.styles +} + +function getStateBefore(cm, n, precise) { + var doc = cm.doc, display = cm.display + if (!doc.mode.startState) { return true } + var pos = findStartLine(cm, n, precise), state = pos > doc.first && getLine(doc, pos-1).stateAfter + if (!state) { state = startState(doc.mode) } + else { state = copyState(doc.mode, state) } + doc.iter(pos, n, function (line) { + processLine(cm, line.text, state) + var save = pos == n - 1 || pos % 5 == 0 || pos >= display.viewFrom && pos < display.viewTo + line.stateAfter = save ? copyState(doc.mode, state) : null + ++pos + }) + if (precise) { doc.frontier = pos } + return state +} + +// Lightweight form of highlight -- proceed over this line and +// update state, but don't save a style array. Used for lines that +// aren't currently visible. +function processLine(cm, text, state, startAt) { + var mode = cm.doc.mode + var stream = new StringStream(text, cm.options.tabSize) + stream.start = stream.pos = startAt || 0 + if (text == "") { callBlankLine(mode, state) } + while (!stream.eol()) { + readToken(mode, stream, state) + stream.start = stream.pos + } +} + +function callBlankLine(mode, state) { + if (mode.blankLine) { return mode.blankLine(state) } + if (!mode.innerMode) { return } + var inner = innerMode(mode, state) + if (inner.mode.blankLine) { return inner.mode.blankLine(inner.state) } +} + +function readToken(mode, stream, state, inner) { + for (var i = 0; i < 10; i++) { + if (inner) { inner[0] = innerMode(mode, state).mode } + var style = mode.token(stream, state) + if (stream.pos > stream.start) { return style } + } + throw new Error("Mode " + mode.name + " failed to advance stream.") +} + +// Utility for getTokenAt and getLineTokens +function takeToken(cm, pos, precise, asArray) { + var getObj = function (copy) { return ({ + start: stream.start, end: stream.pos, + string: stream.current(), + type: style || null, + state: copy ? copyState(doc.mode, state) : state + }); } + + var doc = cm.doc, mode = doc.mode, style + pos = clipPos(doc, pos) + var line = getLine(doc, pos.line), state = getStateBefore(cm, pos.line, precise) + var stream = new StringStream(line.text, cm.options.tabSize), tokens + if (asArray) { tokens = [] } + while ((asArray || stream.pos < pos.ch) && !stream.eol()) { + stream.start = stream.pos + style = readToken(mode, stream, state) + if (asArray) { tokens.push(getObj(true)) } + } + return asArray ? tokens : getObj() +} + +function extractLineClasses(type, output) { + if (type) { for (;;) { + var lineClass = type.match(/(?:^|\s+)line-(background-)?(\S+)/) + if (!lineClass) { break } + type = type.slice(0, lineClass.index) + type.slice(lineClass.index + lineClass[0].length) + var prop = lineClass[1] ? "bgClass" : "textClass" + if (output[prop] == null) + { output[prop] = lineClass[2] } + else if (!(new RegExp("(?:^|\s)" + lineClass[2] + "(?:$|\s)")).test(output[prop])) + { output[prop] += " " + lineClass[2] } + } } + return type +} + +// Run the given mode's parser over a line, calling f for each token. +function runMode(cm, text, mode, state, f, lineClasses, forceToEnd) { + var flattenSpans = mode.flattenSpans + if (flattenSpans == null) { flattenSpans = cm.options.flattenSpans } + var curStart = 0, curStyle = null + var stream = new StringStream(text, cm.options.tabSize), style + var inner = cm.options.addModeClass && [null] + if (text == "") { extractLineClasses(callBlankLine(mode, state), lineClasses) } + while (!stream.eol()) { + if (stream.pos > cm.options.maxHighlightLength) { + flattenSpans = false + if (forceToEnd) { processLine(cm, text, state, stream.pos) } + stream.pos = text.length + style = null + } else { + style = extractLineClasses(readToken(mode, stream, state, inner), lineClasses) + } + if (inner) { + var mName = inner[0].name + if (mName) { style = "m-" + (style ? mName + " " + style : mName) } + } + if (!flattenSpans || curStyle != style) { + while (curStart < stream.start) { + curStart = Math.min(stream.start, curStart + 5000) + f(curStart, curStyle) + } + curStyle = style + } + stream.start = stream.pos + } + while (curStart < stream.pos) { + // Webkit seems to refuse to render text nodes longer than 57444 + // characters, and returns inaccurate measurements in nodes + // starting around 5000 chars. + var pos = Math.min(stream.pos, curStart + 5000) + f(pos, curStyle) + curStart = pos + } +} + +// Finds the line to start with when starting a parse. Tries to +// find a line with a stateAfter, so that it can start with a +// valid state. If that fails, it returns the line with the +// smallest indentation, which tends to need the least context to +// parse correctly. +function findStartLine(cm, n, precise) { + var minindent, minline, doc = cm.doc + var lim = precise ? -1 : n - (cm.doc.mode.innerMode ? 1000 : 100) + for (var search = n; search > lim; --search) { + if (search <= doc.first) { return doc.first } + var line = getLine(doc, search - 1) + if (line.stateAfter && (!precise || search <= doc.frontier)) { return search } + var indented = countColumn(line.text, null, cm.options.tabSize) + if (minline == null || minindent > indented) { + minline = search - 1 + minindent = indented + } + } + return minline +} + +// LINE DATA STRUCTURE + +// Line objects. These hold state related to a line, including +// highlighting info (the styles array). +function Line(text, markedSpans, estimateHeight) { + this.text = text + attachMarkedSpans(this, markedSpans) + this.height = estimateHeight ? estimateHeight(this) : 1 +} +eventMixin(Line) +Line.prototype.lineNo = function() { return lineNo(this) } + +// Change the content (text, markers) of a line. Automatically +// invalidates cached information and tries to re-estimate the +// line's height. +function updateLine(line, text, markedSpans, estimateHeight) { + line.text = text + if (line.stateAfter) { line.stateAfter = null } + if (line.styles) { line.styles = null } + if (line.order != null) { line.order = null } + detachMarkedSpans(line) + attachMarkedSpans(line, markedSpans) + var estHeight = estimateHeight ? estimateHeight(line) : 1 + if (estHeight != line.height) { updateLineHeight(line, estHeight) } +} + +// Detach a line from the document tree and its markers. +function cleanUpLine(line) { + line.parent = null + detachMarkedSpans(line) +} + +// Convert a style as returned by a mode (either null, or a string +// containing one or more styles) to a CSS style. This is cached, +// and also looks for line-wide styles. +var styleToClassCache = {}; +var styleToClassCacheWithMode = {}; +function interpretTokenStyle(style, options) { + if (!style || /^\s*$/.test(style)) { return null } + var cache = options.addModeClass ? styleToClassCacheWithMode : styleToClassCache + return cache[style] || + (cache[style] = style.replace(/\S+/g, "cm-$&")) +} + +// Render the DOM representation of the text of a line. Also builds +// up a 'line map', which points at the DOM nodes that represent +// specific stretches of text, and is used by the measuring code. +// The returned object contains the DOM node, this map, and +// information about line-wide styles that were set by the mode. +function buildLineContent(cm, lineView) { + // The padding-right forces the element to have a 'border', which + // is needed on Webkit to be able to get line-level bounding + // rectangles for it (in measureChar). + var content = elt("span", null, null, webkit ? "padding-right: .1px" : null) + var builder = {pre: elt("pre", [content], "CodeMirror-line"), content: content, + col: 0, pos: 0, cm: cm, + trailingSpace: false, + splitSpaces: (ie || webkit) && cm.getOption("lineWrapping")} + // hide from accessibility tree + content.setAttribute("role", "presentation") + builder.pre.setAttribute("role", "presentation") + lineView.measure = {} + + // Iterate over the logical lines that make up this visual line. + for (var i = 0; i <= (lineView.rest ? lineView.rest.length : 0); i++) { + var line = i ? lineView.rest[i - 1] : lineView.line, order = (void 0) + builder.pos = 0 + builder.addToken = buildToken + // Optionally wire in some hacks into the token-rendering + // algorithm, to deal with browser quirks. + if (hasBadBidiRects(cm.display.measure) && (order = getOrder(line))) + { builder.addToken = buildTokenBadBidi(builder.addToken, order) } + builder.map = [] + var allowFrontierUpdate = lineView != cm.display.externalMeasured && lineNo(line) + insertLineContent(line, builder, getLineStyles(cm, line, allowFrontierUpdate)) + if (line.styleClasses) { + if (line.styleClasses.bgClass) + { builder.bgClass = joinClasses(line.styleClasses.bgClass, builder.bgClass || "") } + if (line.styleClasses.textClass) + { builder.textClass = joinClasses(line.styleClasses.textClass, builder.textClass || "") } + } + + // Ensure at least a single node is present, for measuring. + if (builder.map.length == 0) + { builder.map.push(0, 0, builder.content.appendChild(zeroWidthElement(cm.display.measure))) } + + // Store the map and a cache object for the current logical line + if (i == 0) { + lineView.measure.map = builder.map + lineView.measure.cache = {} + } else { + ;(lineView.measure.maps || (lineView.measure.maps = [])).push(builder.map) + ;(lineView.measure.caches || (lineView.measure.caches = [])).push({}) + } + } + + // See issue #2901 + if (webkit) { + var last = builder.content.lastChild + if (/\bcm-tab\b/.test(last.className) || (last.querySelector && last.querySelector(".cm-tab"))) + { builder.content.className = "cm-tab-wrap-hack" } + } + + signal(cm, "renderLine", cm, lineView.line, builder.pre) + if (builder.pre.className) + { builder.textClass = joinClasses(builder.pre.className, builder.textClass || "") } + + return builder +} + +function defaultSpecialCharPlaceholder(ch) { + var token = elt("span", "\u2022", "cm-invalidchar") + token.title = "\\u" + ch.charCodeAt(0).toString(16) + token.setAttribute("aria-label", token.title) + return token +} + +// Build up the DOM representation for a single token, and add it to +// the line map. Takes care to render special characters separately. +function buildToken(builder, text, style, startStyle, endStyle, title, css) { + if (!text) { return } + var displayText = builder.splitSpaces ? splitSpaces(text, builder.trailingSpace) : text + var special = builder.cm.state.specialChars, mustWrap = false + var content + if (!special.test(text)) { + builder.col += text.length + content = document.createTextNode(displayText) + builder.map.push(builder.pos, builder.pos + text.length, content) + if (ie && ie_version < 9) { mustWrap = true } + builder.pos += text.length + } else { + content = document.createDocumentFragment() + var pos = 0 + while (true) { + special.lastIndex = pos + var m = special.exec(text) + var skipped = m ? m.index - pos : text.length - pos + if (skipped) { + var txt = document.createTextNode(displayText.slice(pos, pos + skipped)) + if (ie && ie_version < 9) { content.appendChild(elt("span", [txt])) } + else { content.appendChild(txt) } + builder.map.push(builder.pos, builder.pos + skipped, txt) + builder.col += skipped + builder.pos += skipped + } + if (!m) { break } + pos += skipped + 1 + var txt$1 = (void 0) + if (m[0] == "\t") { + var tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder.col % tabSize + txt$1 = content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab")) + txt$1.setAttribute("role", "presentation") + txt$1.setAttribute("cm-text", "\t") + builder.col += tabWidth + } else if (m[0] == "\r" || m[0] == "\n") { + txt$1 = content.appendChild(elt("span", m[0] == "\r" ? "\u240d" : "\u2424", "cm-invalidchar")) + txt$1.setAttribute("cm-text", m[0]) + builder.col += 1 + } else { + txt$1 = builder.cm.options.specialCharPlaceholder(m[0]) + txt$1.setAttribute("cm-text", m[0]) + if (ie && ie_version < 9) { content.appendChild(elt("span", [txt$1])) } + else { content.appendChild(txt$1) } + builder.col += 1 + } + builder.map.push(builder.pos, builder.pos + 1, txt$1) + builder.pos++ + } + } + builder.trailingSpace = displayText.charCodeAt(text.length - 1) == 32 + if (style || startStyle || endStyle || mustWrap || css) { + var fullStyle = style || "" + if (startStyle) { fullStyle += startStyle } + if (endStyle) { fullStyle += endStyle } + var token = elt("span", [content], fullStyle, css) + if (title) { token.title = title } + return builder.content.appendChild(token) + } + builder.content.appendChild(content) +} + +function splitSpaces(text, trailingBefore) { + if (text.length > 1 && !/ /.test(text)) { return text } + var spaceBefore = trailingBefore, result = "" + for (var i = 0; i < text.length; i++) { + var ch = text.charAt(i) + if (ch == " " && spaceBefore && (i == text.length - 1 || text.charCodeAt(i + 1) == 32)) + { ch = "\u00a0" } + result += ch + spaceBefore = ch == " " + } + return result +} + +// Work around nonsense dimensions being reported for stretches of +// right-to-left text. +function buildTokenBadBidi(inner, order) { + return function (builder, text, style, startStyle, endStyle, title, css) { + style = style ? style + " cm-force-border" : "cm-force-border" + var start = builder.pos, end = start + text.length + for (;;) { + // Find the part that overlaps with the start of this text + var part = (void 0) + for (var i = 0; i < order.length; i++) { + part = order[i] + if (part.to > start && part.from <= start) { break } + } + if (part.to >= end) { return inner(builder, text, style, startStyle, endStyle, title, css) } + inner(builder, text.slice(0, part.to - start), style, startStyle, null, title, css) + startStyle = null + text = text.slice(part.to - start) + start = part.to + } + } +} + +function buildCollapsedSpan(builder, size, marker, ignoreWidget) { + var widget = !ignoreWidget && marker.widgetNode + if (widget) { builder.map.push(builder.pos, builder.pos + size, widget) } + if (!ignoreWidget && builder.cm.display.input.needsContentAttribute) { + if (!widget) + { widget = builder.content.appendChild(document.createElement("span")) } + widget.setAttribute("cm-marker", marker.id) + } + if (widget) { + builder.cm.display.input.setUneditable(widget) + builder.content.appendChild(widget) + } + builder.pos += size + builder.trailingSpace = false +} + +// Outputs a number of spans to make up a line, taking highlighting +// and marked text into account. +function insertLineContent(line, builder, styles) { + var spans = line.markedSpans, allText = line.text, at = 0 + if (!spans) { + for (var i$1 = 1; i$1 < styles.length; i$1+=2) + { builder.addToken(builder, allText.slice(at, at = styles[i$1]), interpretTokenStyle(styles[i$1+1], builder.cm.options)) } + return + } + + var len = allText.length, pos = 0, i = 1, text = "", style, css + var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, title, collapsed + for (;;) { + if (nextChange == pos) { // Update current marker set + spanStyle = spanEndStyle = spanStartStyle = title = css = "" + collapsed = null; nextChange = Infinity + var foundBookmarks = [], endStyles = (void 0) + for (var j = 0; j < spans.length; ++j) { + var sp = spans[j], m = sp.marker + if (m.type == "bookmark" && sp.from == pos && m.widgetNode) { + foundBookmarks.push(m) + } else if (sp.from <= pos && (sp.to == null || sp.to > pos || m.collapsed && sp.to == pos && sp.from == pos)) { + if (sp.to != null && sp.to != pos && nextChange > sp.to) { + nextChange = sp.to + spanEndStyle = "" + } + if (m.className) { spanStyle += " " + m.className } + if (m.css) { css = (css ? css + ";" : "") + m.css } + if (m.startStyle && sp.from == pos) { spanStartStyle += " " + m.startStyle } + if (m.endStyle && sp.to == nextChange) { (endStyles || (endStyles = [])).push(m.endStyle, sp.to) } + if (m.title && !title) { title = m.title } + if (m.collapsed && (!collapsed || compareCollapsedMarkers(collapsed.marker, m) < 0)) + { collapsed = sp } + } else if (sp.from > pos && nextChange > sp.from) { + nextChange = sp.from + } + } + if (endStyles) { for (var j$1 = 0; j$1 < endStyles.length; j$1 += 2) + { if (endStyles[j$1 + 1] == nextChange) { spanEndStyle += " " + endStyles[j$1] } } } + + if (!collapsed || collapsed.from == pos) { for (var j$2 = 0; j$2 < foundBookmarks.length; ++j$2) + { buildCollapsedSpan(builder, 0, foundBookmarks[j$2]) } } + if (collapsed && (collapsed.from || 0) == pos) { + buildCollapsedSpan(builder, (collapsed.to == null ? len + 1 : collapsed.to) - pos, + collapsed.marker, collapsed.from == null) + if (collapsed.to == null) { return } + if (collapsed.to == pos) { collapsed = false } + } + } + if (pos >= len) { break } + + var upto = Math.min(len, nextChange) + while (true) { + if (text) { + var end = pos + text.length + if (!collapsed) { + var tokenText = end > upto ? text.slice(0, upto - pos) : text + builder.addToken(builder, tokenText, style ? style + spanStyle : spanStyle, + spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : "", title, css) + } + if (end >= upto) {text = text.slice(upto - pos); pos = upto; break} + pos = end + spanStartStyle = "" + } + text = allText.slice(at, at = styles[i++]) + style = interpretTokenStyle(styles[i++], builder.cm.options) + } + } +} + + +// These objects are used to represent the visible (currently drawn) +// part of the document. A LineView may correspond to multiple +// logical lines, if those are connected by collapsed ranges. +function LineView(doc, line, lineN) { + // The starting line + this.line = line + // Continuing lines, if any + this.rest = visualLineContinued(line) + // Number of logical lines in this visual line + this.size = this.rest ? lineNo(lst(this.rest)) - lineN + 1 : 1 + this.node = this.text = null + this.hidden = lineIsHidden(doc, line) +} + +// Create a range of LineView objects for the given lines. +function buildViewArray(cm, from, to) { + var array = [], nextPos + for (var pos = from; pos < to; pos = nextPos) { + var view = new LineView(cm.doc, getLine(cm.doc, pos), pos) + nextPos = pos + view.size + array.push(view) + } + return array +} + +var operationGroup = null + +function pushOperation(op) { + if (operationGroup) { + operationGroup.ops.push(op) + } else { + op.ownsGroup = operationGroup = { + ops: [op], + delayedCallbacks: [] + } + } +} + +function fireCallbacksForOps(group) { + // Calls delayed callbacks and cursorActivity handlers until no + // new ones appear + var callbacks = group.delayedCallbacks, i = 0 + do { + for (; i < callbacks.length; i++) + { callbacks[i].call(null) } + for (var j = 0; j < group.ops.length; j++) { + var op = group.ops[j] + if (op.cursorActivityHandlers) + { while (op.cursorActivityCalled < op.cursorActivityHandlers.length) + { op.cursorActivityHandlers[op.cursorActivityCalled++].call(null, op.cm) } } + } + } while (i < callbacks.length) +} + +function finishOperation(op, endCb) { + var group = op.ownsGroup + if (!group) { return } + + try { fireCallbacksForOps(group) } + finally { + operationGroup = null + endCb(group) + } +} + +var orphanDelayedCallbacks = null + +// Often, we want to signal events at a point where we are in the +// middle of some work, but don't want the handler to start calling +// other methods on the editor, which might be in an inconsistent +// state or simply not expect any other events to happen. +// signalLater looks whether there are any handlers, and schedules +// them to be executed when the last operation ends, or, if no +// operation is active, when a timeout fires. +function signalLater(emitter, type /*, values...*/) { + var arr = getHandlers(emitter, type) + if (!arr.length) { return } + var args = Array.prototype.slice.call(arguments, 2), list + if (operationGroup) { + list = operationGroup.delayedCallbacks + } else if (orphanDelayedCallbacks) { + list = orphanDelayedCallbacks + } else { + list = orphanDelayedCallbacks = [] + setTimeout(fireOrphanDelayed, 0) + } + var loop = function ( i ) { + list.push(function () { return arr[i].apply(null, args); }) + }; + + for (var i = 0; i < arr.length; ++i) + loop( i ); +} + +function fireOrphanDelayed() { + var delayed = orphanDelayedCallbacks + orphanDelayedCallbacks = null + for (var i = 0; i < delayed.length; ++i) { delayed[i]() } +} + +// When an aspect of a line changes, a string is added to +// lineView.changes. This updates the relevant part of the line's +// DOM structure. +function updateLineForChanges(cm, lineView, lineN, dims) { + for (var j = 0; j < lineView.changes.length; j++) { + var type = lineView.changes[j] + if (type == "text") { updateLineText(cm, lineView) } + else if (type == "gutter") { updateLineGutter(cm, lineView, lineN, dims) } + else if (type == "class") { updateLineClasses(lineView) } + else if (type == "widget") { updateLineWidgets(cm, lineView, dims) } + } + lineView.changes = null +} + +// Lines with gutter elements, widgets or a background class need to +// be wrapped, and have the extra elements added to the wrapper div +function ensureLineWrapped(lineView) { + if (lineView.node == lineView.text) { + lineView.node = elt("div", null, null, "position: relative") + if (lineView.text.parentNode) + { lineView.text.parentNode.replaceChild(lineView.node, lineView.text) } + lineView.node.appendChild(lineView.text) + if (ie && ie_version < 8) { lineView.node.style.zIndex = 2 } + } + return lineView.node +} + +function updateLineBackground(lineView) { + var cls = lineView.bgClass ? lineView.bgClass + " " + (lineView.line.bgClass || "") : lineView.line.bgClass + if (cls) { cls += " CodeMirror-linebackground" } + if (lineView.background) { + if (cls) { lineView.background.className = cls } + else { lineView.background.parentNode.removeChild(lineView.background); lineView.background = null } + } else if (cls) { + var wrap = ensureLineWrapped(lineView) + lineView.background = wrap.insertBefore(elt("div", null, cls), wrap.firstChild) + } +} + +// Wrapper around buildLineContent which will reuse the structure +// in display.externalMeasured when possible. +function getLineContent(cm, lineView) { + var ext = cm.display.externalMeasured + if (ext && ext.line == lineView.line) { + cm.display.externalMeasured = null + lineView.measure = ext.measure + return ext.built + } + return buildLineContent(cm, lineView) +} + +// Redraw the line's text. Interacts with the background and text +// classes because the mode may output tokens that influence these +// classes. +function updateLineText(cm, lineView) { + var cls = lineView.text.className + var built = getLineContent(cm, lineView) + if (lineView.text == lineView.node) { lineView.node = built.pre } + lineView.text.parentNode.replaceChild(built.pre, lineView.text) + lineView.text = built.pre + if (built.bgClass != lineView.bgClass || built.textClass != lineView.textClass) { + lineView.bgClass = built.bgClass + lineView.textClass = built.textClass + updateLineClasses(lineView) + } else if (cls) { + lineView.text.className = cls + } +} + +function updateLineClasses(lineView) { + updateLineBackground(lineView) + if (lineView.line.wrapClass) + { ensureLineWrapped(lineView).className = lineView.line.wrapClass } + else if (lineView.node != lineView.text) + { lineView.node.className = "" } + var textClass = lineView.textClass ? lineView.textClass + " " + (lineView.line.textClass || "") : lineView.line.textClass + lineView.text.className = textClass || "" +} + +function updateLineGutter(cm, lineView, lineN, dims) { + if (lineView.gutter) { + lineView.node.removeChild(lineView.gutter) + lineView.gutter = null + } + if (lineView.gutterBackground) { + lineView.node.removeChild(lineView.gutterBackground) + lineView.gutterBackground = null + } + if (lineView.line.gutterClass) { + var wrap = ensureLineWrapped(lineView) + lineView.gutterBackground = elt("div", null, "CodeMirror-gutter-background " + lineView.line.gutterClass, + ("left: " + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px; width: " + (dims.gutterTotalWidth) + "px")) + wrap.insertBefore(lineView.gutterBackground, lineView.text) + } + var markers = lineView.line.gutterMarkers + if (cm.options.lineNumbers || markers) { + var wrap$1 = ensureLineWrapped(lineView) + var gutterWrap = lineView.gutter = elt("div", null, "CodeMirror-gutter-wrapper", ("left: " + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px")) + cm.display.input.setUneditable(gutterWrap) + wrap$1.insertBefore(gutterWrap, lineView.text) + if (lineView.line.gutterClass) + { gutterWrap.className += " " + lineView.line.gutterClass } + if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"])) + { lineView.lineNumber = gutterWrap.appendChild( + elt("div", lineNumberFor(cm.options, lineN), + "CodeMirror-linenumber CodeMirror-gutter-elt", + ("left: " + (dims.gutterLeft["CodeMirror-linenumbers"]) + "px; width: " + (cm.display.lineNumInnerWidth) + "px"))) } + if (markers) { for (var k = 0; k < cm.options.gutters.length; ++k) { + var id = cm.options.gutters[k], found = markers.hasOwnProperty(id) && markers[id] + if (found) + { gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt", + ("left: " + (dims.gutterLeft[id]) + "px; width: " + (dims.gutterWidth[id]) + "px"))) } + } } + } +} + +function updateLineWidgets(cm, lineView, dims) { + if (lineView.alignable) { lineView.alignable = null } + for (var node = lineView.node.firstChild, next = (void 0); node; node = next) { + next = node.nextSibling + if (node.className == "CodeMirror-linewidget") + { lineView.node.removeChild(node) } + } + insertLineWidgets(cm, lineView, dims) +} + +// Build a line's DOM representation from scratch +function buildLineElement(cm, lineView, lineN, dims) { + var built = getLineContent(cm, lineView) + lineView.text = lineView.node = built.pre + if (built.bgClass) { lineView.bgClass = built.bgClass } + if (built.textClass) { lineView.textClass = built.textClass } + + updateLineClasses(lineView) + updateLineGutter(cm, lineView, lineN, dims) + insertLineWidgets(cm, lineView, dims) + return lineView.node +} + +// A lineView may contain multiple logical lines (when merged by +// collapsed spans). The widgets for all of them need to be drawn. +function insertLineWidgets(cm, lineView, dims) { + insertLineWidgetsFor(cm, lineView.line, lineView, dims, true) + if (lineView.rest) { for (var i = 0; i < lineView.rest.length; i++) + { insertLineWidgetsFor(cm, lineView.rest[i], lineView, dims, false) } } +} + +function insertLineWidgetsFor(cm, line, lineView, dims, allowAbove) { + if (!line.widgets) { return } + var wrap = ensureLineWrapped(lineView) + for (var i = 0, ws = line.widgets; i < ws.length; ++i) { + var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget") + if (!widget.handleMouseEvents) { node.setAttribute("cm-ignore-events", "true") } + positionLineWidget(widget, node, lineView, dims) + cm.display.input.setUneditable(node) + if (allowAbove && widget.above) + { wrap.insertBefore(node, lineView.gutter || lineView.text) } + else + { wrap.appendChild(node) } + signalLater(widget, "redraw") + } +} + +function positionLineWidget(widget, node, lineView, dims) { + if (widget.noHScroll) { + ;(lineView.alignable || (lineView.alignable = [])).push(node) + var width = dims.wrapperWidth + node.style.left = dims.fixedPos + "px" + if (!widget.coverGutter) { + width -= dims.gutterTotalWidth + node.style.paddingLeft = dims.gutterTotalWidth + "px" + } + node.style.width = width + "px" + } + if (widget.coverGutter) { + node.style.zIndex = 5 + node.style.position = "relative" + if (!widget.noHScroll) { node.style.marginLeft = -dims.gutterTotalWidth + "px" } + } +} + +function widgetHeight(widget) { + if (widget.height != null) { return widget.height } + var cm = widget.doc.cm + if (!cm) { return 0 } + if (!contains(document.body, widget.node)) { + var parentStyle = "position: relative;" + if (widget.coverGutter) + { parentStyle += "margin-left: -" + cm.display.gutters.offsetWidth + "px;" } + if (widget.noHScroll) + { parentStyle += "width: " + cm.display.wrapper.clientWidth + "px;" } + removeChildrenAndAdd(cm.display.measure, elt("div", [widget.node], null, parentStyle)) + } + return widget.height = widget.node.parentNode.offsetHeight +} + +// Return true when the given mouse event happened in a widget +function eventInWidget(display, e) { + for (var n = e_target(e); n != display.wrapper; n = n.parentNode) { + if (!n || (n.nodeType == 1 && n.getAttribute("cm-ignore-events") == "true") || + (n.parentNode == display.sizer && n != display.mover)) + { return true } + } +} + +// POSITION MEASUREMENT + +function paddingTop(display) {return display.lineSpace.offsetTop} +function paddingVert(display) {return display.mover.offsetHeight - display.lineSpace.offsetHeight} +function paddingH(display) { + if (display.cachedPaddingH) { return display.cachedPaddingH } + var e = removeChildrenAndAdd(display.measure, elt("pre", "x")) + var style = window.getComputedStyle ? window.getComputedStyle(e) : e.currentStyle + var data = {left: parseInt(style.paddingLeft), right: parseInt(style.paddingRight)} + if (!isNaN(data.left) && !isNaN(data.right)) { display.cachedPaddingH = data } + return data +} + +function scrollGap(cm) { return scrollerGap - cm.display.nativeBarWidth } +function displayWidth(cm) { + return cm.display.scroller.clientWidth - scrollGap(cm) - cm.display.barWidth +} +function displayHeight(cm) { + return cm.display.scroller.clientHeight - scrollGap(cm) - cm.display.barHeight +} + +// Ensure the lineView.wrapping.heights array is populated. This is +// an array of bottom offsets for the lines that make up a drawn +// line. When lineWrapping is on, there might be more than one +// height. +function ensureLineHeights(cm, lineView, rect) { + var wrapping = cm.options.lineWrapping + var curWidth = wrapping && displayWidth(cm) + if (!lineView.measure.heights || wrapping && lineView.measure.width != curWidth) { + var heights = lineView.measure.heights = [] + if (wrapping) { + lineView.measure.width = curWidth + var rects = lineView.text.firstChild.getClientRects() + for (var i = 0; i < rects.length - 1; i++) { + var cur = rects[i], next = rects[i + 1] + if (Math.abs(cur.bottom - next.bottom) > 2) + { heights.push((cur.bottom + next.top) / 2 - rect.top) } + } + } + heights.push(rect.bottom - rect.top) + } +} + +// Find a line map (mapping character offsets to text nodes) and a +// measurement cache for the given line number. (A line view might +// contain multiple lines when collapsed ranges are present.) +function mapFromLineView(lineView, line, lineN) { + if (lineView.line == line) + { return {map: lineView.measure.map, cache: lineView.measure.cache} } + for (var i = 0; i < lineView.rest.length; i++) + { if (lineView.rest[i] == line) + { return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i]} } } + for (var i$1 = 0; i$1 < lineView.rest.length; i$1++) + { if (lineNo(lineView.rest[i$1]) > lineN) + { return {map: lineView.measure.maps[i$1], cache: lineView.measure.caches[i$1], before: true} } } +} + +// Render a line into the hidden node display.externalMeasured. Used +// when measurement is needed for a line that's not in the viewport. +function updateExternalMeasurement(cm, line) { + line = visualLine(line) + var lineN = lineNo(line) + var view = cm.display.externalMeasured = new LineView(cm.doc, line, lineN) + view.lineN = lineN + var built = view.built = buildLineContent(cm, view) + view.text = built.pre + removeChildrenAndAdd(cm.display.lineMeasure, built.pre) + return view +} + +// Get a {top, bottom, left, right} box (in line-local coordinates) +// for a given character. +function measureChar(cm, line, ch, bias) { + return measureCharPrepared(cm, prepareMeasureForLine(cm, line), ch, bias) +} + +// Find a line view that corresponds to the given line number. +function findViewForLine(cm, lineN) { + if (lineN >= cm.display.viewFrom && lineN < cm.display.viewTo) + { return cm.display.view[findViewIndex(cm, lineN)] } + var ext = cm.display.externalMeasured + if (ext && lineN >= ext.lineN && lineN < ext.lineN + ext.size) + { return ext } +} + +// Measurement can be split in two steps, the set-up work that +// applies to the whole line, and the measurement of the actual +// character. Functions like coordsChar, that need to do a lot of +// measurements in a row, can thus ensure that the set-up work is +// only done once. +function prepareMeasureForLine(cm, line) { + var lineN = lineNo(line) + var view = findViewForLine(cm, lineN) + if (view && !view.text) { + view = null + } else if (view && view.changes) { + updateLineForChanges(cm, view, lineN, getDimensions(cm)) + cm.curOp.forceUpdate = true + } + if (!view) + { view = updateExternalMeasurement(cm, line) } + + var info = mapFromLineView(view, line, lineN) + return { + line: line, view: view, rect: null, + map: info.map, cache: info.cache, before: info.before, + hasHeights: false + } +} + +// Given a prepared measurement object, measures the position of an +// actual character (or fetches it from the cache). +function measureCharPrepared(cm, prepared, ch, bias, varHeight) { + if (prepared.before) { ch = -1 } + var key = ch + (bias || ""), found + if (prepared.cache.hasOwnProperty(key)) { + found = prepared.cache[key] + } else { + if (!prepared.rect) + { prepared.rect = prepared.view.text.getBoundingClientRect() } + if (!prepared.hasHeights) { + ensureLineHeights(cm, prepared.view, prepared.rect) + prepared.hasHeights = true + } + found = measureCharInner(cm, prepared, ch, bias) + if (!found.bogus) { prepared.cache[key] = found } + } + return {left: found.left, right: found.right, + top: varHeight ? found.rtop : found.top, + bottom: varHeight ? found.rbottom : found.bottom} +} + +var nullRect = {left: 0, right: 0, top: 0, bottom: 0} + +function nodeAndOffsetInLineMap(map, ch, bias) { + var node, start, end, collapse, mStart, mEnd + // First, search the line map for the text node corresponding to, + // or closest to, the target character. + for (var i = 0; i < map.length; i += 3) { + mStart = map[i] + mEnd = map[i + 1] + if (ch < mStart) { + start = 0; end = 1 + collapse = "left" + } else if (ch < mEnd) { + start = ch - mStart + end = start + 1 + } else if (i == map.length - 3 || ch == mEnd && map[i + 3] > ch) { + end = mEnd - mStart + start = end - 1 + if (ch >= mEnd) { collapse = "right" } + } + if (start != null) { + node = map[i + 2] + if (mStart == mEnd && bias == (node.insertLeft ? "left" : "right")) + { collapse = bias } + if (bias == "left" && start == 0) + { while (i && map[i - 2] == map[i - 3] && map[i - 1].insertLeft) { + node = map[(i -= 3) + 2] + collapse = "left" + } } + if (bias == "right" && start == mEnd - mStart) + { while (i < map.length - 3 && map[i + 3] == map[i + 4] && !map[i + 5].insertLeft) { + node = map[(i += 3) + 2] + collapse = "right" + } } + break + } + } + return {node: node, start: start, end: end, collapse: collapse, coverStart: mStart, coverEnd: mEnd} +} + +function getUsefulRect(rects, bias) { + var rect = nullRect + if (bias == "left") { for (var i = 0; i < rects.length; i++) { + if ((rect = rects[i]).left != rect.right) { break } + } } else { for (var i$1 = rects.length - 1; i$1 >= 0; i$1--) { + if ((rect = rects[i$1]).left != rect.right) { break } + } } + return rect +} + +function measureCharInner(cm, prepared, ch, bias) { + var place = nodeAndOffsetInLineMap(prepared.map, ch, bias) + var node = place.node, start = place.start, end = place.end, collapse = place.collapse + + var rect + if (node.nodeType == 3) { // If it is a text node, use a range to retrieve the coordinates. + for (var i$1 = 0; i$1 < 4; i$1++) { // Retry a maximum of 4 times when nonsense rectangles are returned + while (start && isExtendingChar(prepared.line.text.charAt(place.coverStart + start))) { --start } + while (place.coverStart + end < place.coverEnd && isExtendingChar(prepared.line.text.charAt(place.coverStart + end))) { ++end } + if (ie && ie_version < 9 && start == 0 && end == place.coverEnd - place.coverStart) + { rect = node.parentNode.getBoundingClientRect() } + else + { rect = getUsefulRect(range(node, start, end).getClientRects(), bias) } + if (rect.left || rect.right || start == 0) { break } + end = start + start = start - 1 + collapse = "right" + } + if (ie && ie_version < 11) { rect = maybeUpdateRectForZooming(cm.display.measure, rect) } + } else { // If it is a widget, simply get the box for the whole widget. + if (start > 0) { collapse = bias = "right" } + var rects + if (cm.options.lineWrapping && (rects = node.getClientRects()).length > 1) + { rect = rects[bias == "right" ? rects.length - 1 : 0] } + else + { rect = node.getBoundingClientRect() } + } + if (ie && ie_version < 9 && !start && (!rect || !rect.left && !rect.right)) { + var rSpan = node.parentNode.getClientRects()[0] + if (rSpan) + { rect = {left: rSpan.left, right: rSpan.left + charWidth(cm.display), top: rSpan.top, bottom: rSpan.bottom} } + else + { rect = nullRect } + } + + var rtop = rect.top - prepared.rect.top, rbot = rect.bottom - prepared.rect.top + var mid = (rtop + rbot) / 2 + var heights = prepared.view.measure.heights + var i = 0 + for (; i < heights.length - 1; i++) + { if (mid < heights[i]) { break } } + var top = i ? heights[i - 1] : 0, bot = heights[i] + var result = {left: (collapse == "right" ? rect.right : rect.left) - prepared.rect.left, + right: (collapse == "left" ? rect.left : rect.right) - prepared.rect.left, + top: top, bottom: bot} + if (!rect.left && !rect.right) { result.bogus = true } + if (!cm.options.singleCursorHeightPerLine) { result.rtop = rtop; result.rbottom = rbot } + + return result +} + +// Work around problem with bounding client rects on ranges being +// returned incorrectly when zoomed on IE10 and below. +function maybeUpdateRectForZooming(measure, rect) { + if (!window.screen || screen.logicalXDPI == null || + screen.logicalXDPI == screen.deviceXDPI || !hasBadZoomedRects(measure)) + { return rect } + var scaleX = screen.logicalXDPI / screen.deviceXDPI + var scaleY = screen.logicalYDPI / screen.deviceYDPI + return {left: rect.left * scaleX, right: rect.right * scaleX, + top: rect.top * scaleY, bottom: rect.bottom * scaleY} +} + +function clearLineMeasurementCacheFor(lineView) { + if (lineView.measure) { + lineView.measure.cache = {} + lineView.measure.heights = null + if (lineView.rest) { for (var i = 0; i < lineView.rest.length; i++) + { lineView.measure.caches[i] = {} } } + } +} + +function clearLineMeasurementCache(cm) { + cm.display.externalMeasure = null + removeChildren(cm.display.lineMeasure) + for (var i = 0; i < cm.display.view.length; i++) + { clearLineMeasurementCacheFor(cm.display.view[i]) } +} + +function clearCaches(cm) { + clearLineMeasurementCache(cm) + cm.display.cachedCharWidth = cm.display.cachedTextHeight = cm.display.cachedPaddingH = null + if (!cm.options.lineWrapping) { cm.display.maxLineChanged = true } + cm.display.lineNumChars = null +} + +function pageScrollX() { return window.pageXOffset || (document.documentElement || document.body).scrollLeft } +function pageScrollY() { return window.pageYOffset || (document.documentElement || document.body).scrollTop } + +// Converts a {top, bottom, left, right} box from line-local +// coordinates into another coordinate system. Context may be one of +// "line", "div" (display.lineDiv), "local"./null (editor), "window", +// or "page". +function intoCoordSystem(cm, lineObj, rect, context, includeWidgets) { + if (!includeWidgets && lineObj.widgets) { for (var i = 0; i < lineObj.widgets.length; ++i) { if (lineObj.widgets[i].above) { + var size = widgetHeight(lineObj.widgets[i]) + rect.top += size; rect.bottom += size + } } } + if (context == "line") { return rect } + if (!context) { context = "local" } + var yOff = heightAtLine(lineObj) + if (context == "local") { yOff += paddingTop(cm.display) } + else { yOff -= cm.display.viewOffset } + if (context == "page" || context == "window") { + var lOff = cm.display.lineSpace.getBoundingClientRect() + yOff += lOff.top + (context == "window" ? 0 : pageScrollY()) + var xOff = lOff.left + (context == "window" ? 0 : pageScrollX()) + rect.left += xOff; rect.right += xOff + } + rect.top += yOff; rect.bottom += yOff + return rect +} + +// Coverts a box from "div" coords to another coordinate system. +// Context may be "window", "page", "div", or "local"./null. +function fromCoordSystem(cm, coords, context) { + if (context == "div") { return coords } + var left = coords.left, top = coords.top + // First move into "page" coordinate system + if (context == "page") { + left -= pageScrollX() + top -= pageScrollY() + } else if (context == "local" || !context) { + var localBox = cm.display.sizer.getBoundingClientRect() + left += localBox.left + top += localBox.top + } + + var lineSpaceBox = cm.display.lineSpace.getBoundingClientRect() + return {left: left - lineSpaceBox.left, top: top - lineSpaceBox.top} +} + +function charCoords(cm, pos, context, lineObj, bias) { + if (!lineObj) { lineObj = getLine(cm.doc, pos.line) } + return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, bias), context) +} + +// Returns a box for a given cursor position, which may have an +// 'other' property containing the position of the secondary cursor +// on a bidi boundary. +function cursorCoords(cm, pos, context, lineObj, preparedMeasure, varHeight) { + lineObj = lineObj || getLine(cm.doc, pos.line) + if (!preparedMeasure) { preparedMeasure = prepareMeasureForLine(cm, lineObj) } + function get(ch, right) { + var m = measureCharPrepared(cm, preparedMeasure, ch, right ? "right" : "left", varHeight) + if (right) { m.left = m.right; } else { m.right = m.left } + return intoCoordSystem(cm, lineObj, m, context) + } + function getBidi(ch, partPos) { + var part = order[partPos], right = part.level % 2 + if (ch == bidiLeft(part) && partPos && part.level < order[partPos - 1].level) { + part = order[--partPos] + ch = bidiRight(part) - (part.level % 2 ? 0 : 1) + right = true + } else if (ch == bidiRight(part) && partPos < order.length - 1 && part.level < order[partPos + 1].level) { + part = order[++partPos] + ch = bidiLeft(part) - part.level % 2 + right = false + } + if (right && ch == part.to && ch > part.from) { return get(ch - 1) } + return get(ch, right) + } + var order = getOrder(lineObj), ch = pos.ch + if (!order) { return get(ch) } + var partPos = getBidiPartAt(order, ch) + var val = getBidi(ch, partPos) + if (bidiOther != null) { val.other = getBidi(ch, bidiOther) } + return val +} + +// Used to cheaply estimate the coordinates for a position. Used for +// intermediate scroll updates. +function estimateCoords(cm, pos) { + var left = 0 + pos = clipPos(cm.doc, pos) + if (!cm.options.lineWrapping) { left = charWidth(cm.display) * pos.ch } + var lineObj = getLine(cm.doc, pos.line) + var top = heightAtLine(lineObj) + paddingTop(cm.display) + return {left: left, right: left, top: top, bottom: top + lineObj.height} +} + +// Positions returned by coordsChar contain some extra information. +// xRel is the relative x position of the input coordinates compared +// to the found position (so xRel > 0 means the coordinates are to +// the right of the character position, for example). When outside +// is true, that means the coordinates lie outside the line's +// vertical range. +function PosWithInfo(line, ch, outside, xRel) { + var pos = Pos(line, ch) + pos.xRel = xRel + if (outside) { pos.outside = true } + return pos +} + +// Compute the character position closest to the given coordinates. +// Input must be lineSpace-local ("div" coordinate system). +function coordsChar(cm, x, y) { + var doc = cm.doc + y += cm.display.viewOffset + if (y < 0) { return PosWithInfo(doc.first, 0, true, -1) } + var lineN = lineAtHeight(doc, y), last = doc.first + doc.size - 1 + if (lineN > last) + { return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, true, 1) } + if (x < 0) { x = 0 } + + var lineObj = getLine(doc, lineN) + for (;;) { + var found = coordsCharInner(cm, lineObj, lineN, x, y) + var merged = collapsedSpanAtEnd(lineObj) + var mergedPos = merged && merged.find(0, true) + if (merged && (found.ch > mergedPos.from.ch || found.ch == mergedPos.from.ch && found.xRel > 0)) + { lineN = lineNo(lineObj = mergedPos.to.line) } + else + { return found } + } +} + +function coordsCharInner(cm, lineObj, lineNo, x, y) { + var innerOff = y - heightAtLine(lineObj) + var wrongLine = false, adjust = 2 * cm.display.wrapper.clientWidth + var preparedMeasure = prepareMeasureForLine(cm, lineObj) + + function getX(ch) { + var sp = cursorCoords(cm, Pos(lineNo, ch), "line", lineObj, preparedMeasure) + wrongLine = true + if (innerOff > sp.bottom) { return sp.left - adjust } + else if (innerOff < sp.top) { return sp.left + adjust } + else { wrongLine = false } + return sp.left + } + + var bidi = getOrder(lineObj), dist = lineObj.text.length + var from = lineLeft(lineObj), to = lineRight(lineObj) + var fromX = getX(from), fromOutside = wrongLine, toX = getX(to), toOutside = wrongLine + + if (x > toX) { return PosWithInfo(lineNo, to, toOutside, 1) } + // Do a binary search between these bounds. + for (;;) { + if (bidi ? to == from || to == moveVisually(lineObj, from, 1) : to - from <= 1) { + var ch = x < fromX || x - fromX <= toX - x ? from : to + var outside = ch == from ? fromOutside : toOutside + var xDiff = x - (ch == from ? fromX : toX) + // This is a kludge to handle the case where the coordinates + // are after a line-wrapped line. We should replace it with a + // more general handling of cursor positions around line + // breaks. (Issue #4078) + if (toOutside && !bidi && !/\s/.test(lineObj.text.charAt(ch)) && xDiff > 0 && + ch < lineObj.text.length && preparedMeasure.view.measure.heights.length > 1) { + var charSize = measureCharPrepared(cm, preparedMeasure, ch, "right") + if (innerOff <= charSize.bottom && innerOff >= charSize.top && Math.abs(x - charSize.right) < xDiff) { + outside = false + ch++ + xDiff = x - charSize.right + } + } + while (isExtendingChar(lineObj.text.charAt(ch))) { ++ch } + var pos = PosWithInfo(lineNo, ch, outside, xDiff < -1 ? -1 : xDiff > 1 ? 1 : 0) + return pos + } + var step = Math.ceil(dist / 2), middle = from + step + if (bidi) { + middle = from + for (var i = 0; i < step; ++i) { middle = moveVisually(lineObj, middle, 1) } + } + var middleX = getX(middle) + if (middleX > x) {to = middle; toX = middleX; if (toOutside = wrongLine) { toX += 1000; } dist = step} + else {from = middle; fromX = middleX; fromOutside = wrongLine; dist -= step} + } +} + +var measureText +// Compute the default text height. +function textHeight(display) { + if (display.cachedTextHeight != null) { return display.cachedTextHeight } + if (measureText == null) { + measureText = elt("pre") + // Measure a bunch of lines, for browsers that compute + // fractional heights. + for (var i = 0; i < 49; ++i) { + measureText.appendChild(document.createTextNode("x")) + measureText.appendChild(elt("br")) + } + measureText.appendChild(document.createTextNode("x")) + } + removeChildrenAndAdd(display.measure, measureText) + var height = measureText.offsetHeight / 50 + if (height > 3) { display.cachedTextHeight = height } + removeChildren(display.measure) + return height || 1 +} + +// Compute the default character width. +function charWidth(display) { + if (display.cachedCharWidth != null) { return display.cachedCharWidth } + var anchor = elt("span", "xxxxxxxxxx") + var pre = elt("pre", [anchor]) + removeChildrenAndAdd(display.measure, pre) + var rect = anchor.getBoundingClientRect(), width = (rect.right - rect.left) / 10 + if (width > 2) { display.cachedCharWidth = width } + return width || 10 +} + +// Do a bulk-read of the DOM positions and sizes needed to draw the +// view, so that we don't interleave reading and writing to the DOM. +function getDimensions(cm) { + var d = cm.display, left = {}, width = {} + var gutterLeft = d.gutters.clientLeft + for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) { + left[cm.options.gutters[i]] = n.offsetLeft + n.clientLeft + gutterLeft + width[cm.options.gutters[i]] = n.clientWidth + } + return {fixedPos: compensateForHScroll(d), + gutterTotalWidth: d.gutters.offsetWidth, + gutterLeft: left, + gutterWidth: width, + wrapperWidth: d.wrapper.clientWidth} +} + +// Computes display.scroller.scrollLeft + display.gutters.offsetWidth, +// but using getBoundingClientRect to get a sub-pixel-accurate +// result. +function compensateForHScroll(display) { + return display.scroller.getBoundingClientRect().left - display.sizer.getBoundingClientRect().left +} + +// Returns a function that estimates the height of a line, to use as +// first approximation until the line becomes visible (and is thus +// properly measurable). +function estimateHeight(cm) { + var th = textHeight(cm.display), wrapping = cm.options.lineWrapping + var perLine = wrapping && Math.max(5, cm.display.scroller.clientWidth / charWidth(cm.display) - 3) + return function (line) { + if (lineIsHidden(cm.doc, line)) { return 0 } + + var widgetsHeight = 0 + if (line.widgets) { for (var i = 0; i < line.widgets.length; i++) { + if (line.widgets[i].height) { widgetsHeight += line.widgets[i].height } + } } + + if (wrapping) + { return widgetsHeight + (Math.ceil(line.text.length / perLine) || 1) * th } + else + { return widgetsHeight + th } + } +} + +function estimateLineHeights(cm) { + var doc = cm.doc, est = estimateHeight(cm) + doc.iter(function (line) { + var estHeight = est(line) + if (estHeight != line.height) { updateLineHeight(line, estHeight) } + }) +} + +// Given a mouse event, find the corresponding position. If liberal +// is false, it checks whether a gutter or scrollbar was clicked, +// and returns null if it was. forRect is used by rectangular +// selections, and tries to estimate a character position even for +// coordinates beyond the right of the text. +function posFromMouse(cm, e, liberal, forRect) { + var display = cm.display + if (!liberal && e_target(e).getAttribute("cm-not-content") == "true") { return null } + + var x, y, space = display.lineSpace.getBoundingClientRect() + // Fails unpredictably on IE[67] when mouse is dragged around quickly. + try { x = e.clientX - space.left; y = e.clientY - space.top } + catch (e) { return null } + var coords = coordsChar(cm, x, y), line + if (forRect && coords.xRel == 1 && (line = getLine(cm.doc, coords.line).text).length == coords.ch) { + var colDiff = countColumn(line, line.length, cm.options.tabSize) - line.length + coords = Pos(coords.line, Math.max(0, Math.round((x - paddingH(cm.display).left) / charWidth(cm.display)) - colDiff)) + } + return coords +} + +// Find the view element corresponding to a given line. Return null +// when the line isn't visible. +function findViewIndex(cm, n) { + if (n >= cm.display.viewTo) { return null } + n -= cm.display.viewFrom + if (n < 0) { return null } + var view = cm.display.view + for (var i = 0; i < view.length; i++) { + n -= view[i].size + if (n < 0) { return i } + } +} + +function updateSelection(cm) { + cm.display.input.showSelection(cm.display.input.prepareSelection()) +} + +function prepareSelection(cm, primary) { + var doc = cm.doc, result = {} + var curFragment = result.cursors = document.createDocumentFragment() + var selFragment = result.selection = document.createDocumentFragment() + + for (var i = 0; i < doc.sel.ranges.length; i++) { + if (primary === false && i == doc.sel.primIndex) { continue } + var range = doc.sel.ranges[i] + if (range.from().line >= cm.display.viewTo || range.to().line < cm.display.viewFrom) { continue } + var collapsed = range.empty() + if (collapsed || cm.options.showCursorWhenSelecting) + { drawSelectionCursor(cm, range.head, curFragment) } + if (!collapsed) + { drawSelectionRange(cm, range, selFragment) } + } + return result +} + +// Draws a cursor for the given range +function drawSelectionCursor(cm, head, output) { + var pos = cursorCoords(cm, head, "div", null, null, !cm.options.singleCursorHeightPerLine) + + var cursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor")) + cursor.style.left = pos.left + "px" + cursor.style.top = pos.top + "px" + cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px" + + if (pos.other) { + // Secondary cursor, shown when on a 'jump' in bi-directional text + var otherCursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor")) + otherCursor.style.display = "" + otherCursor.style.left = pos.other.left + "px" + otherCursor.style.top = pos.other.top + "px" + otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px" + } +} + +// Draws the given range as a highlighted selection +function drawSelectionRange(cm, range, output) { + var display = cm.display, doc = cm.doc + var fragment = document.createDocumentFragment() + var padding = paddingH(cm.display), leftSide = padding.left + var rightSide = Math.max(display.sizerWidth, displayWidth(cm) - display.sizer.offsetLeft) - padding.right + + function add(left, top, width, bottom) { + if (top < 0) { top = 0 } + top = Math.round(top) + bottom = Math.round(bottom) + fragment.appendChild(elt("div", null, "CodeMirror-selected", ("position: absolute; left: " + left + "px;\n top: " + top + "px; width: " + (width == null ? rightSide - left : width) + "px;\n height: " + (bottom - top) + "px"))) + } + + function drawForLine(line, fromArg, toArg) { + var lineObj = getLine(doc, line) + var lineLen = lineObj.text.length + var start, end + function coords(ch, bias) { + return charCoords(cm, Pos(line, ch), "div", lineObj, bias) + } + + iterateBidiSections(getOrder(lineObj), fromArg || 0, toArg == null ? lineLen : toArg, function (from, to, dir) { + var leftPos = coords(from, "left"), rightPos, left, right + if (from == to) { + rightPos = leftPos + left = right = leftPos.left + } else { + rightPos = coords(to - 1, "right") + if (dir == "rtl") { var tmp = leftPos; leftPos = rightPos; rightPos = tmp } + left = leftPos.left + right = rightPos.right + } + if (fromArg == null && from == 0) { left = leftSide } + if (rightPos.top - leftPos.top > 3) { // Different lines, draw top part + add(left, leftPos.top, null, leftPos.bottom) + left = leftSide + if (leftPos.bottom < rightPos.top) { add(left, leftPos.bottom, null, rightPos.top) } + } + if (toArg == null && to == lineLen) { right = rightSide } + if (!start || leftPos.top < start.top || leftPos.top == start.top && leftPos.left < start.left) + { start = leftPos } + if (!end || rightPos.bottom > end.bottom || rightPos.bottom == end.bottom && rightPos.right > end.right) + { end = rightPos } + if (left < leftSide + 1) { left = leftSide } + add(left, rightPos.top, right - left, rightPos.bottom) + }) + return {start: start, end: end} + } + + var sFrom = range.from(), sTo = range.to() + if (sFrom.line == sTo.line) { + drawForLine(sFrom.line, sFrom.ch, sTo.ch) + } else { + var fromLine = getLine(doc, sFrom.line), toLine = getLine(doc, sTo.line) + var singleVLine = visualLine(fromLine) == visualLine(toLine) + var leftEnd = drawForLine(sFrom.line, sFrom.ch, singleVLine ? fromLine.text.length + 1 : null).end + var rightStart = drawForLine(sTo.line, singleVLine ? 0 : null, sTo.ch).start + if (singleVLine) { + if (leftEnd.top < rightStart.top - 2) { + add(leftEnd.right, leftEnd.top, null, leftEnd.bottom) + add(leftSide, rightStart.top, rightStart.left, rightStart.bottom) + } else { + add(leftEnd.right, leftEnd.top, rightStart.left - leftEnd.right, leftEnd.bottom) + } + } + if (leftEnd.bottom < rightStart.top) + { add(leftSide, leftEnd.bottom, null, rightStart.top) } + } + + output.appendChild(fragment) +} + +// Cursor-blinking +function restartBlink(cm) { + if (!cm.state.focused) { return } + var display = cm.display + clearInterval(display.blinker) + var on = true + display.cursorDiv.style.visibility = "" + if (cm.options.cursorBlinkRate > 0) + { display.blinker = setInterval(function () { return display.cursorDiv.style.visibility = (on = !on) ? "" : "hidden"; }, + cm.options.cursorBlinkRate) } + else if (cm.options.cursorBlinkRate < 0) + { display.cursorDiv.style.visibility = "hidden" } +} + +function ensureFocus(cm) { + if (!cm.state.focused) { cm.display.input.focus(); onFocus(cm) } +} + +function delayBlurEvent(cm) { + cm.state.delayingBlurEvent = true + setTimeout(function () { if (cm.state.delayingBlurEvent) { + cm.state.delayingBlurEvent = false + onBlur(cm) + } }, 100) +} + +function onFocus(cm, e) { + if (cm.state.delayingBlurEvent) { cm.state.delayingBlurEvent = false } + + if (cm.options.readOnly == "nocursor") { return } + if (!cm.state.focused) { + signal(cm, "focus", cm, e) + cm.state.focused = true + addClass(cm.display.wrapper, "CodeMirror-focused") + // This test prevents this from firing when a context + // menu is closed (since the input reset would kill the + // select-all detection hack) + if (!cm.curOp && cm.display.selForContextMenu != cm.doc.sel) { + cm.display.input.reset() + if (webkit) { setTimeout(function () { return cm.display.input.reset(true); }, 20) } // Issue #1730 + } + cm.display.input.receivedFocus() + } + restartBlink(cm) +} +function onBlur(cm, e) { + if (cm.state.delayingBlurEvent) { return } + + if (cm.state.focused) { + signal(cm, "blur", cm, e) + cm.state.focused = false + rmClass(cm.display.wrapper, "CodeMirror-focused") + } + clearInterval(cm.display.blinker) + setTimeout(function () { if (!cm.state.focused) { cm.display.shift = false } }, 150) +} + +// Re-align line numbers and gutter marks to compensate for +// horizontal scrolling. +function alignHorizontally(cm) { + var display = cm.display, view = display.view + if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fixedGutter)) { return } + var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.doc.scrollLeft + var gutterW = display.gutters.offsetWidth, left = comp + "px" + for (var i = 0; i < view.length; i++) { if (!view[i].hidden) { + if (cm.options.fixedGutter) { + if (view[i].gutter) + { view[i].gutter.style.left = left } + if (view[i].gutterBackground) + { view[i].gutterBackground.style.left = left } + } + var align = view[i].alignable + if (align) { for (var j = 0; j < align.length; j++) + { align[j].style.left = left } } + } } + if (cm.options.fixedGutter) + { display.gutters.style.left = (comp + gutterW) + "px" } +} + +// Used to ensure that the line number gutter is still the right +// size for the current document size. Returns true when an update +// is needed. +function maybeUpdateLineNumberWidth(cm) { + if (!cm.options.lineNumbers) { return false } + var doc = cm.doc, last = lineNumberFor(cm.options, doc.first + doc.size - 1), display = cm.display + if (last.length != display.lineNumChars) { + var test = display.measure.appendChild(elt("div", [elt("div", last)], + "CodeMirror-linenumber CodeMirror-gutter-elt")) + var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - innerW + display.lineGutter.style.width = "" + display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding) + 1 + display.lineNumWidth = display.lineNumInnerWidth + padding + display.lineNumChars = display.lineNumInnerWidth ? last.length : -1 + display.lineGutter.style.width = display.lineNumWidth + "px" + updateGutterSpace(cm) + return true + } + return false +} + +// Read the actual heights of the rendered lines, and update their +// stored heights to match. +function updateHeightsInViewport(cm) { + var display = cm.display + var prevBottom = display.lineDiv.offsetTop + for (var i = 0; i < display.view.length; i++) { + var cur = display.view[i], height = (void 0) + if (cur.hidden) { continue } + if (ie && ie_version < 8) { + var bot = cur.node.offsetTop + cur.node.offsetHeight + height = bot - prevBottom + prevBottom = bot + } else { + var box = cur.node.getBoundingClientRect() + height = box.bottom - box.top + } + var diff = cur.line.height - height + if (height < 2) { height = textHeight(display) } + if (diff > .001 || diff < -.001) { + updateLineHeight(cur.line, height) + updateWidgetHeight(cur.line) + if (cur.rest) { for (var j = 0; j < cur.rest.length; j++) + { updateWidgetHeight(cur.rest[j]) } } + } + } +} + +// Read and store the height of line widgets associated with the +// given line. +function updateWidgetHeight(line) { + if (line.widgets) { for (var i = 0; i < line.widgets.length; ++i) + { line.widgets[i].height = line.widgets[i].node.parentNode.offsetHeight } } +} + +// Compute the lines that are visible in a given viewport (defaults +// the the current scroll position). viewport may contain top, +// height, and ensure (see op.scrollToPos) properties. +function visibleLines(display, doc, viewport) { + var top = viewport && viewport.top != null ? Math.max(0, viewport.top) : display.scroller.scrollTop + top = Math.floor(top - paddingTop(display)) + var bottom = viewport && viewport.bottom != null ? viewport.bottom : top + display.wrapper.clientHeight + + var from = lineAtHeight(doc, top), to = lineAtHeight(doc, bottom) + // Ensure is a {from: {line, ch}, to: {line, ch}} object, and + // forces those lines into the viewport (if possible). + if (viewport && viewport.ensure) { + var ensureFrom = viewport.ensure.from.line, ensureTo = viewport.ensure.to.line + if (ensureFrom < from) { + from = ensureFrom + to = lineAtHeight(doc, heightAtLine(getLine(doc, ensureFrom)) + display.wrapper.clientHeight) + } else if (Math.min(ensureTo, doc.lastLine()) >= to) { + from = lineAtHeight(doc, heightAtLine(getLine(doc, ensureTo)) - display.wrapper.clientHeight) + to = ensureTo + } + } + return {from: from, to: Math.max(to, from + 1)} +} + +// Sync the scrollable area and scrollbars, ensure the viewport +// covers the visible area. +function setScrollTop(cm, val) { + if (Math.abs(cm.doc.scrollTop - val) < 2) { return } + cm.doc.scrollTop = val + if (!gecko) { updateDisplaySimple(cm, {top: val}) } + if (cm.display.scroller.scrollTop != val) { cm.display.scroller.scrollTop = val } + cm.display.scrollbars.setScrollTop(val) + if (gecko) { updateDisplaySimple(cm) } + startWorker(cm, 100) +} +// Sync scroller and scrollbar, ensure the gutter elements are +// aligned. +function setScrollLeft(cm, val, isScroller) { + if (isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) { return } + val = Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clientWidth) + cm.doc.scrollLeft = val + alignHorizontally(cm) + if (cm.display.scroller.scrollLeft != val) { cm.display.scroller.scrollLeft = val } + cm.display.scrollbars.setScrollLeft(val) +} + +// Since the delta values reported on mouse wheel events are +// unstandardized between browsers and even browser versions, and +// generally horribly unpredictable, this code starts by measuring +// the scroll effect that the first few mouse wheel events have, +// and, from that, detects the way it can convert deltas to pixel +// offsets afterwards. +// +// The reason we want to know the amount a wheel event will scroll +// is that it gives us a chance to update the display before the +// actual scrolling happens, reducing flickering. + +var wheelSamples = 0; +var wheelPixelsPerUnit = null; +// Fill in a browser-detected starting value on browsers where we +// know one. These don't have to be accurate -- the result of them +// being wrong would just be a slight flicker on the first wheel +// scroll (if it is large enough). +if (ie) { wheelPixelsPerUnit = -.53 } +else if (gecko) { wheelPixelsPerUnit = 15 } +else if (chrome) { wheelPixelsPerUnit = -.7 } +else if (safari) { wheelPixelsPerUnit = -1/3 } + +function wheelEventDelta(e) { + var dx = e.wheelDeltaX, dy = e.wheelDeltaY + if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) { dx = e.detail } + if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) { dy = e.detail } + else if (dy == null) { dy = e.wheelDelta } + return {x: dx, y: dy} +} +function wheelEventPixels(e) { + var delta = wheelEventDelta(e) + delta.x *= wheelPixelsPerUnit + delta.y *= wheelPixelsPerUnit + return delta +} + +function onScrollWheel(cm, e) { + var delta = wheelEventDelta(e), dx = delta.x, dy = delta.y + + var display = cm.display, scroll = display.scroller + // Quit if there's nothing to scroll here + var canScrollX = scroll.scrollWidth > scroll.clientWidth + var canScrollY = scroll.scrollHeight > scroll.clientHeight + if (!(dx && canScrollX || dy && canScrollY)) { return } + + // Webkit browsers on OS X abort momentum scrolls when the target + // of the scroll event is removed from the scrollable element. + // This hack (see related code in patchDisplay) makes sure the + // element is kept around. + if (dy && mac && webkit) { + outer: for (var cur = e.target, view = display.view; cur != scroll; cur = cur.parentNode) { + for (var i = 0; i < view.length; i++) { + if (view[i].node == cur) { + cm.display.currentWheelTarget = cur + break outer + } + } + } + } + + // On some browsers, horizontal scrolling will cause redraws to + // happen before the gutter has been realigned, causing it to + // wriggle around in a most unseemly way. When we have an + // estimated pixels/delta value, we just handle horizontal + // scrolling entirely here. It'll be slightly off from native, but + // better than glitching out. + if (dx && !gecko && !presto && wheelPixelsPerUnit != null) { + if (dy && canScrollY) + { setScrollTop(cm, Math.max(0, Math.min(scroll.scrollTop + dy * wheelPixelsPerUnit, scroll.scrollHeight - scroll.clientHeight))) } + setScrollLeft(cm, Math.max(0, Math.min(scroll.scrollLeft + dx * wheelPixelsPerUnit, scroll.scrollWidth - scroll.clientWidth))) + // Only prevent default scrolling if vertical scrolling is + // actually possible. Otherwise, it causes vertical scroll + // jitter on OSX trackpads when deltaX is small and deltaY + // is large (issue #3579) + if (!dy || (dy && canScrollY)) + { e_preventDefault(e) } + display.wheelStartX = null // Abort measurement, if in progress + return + } + + // 'Project' the visible viewport to cover the area that is being + // scrolled into view (if we know enough to estimate it). + if (dy && wheelPixelsPerUnit != null) { + var pixels = dy * wheelPixelsPerUnit + var top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight + if (pixels < 0) { top = Math.max(0, top + pixels - 50) } + else { bot = Math.min(cm.doc.height, bot + pixels + 50) } + updateDisplaySimple(cm, {top: top, bottom: bot}) + } + + if (wheelSamples < 20) { + if (display.wheelStartX == null) { + display.wheelStartX = scroll.scrollLeft; display.wheelStartY = scroll.scrollTop + display.wheelDX = dx; display.wheelDY = dy + setTimeout(function () { + if (display.wheelStartX == null) { return } + var movedX = scroll.scrollLeft - display.wheelStartX + var movedY = scroll.scrollTop - display.wheelStartY + var sample = (movedY && display.wheelDY && movedY / display.wheelDY) || + (movedX && display.wheelDX && movedX / display.wheelDX) + display.wheelStartX = display.wheelStartY = null + if (!sample) { return } + wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1) + ++wheelSamples + }, 200) + } else { + display.wheelDX += dx; display.wheelDY += dy + } + } +} + +// SCROLLBARS + +// Prepare DOM reads needed to update the scrollbars. Done in one +// shot to minimize update/measure roundtrips. +function measureForScrollbars(cm) { + var d = cm.display, gutterW = d.gutters.offsetWidth + var docH = Math.round(cm.doc.height + paddingVert(cm.display)) + return { + clientHeight: d.scroller.clientHeight, + viewHeight: d.wrapper.clientHeight, + scrollWidth: d.scroller.scrollWidth, clientWidth: d.scroller.clientWidth, + viewWidth: d.wrapper.clientWidth, + barLeft: cm.options.fixedGutter ? gutterW : 0, + docHeight: docH, + scrollHeight: docH + scrollGap(cm) + d.barHeight, + nativeBarWidth: d.nativeBarWidth, + gutterWidth: gutterW + } +} + +var NativeScrollbars = function(place, scroll, cm) { + this.cm = cm + var vert = this.vert = elt("div", [elt("div", null, null, "min-width: 1px")], "CodeMirror-vscrollbar") + var horiz = this.horiz = elt("div", [elt("div", null, null, "height: 100%; min-height: 1px")], "CodeMirror-hscrollbar") + place(vert); place(horiz) + + on(vert, "scroll", function () { + if (vert.clientHeight) { scroll(vert.scrollTop, "vertical") } + }) + on(horiz, "scroll", function () { + if (horiz.clientWidth) { scroll(horiz.scrollLeft, "horizontal") } + }) + + this.checkedZeroWidth = false + // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8). + if (ie && ie_version < 8) { this.horiz.style.minHeight = this.vert.style.minWidth = "18px" } +}; + +NativeScrollbars.prototype.update = function (measure) { + var needsH = measure.scrollWidth > measure.clientWidth + 1 + var needsV = measure.scrollHeight > measure.clientHeight + 1 + var sWidth = measure.nativeBarWidth + + if (needsV) { + this.vert.style.display = "block" + this.vert.style.bottom = needsH ? sWidth + "px" : "0" + var totalHeight = measure.viewHeight - (needsH ? sWidth : 0) + // A bug in IE8 can cause this value to be negative, so guard it. + this.vert.firstChild.style.height = + Math.max(0, measure.scrollHeight - measure.clientHeight + totalHeight) + "px" + } else { + this.vert.style.display = "" + this.vert.firstChild.style.height = "0" + } + + if (needsH) { + this.horiz.style.display = "block" + this.horiz.style.right = needsV ? sWidth + "px" : "0" + this.horiz.style.left = measure.barLeft + "px" + var totalWidth = measure.viewWidth - measure.barLeft - (needsV ? sWidth : 0) + this.horiz.firstChild.style.width = + (measure.scrollWidth - measure.clientWidth + totalWidth) + "px" + } else { + this.horiz.style.display = "" + this.horiz.firstChild.style.width = "0" + } + + if (!this.checkedZeroWidth && measure.clientHeight > 0) { + if (sWidth == 0) { this.zeroWidthHack() } + this.checkedZeroWidth = true + } + + return {right: needsV ? sWidth : 0, bottom: needsH ? sWidth : 0} +}; + +NativeScrollbars.prototype.setScrollLeft = function (pos) { + if (this.horiz.scrollLeft != pos) { this.horiz.scrollLeft = pos } + if (this.disableHoriz) { this.enableZeroWidthBar(this.horiz, this.disableHoriz) } +}; + +NativeScrollbars.prototype.setScrollTop = function (pos) { + if (this.vert.scrollTop != pos) { this.vert.scrollTop = pos } + if (this.disableVert) { this.enableZeroWidthBar(this.vert, this.disableVert) } +}; + +NativeScrollbars.prototype.zeroWidthHack = function () { + var w = mac && !mac_geMountainLion ? "12px" : "18px" + this.horiz.style.height = this.vert.style.width = w + this.horiz.style.pointerEvents = this.vert.style.pointerEvents = "none" + this.disableHoriz = new Delayed + this.disableVert = new Delayed +}; + +NativeScrollbars.prototype.enableZeroWidthBar = function (bar, delay) { + bar.style.pointerEvents = "auto" + function maybeDisable() { + // To find out whether the scrollbar is still visible, we + // check whether the element under the pixel in the bottom + // left corner of the scrollbar box is the scrollbar box + // itself (when the bar is still visible) or its filler child + // (when the bar is hidden). If it is still visible, we keep + // it enabled, if it's hidden, we disable pointer events. + var box = bar.getBoundingClientRect() + var elt = document.elementFromPoint(box.left + 1, box.bottom - 1) + if (elt != bar) { bar.style.pointerEvents = "none" } + else { delay.set(1000, maybeDisable) } + } + delay.set(1000, maybeDisable) +}; + +NativeScrollbars.prototype.clear = function () { + var parent = this.horiz.parentNode + parent.removeChild(this.horiz) + parent.removeChild(this.vert) +}; + +var NullScrollbars = function () {}; + +NullScrollbars.prototype.update = function () { return {bottom: 0, right: 0} }; +NullScrollbars.prototype.setScrollLeft = function () {}; +NullScrollbars.prototype.setScrollTop = function () {}; +NullScrollbars.prototype.clear = function () {}; + +function updateScrollbars(cm, measure) { + if (!measure) { measure = measureForScrollbars(cm) } + var startWidth = cm.display.barWidth, startHeight = cm.display.barHeight + updateScrollbarsInner(cm, measure) + for (var i = 0; i < 4 && startWidth != cm.display.barWidth || startHeight != cm.display.barHeight; i++) { + if (startWidth != cm.display.barWidth && cm.options.lineWrapping) + { updateHeightsInViewport(cm) } + updateScrollbarsInner(cm, measureForScrollbars(cm)) + startWidth = cm.display.barWidth; startHeight = cm.display.barHeight + } +} + +// Re-synchronize the fake scrollbars with the actual size of the +// content. +function updateScrollbarsInner(cm, measure) { + var d = cm.display + var sizes = d.scrollbars.update(measure) + + d.sizer.style.paddingRight = (d.barWidth = sizes.right) + "px" + d.sizer.style.paddingBottom = (d.barHeight = sizes.bottom) + "px" + d.heightForcer.style.borderBottom = sizes.bottom + "px solid transparent" + + if (sizes.right && sizes.bottom) { + d.scrollbarFiller.style.display = "block" + d.scrollbarFiller.style.height = sizes.bottom + "px" + d.scrollbarFiller.style.width = sizes.right + "px" + } else { d.scrollbarFiller.style.display = "" } + if (sizes.bottom && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) { + d.gutterFiller.style.display = "block" + d.gutterFiller.style.height = sizes.bottom + "px" + d.gutterFiller.style.width = measure.gutterWidth + "px" + } else { d.gutterFiller.style.display = "" } +} + +var scrollbarModel = {"native": NativeScrollbars, "null": NullScrollbars} + +function initScrollbars(cm) { + if (cm.display.scrollbars) { + cm.display.scrollbars.clear() + if (cm.display.scrollbars.addClass) + { rmClass(cm.display.wrapper, cm.display.scrollbars.addClass) } + } + + cm.display.scrollbars = new scrollbarModel[cm.options.scrollbarStyle](function (node) { + cm.display.wrapper.insertBefore(node, cm.display.scrollbarFiller) + // Prevent clicks in the scrollbars from killing focus + on(node, "mousedown", function () { + if (cm.state.focused) { setTimeout(function () { return cm.display.input.focus(); }, 0) } + }) + node.setAttribute("cm-not-content", "true") + }, function (pos, axis) { + if (axis == "horizontal") { setScrollLeft(cm, pos) } + else { setScrollTop(cm, pos) } + }, cm) + if (cm.display.scrollbars.addClass) + { addClass(cm.display.wrapper, cm.display.scrollbars.addClass) } +} + +// SCROLLING THINGS INTO VIEW + +// If an editor sits on the top or bottom of the window, partially +// scrolled out of view, this ensures that the cursor is visible. +function maybeScrollWindow(cm, coords) { + if (signalDOMEvent(cm, "scrollCursorIntoView")) { return } + + var display = cm.display, box = display.sizer.getBoundingClientRect(), doScroll = null + if (coords.top + box.top < 0) { doScroll = true } + else if (coords.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) { doScroll = false } + if (doScroll != null && !phantom) { + var scrollNode = elt("div", "\u200b", null, ("position: absolute;\n top: " + (coords.top - display.viewOffset - paddingTop(cm.display)) + "px;\n height: " + (coords.bottom - coords.top + scrollGap(cm) + display.barHeight) + "px;\n left: " + (coords.left) + "px; width: 2px;")) + cm.display.lineSpace.appendChild(scrollNode) + scrollNode.scrollIntoView(doScroll) + cm.display.lineSpace.removeChild(scrollNode) + } +} + +// Scroll a given position into view (immediately), verifying that +// it actually became visible (as line heights are accurately +// measured, the position of something may 'drift' during drawing). +function scrollPosIntoView(cm, pos, end, margin) { + if (margin == null) { margin = 0 } + var coords + for (var limit = 0; limit < 5; limit++) { + var changed = false + coords = cursorCoords(cm, pos) + var endCoords = !end || end == pos ? coords : cursorCoords(cm, end) + var scrollPos = calculateScrollPos(cm, Math.min(coords.left, endCoords.left), + Math.min(coords.top, endCoords.top) - margin, + Math.max(coords.left, endCoords.left), + Math.max(coords.bottom, endCoords.bottom) + margin) + var startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft + if (scrollPos.scrollTop != null) { + setScrollTop(cm, scrollPos.scrollTop) + if (Math.abs(cm.doc.scrollTop - startTop) > 1) { changed = true } + } + if (scrollPos.scrollLeft != null) { + setScrollLeft(cm, scrollPos.scrollLeft) + if (Math.abs(cm.doc.scrollLeft - startLeft) > 1) { changed = true } + } + if (!changed) { break } + } + return coords +} + +// Scroll a given set of coordinates into view (immediately). +function scrollIntoView(cm, x1, y1, x2, y2) { + var scrollPos = calculateScrollPos(cm, x1, y1, x2, y2) + if (scrollPos.scrollTop != null) { setScrollTop(cm, scrollPos.scrollTop) } + if (scrollPos.scrollLeft != null) { setScrollLeft(cm, scrollPos.scrollLeft) } +} + +// Calculate a new scroll position needed to scroll the given +// rectangle into view. Returns an object with scrollTop and +// scrollLeft properties. When these are undefined, the +// vertical/horizontal position does not need to be adjusted. +function calculateScrollPos(cm, x1, y1, x2, y2) { + var display = cm.display, snapMargin = textHeight(cm.display) + if (y1 < 0) { y1 = 0 } + var screentop = cm.curOp && cm.curOp.scrollTop != null ? cm.curOp.scrollTop : display.scroller.scrollTop + var screen = displayHeight(cm), result = {} + if (y2 - y1 > screen) { y2 = y1 + screen } + var docBottom = cm.doc.height + paddingVert(display) + var atTop = y1 < snapMargin, atBottom = y2 > docBottom - snapMargin + if (y1 < screentop) { + result.scrollTop = atTop ? 0 : y1 + } else if (y2 > screentop + screen) { + var newTop = Math.min(y1, (atBottom ? docBottom : y2) - screen) + if (newTop != screentop) { result.scrollTop = newTop } + } + + var screenleft = cm.curOp && cm.curOp.scrollLeft != null ? cm.curOp.scrollLeft : display.scroller.scrollLeft + var screenw = displayWidth(cm) - (cm.options.fixedGutter ? display.gutters.offsetWidth : 0) + var tooWide = x2 - x1 > screenw + if (tooWide) { x2 = x1 + screenw } + if (x1 < 10) + { result.scrollLeft = 0 } + else if (x1 < screenleft) + { result.scrollLeft = Math.max(0, x1 - (tooWide ? 0 : 10)) } + else if (x2 > screenw + screenleft - 3) + { result.scrollLeft = x2 + (tooWide ? 0 : 10) - screenw } + return result +} + +// Store a relative adjustment to the scroll position in the current +// operation (to be applied when the operation finishes). +function addToScrollPos(cm, left, top) { + if (left != null || top != null) { resolveScrollToPos(cm) } + if (left != null) + { cm.curOp.scrollLeft = (cm.curOp.scrollLeft == null ? cm.doc.scrollLeft : cm.curOp.scrollLeft) + left } + if (top != null) + { cm.curOp.scrollTop = (cm.curOp.scrollTop == null ? cm.doc.scrollTop : cm.curOp.scrollTop) + top } +} + +// Make sure that at the end of the operation the current cursor is +// shown. +function ensureCursorVisible(cm) { + resolveScrollToPos(cm) + var cur = cm.getCursor(), from = cur, to = cur + if (!cm.options.lineWrapping) { + from = cur.ch ? Pos(cur.line, cur.ch - 1) : cur + to = Pos(cur.line, cur.ch + 1) + } + cm.curOp.scrollToPos = {from: from, to: to, margin: cm.options.cursorScrollMargin, isCursor: true} +} + +// When an operation has its scrollToPos property set, and another +// scroll action is applied before the end of the operation, this +// 'simulates' scrolling that position into view in a cheap way, so +// that the effect of intermediate scroll commands is not ignored. +function resolveScrollToPos(cm) { + var range = cm.curOp.scrollToPos + if (range) { + cm.curOp.scrollToPos = null + var from = estimateCoords(cm, range.from), to = estimateCoords(cm, range.to) + var sPos = calculateScrollPos(cm, Math.min(from.left, to.left), + Math.min(from.top, to.top) - range.margin, + Math.max(from.right, to.right), + Math.max(from.bottom, to.bottom) + range.margin) + cm.scrollTo(sPos.scrollLeft, sPos.scrollTop) + } +} + +// Operations are used to wrap a series of changes to the editor +// state in such a way that each change won't have to update the +// cursor and display (which would be awkward, slow, and +// error-prone). Instead, display updates are batched and then all +// combined and executed at once. + +var nextOpId = 0 +// Start a new operation. +function startOperation(cm) { + cm.curOp = { + cm: cm, + viewChanged: false, // Flag that indicates that lines might need to be redrawn + startHeight: cm.doc.height, // Used to detect need to update scrollbar + forceUpdate: false, // Used to force a redraw + updateInput: null, // Whether to reset the input textarea + typing: false, // Whether this reset should be careful to leave existing text (for compositing) + changeObjs: null, // Accumulated changes, for firing change events + cursorActivityHandlers: null, // Set of handlers to fire cursorActivity on + cursorActivityCalled: 0, // Tracks which cursorActivity handlers have been called already + selectionChanged: false, // Whether the selection needs to be redrawn + updateMaxLine: false, // Set when the widest line needs to be determined anew + scrollLeft: null, scrollTop: null, // Intermediate scroll position, not pushed to DOM yet + scrollToPos: null, // Used to scroll to a specific position + focus: false, + id: ++nextOpId // Unique ID + } + pushOperation(cm.curOp) +} + +// Finish an operation, updating the display and signalling delayed events +function endOperation(cm) { + var op = cm.curOp + finishOperation(op, function (group) { + for (var i = 0; i < group.ops.length; i++) + { group.ops[i].cm.curOp = null } + endOperations(group) + }) +} + +// The DOM updates done when an operation finishes are batched so +// that the minimum number of relayouts are required. +function endOperations(group) { + var ops = group.ops + for (var i = 0; i < ops.length; i++) // Read DOM + { endOperation_R1(ops[i]) } + for (var i$1 = 0; i$1 < ops.length; i$1++) // Write DOM (maybe) + { endOperation_W1(ops[i$1]) } + for (var i$2 = 0; i$2 < ops.length; i$2++) // Read DOM + { endOperation_R2(ops[i$2]) } + for (var i$3 = 0; i$3 < ops.length; i$3++) // Write DOM (maybe) + { endOperation_W2(ops[i$3]) } + for (var i$4 = 0; i$4 < ops.length; i$4++) // Read DOM + { endOperation_finish(ops[i$4]) } +} + +function endOperation_R1(op) { + var cm = op.cm, display = cm.display + maybeClipScrollbars(cm) + if (op.updateMaxLine) { findMaxLine(cm) } + + op.mustUpdate = op.viewChanged || op.forceUpdate || op.scrollTop != null || + op.scrollToPos && (op.scrollToPos.from.line < display.viewFrom || + op.scrollToPos.to.line >= display.viewTo) || + display.maxLineChanged && cm.options.lineWrapping + op.update = op.mustUpdate && + new DisplayUpdate(cm, op.mustUpdate && {top: op.scrollTop, ensure: op.scrollToPos}, op.forceUpdate) +} + +function endOperation_W1(op) { + op.updatedDisplay = op.mustUpdate && updateDisplayIfNeeded(op.cm, op.update) +} + +function endOperation_R2(op) { + var cm = op.cm, display = cm.display + if (op.updatedDisplay) { updateHeightsInViewport(cm) } + + op.barMeasure = measureForScrollbars(cm) + + // If the max line changed since it was last measured, measure it, + // and ensure the document's width matches it. + // updateDisplay_W2 will use these properties to do the actual resizing + if (display.maxLineChanged && !cm.options.lineWrapping) { + op.adjustWidthTo = measureChar(cm, display.maxLine, display.maxLine.text.length).left + 3 + cm.display.sizerWidth = op.adjustWidthTo + op.barMeasure.scrollWidth = + Math.max(display.scroller.clientWidth, display.sizer.offsetLeft + op.adjustWidthTo + scrollGap(cm) + cm.display.barWidth) + op.maxScrollLeft = Math.max(0, display.sizer.offsetLeft + op.adjustWidthTo - displayWidth(cm)) + } + + if (op.updatedDisplay || op.selectionChanged) + { op.preparedSelection = display.input.prepareSelection(op.focus) } +} + +function endOperation_W2(op) { + var cm = op.cm + + if (op.adjustWidthTo != null) { + cm.display.sizer.style.minWidth = op.adjustWidthTo + "px" + if (op.maxScrollLeft < cm.doc.scrollLeft) + { setScrollLeft(cm, Math.min(cm.display.scroller.scrollLeft, op.maxScrollLeft), true) } + cm.display.maxLineChanged = false + } + + var takeFocus = op.focus && op.focus == activeElt() && (!document.hasFocus || document.hasFocus()) + if (op.preparedSelection) + { cm.display.input.showSelection(op.preparedSelection, takeFocus) } + if (op.updatedDisplay || op.startHeight != cm.doc.height) + { updateScrollbars(cm, op.barMeasure) } + if (op.updatedDisplay) + { setDocumentHeight(cm, op.barMeasure) } + + if (op.selectionChanged) { restartBlink(cm) } + + if (cm.state.focused && op.updateInput) + { cm.display.input.reset(op.typing) } + if (takeFocus) { ensureFocus(op.cm) } +} + +function endOperation_finish(op) { + var cm = op.cm, display = cm.display, doc = cm.doc + + if (op.updatedDisplay) { postUpdateDisplay(cm, op.update) } + + // Abort mouse wheel delta measurement, when scrolling explicitly + if (display.wheelStartX != null && (op.scrollTop != null || op.scrollLeft != null || op.scrollToPos)) + { display.wheelStartX = display.wheelStartY = null } + + // Propagate the scroll position to the actual DOM scroller + if (op.scrollTop != null && (display.scroller.scrollTop != op.scrollTop || op.forceScroll)) { + doc.scrollTop = Math.max(0, Math.min(display.scroller.scrollHeight - display.scroller.clientHeight, op.scrollTop)) + display.scrollbars.setScrollTop(doc.scrollTop) + display.scroller.scrollTop = doc.scrollTop + } + if (op.scrollLeft != null && (display.scroller.scrollLeft != op.scrollLeft || op.forceScroll)) { + doc.scrollLeft = Math.max(0, Math.min(display.scroller.scrollWidth - display.scroller.clientWidth, op.scrollLeft)) + display.scrollbars.setScrollLeft(doc.scrollLeft) + display.scroller.scrollLeft = doc.scrollLeft + alignHorizontally(cm) + } + // If we need to scroll a specific position into view, do so. + if (op.scrollToPos) { + var coords = scrollPosIntoView(cm, clipPos(doc, op.scrollToPos.from), + clipPos(doc, op.scrollToPos.to), op.scrollToPos.margin) + if (op.scrollToPos.isCursor && cm.state.focused) { maybeScrollWindow(cm, coords) } + } + + // Fire events for markers that are hidden/unidden by editing or + // undoing + var hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers + if (hidden) { for (var i = 0; i < hidden.length; ++i) + { if (!hidden[i].lines.length) { signal(hidden[i], "hide") } } } + if (unhidden) { for (var i$1 = 0; i$1 < unhidden.length; ++i$1) + { if (unhidden[i$1].lines.length) { signal(unhidden[i$1], "unhide") } } } + + if (display.wrapper.offsetHeight) + { doc.scrollTop = cm.display.scroller.scrollTop } + + // Fire change events, and delayed event handlers + if (op.changeObjs) + { signal(cm, "changes", cm, op.changeObjs) } + if (op.update) + { op.update.finish() } +} + +// Run the given function in an operation +function runInOp(cm, f) { + if (cm.curOp) { return f() } + startOperation(cm) + try { return f() } + finally { endOperation(cm) } +} +// Wraps a function in an operation. Returns the wrapped function. +function operation(cm, f) { + return function() { + if (cm.curOp) { return f.apply(cm, arguments) } + startOperation(cm) + try { return f.apply(cm, arguments) } + finally { endOperation(cm) } + } +} +// Used to add methods to editor and doc instances, wrapping them in +// operations. +function methodOp(f) { + return function() { + if (this.curOp) { return f.apply(this, arguments) } + startOperation(this) + try { return f.apply(this, arguments) } + finally { endOperation(this) } + } +} +function docMethodOp(f) { + return function() { + var cm = this.cm + if (!cm || cm.curOp) { return f.apply(this, arguments) } + startOperation(cm) + try { return f.apply(this, arguments) } + finally { endOperation(cm) } + } +} + +// Updates the display.view data structure for a given change to the +// document. From and to are in pre-change coordinates. Lendiff is +// the amount of lines added or subtracted by the change. This is +// used for changes that span multiple lines, or change the way +// lines are divided into visual lines. regLineChange (below) +// registers single-line changes. +function regChange(cm, from, to, lendiff) { + if (from == null) { from = cm.doc.first } + if (to == null) { to = cm.doc.first + cm.doc.size } + if (!lendiff) { lendiff = 0 } + + var display = cm.display + if (lendiff && to < display.viewTo && + (display.updateLineNumbers == null || display.updateLineNumbers > from)) + { display.updateLineNumbers = from } + + cm.curOp.viewChanged = true + + if (from >= display.viewTo) { // Change after + if (sawCollapsedSpans && visualLineNo(cm.doc, from) < display.viewTo) + { resetView(cm) } + } else if (to <= display.viewFrom) { // Change before + if (sawCollapsedSpans && visualLineEndNo(cm.doc, to + lendiff) > display.viewFrom) { + resetView(cm) + } else { + display.viewFrom += lendiff + display.viewTo += lendiff + } + } else if (from <= display.viewFrom && to >= display.viewTo) { // Full overlap + resetView(cm) + } else if (from <= display.viewFrom) { // Top overlap + var cut = viewCuttingPoint(cm, to, to + lendiff, 1) + if (cut) { + display.view = display.view.slice(cut.index) + display.viewFrom = cut.lineN + display.viewTo += lendiff + } else { + resetView(cm) + } + } else if (to >= display.viewTo) { // Bottom overlap + var cut$1 = viewCuttingPoint(cm, from, from, -1) + if (cut$1) { + display.view = display.view.slice(0, cut$1.index) + display.viewTo = cut$1.lineN + } else { + resetView(cm) + } + } else { // Gap in the middle + var cutTop = viewCuttingPoint(cm, from, from, -1) + var cutBot = viewCuttingPoint(cm, to, to + lendiff, 1) + if (cutTop && cutBot) { + display.view = display.view.slice(0, cutTop.index) + .concat(buildViewArray(cm, cutTop.lineN, cutBot.lineN)) + .concat(display.view.slice(cutBot.index)) + display.viewTo += lendiff + } else { + resetView(cm) + } + } + + var ext = display.externalMeasured + if (ext) { + if (to < ext.lineN) + { ext.lineN += lendiff } + else if (from < ext.lineN + ext.size) + { display.externalMeasured = null } + } +} + +// Register a change to a single line. Type must be one of "text", +// "gutter", "class", "widget" +function regLineChange(cm, line, type) { + cm.curOp.viewChanged = true + var display = cm.display, ext = cm.display.externalMeasured + if (ext && line >= ext.lineN && line < ext.lineN + ext.size) + { display.externalMeasured = null } + + if (line < display.viewFrom || line >= display.viewTo) { return } + var lineView = display.view[findViewIndex(cm, line)] + if (lineView.node == null) { return } + var arr = lineView.changes || (lineView.changes = []) + if (indexOf(arr, type) == -1) { arr.push(type) } +} + +// Clear the view. +function resetView(cm) { + cm.display.viewFrom = cm.display.viewTo = cm.doc.first + cm.display.view = [] + cm.display.viewOffset = 0 +} + +function viewCuttingPoint(cm, oldN, newN, dir) { + var index = findViewIndex(cm, oldN), diff, view = cm.display.view + if (!sawCollapsedSpans || newN == cm.doc.first + cm.doc.size) + { return {index: index, lineN: newN} } + var n = cm.display.viewFrom + for (var i = 0; i < index; i++) + { n += view[i].size } + if (n != oldN) { + if (dir > 0) { + if (index == view.length - 1) { return null } + diff = (n + view[index].size) - oldN + index++ + } else { + diff = n - oldN + } + oldN += diff; newN += diff + } + while (visualLineNo(cm.doc, newN) != newN) { + if (index == (dir < 0 ? 0 : view.length - 1)) { return null } + newN += dir * view[index - (dir < 0 ? 1 : 0)].size + index += dir + } + return {index: index, lineN: newN} +} + +// Force the view to cover a given range, adding empty view element +// or clipping off existing ones as needed. +function adjustView(cm, from, to) { + var display = cm.display, view = display.view + if (view.length == 0 || from >= display.viewTo || to <= display.viewFrom) { + display.view = buildViewArray(cm, from, to) + display.viewFrom = from + } else { + if (display.viewFrom > from) + { display.view = buildViewArray(cm, from, display.viewFrom).concat(display.view) } + else if (display.viewFrom < from) + { display.view = display.view.slice(findViewIndex(cm, from)) } + display.viewFrom = from + if (display.viewTo < to) + { display.view = display.view.concat(buildViewArray(cm, display.viewTo, to)) } + else if (display.viewTo > to) + { display.view = display.view.slice(0, findViewIndex(cm, to)) } + } + display.viewTo = to +} + +// Count the number of lines in the view whose DOM representation is +// out of date (or nonexistent). +function countDirtyView(cm) { + var view = cm.display.view, dirty = 0 + for (var i = 0; i < view.length; i++) { + var lineView = view[i] + if (!lineView.hidden && (!lineView.node || lineView.changes)) { ++dirty } + } + return dirty +} + +// HIGHLIGHT WORKER + +function startWorker(cm, time) { + if (cm.doc.mode.startState && cm.doc.frontier < cm.display.viewTo) + { cm.state.highlight.set(time, bind(highlightWorker, cm)) } +} + +function highlightWorker(cm) { + var doc = cm.doc + if (doc.frontier < doc.first) { doc.frontier = doc.first } + if (doc.frontier >= cm.display.viewTo) { return } + var end = +new Date + cm.options.workTime + var state = copyState(doc.mode, getStateBefore(cm, doc.frontier)) + var changedLines = [] + + doc.iter(doc.frontier, Math.min(doc.first + doc.size, cm.display.viewTo + 500), function (line) { + if (doc.frontier >= cm.display.viewFrom) { // Visible + var oldStyles = line.styles, tooLong = line.text.length > cm.options.maxHighlightLength + var highlighted = highlightLine(cm, line, tooLong ? copyState(doc.mode, state) : state, true) + line.styles = highlighted.styles + var oldCls = line.styleClasses, newCls = highlighted.classes + if (newCls) { line.styleClasses = newCls } + else if (oldCls) { line.styleClasses = null } + var ischange = !oldStyles || oldStyles.length != line.styles.length || + oldCls != newCls && (!oldCls || !newCls || oldCls.bgClass != newCls.bgClass || oldCls.textClass != newCls.textClass) + for (var i = 0; !ischange && i < oldStyles.length; ++i) { ischange = oldStyles[i] != line.styles[i] } + if (ischange) { changedLines.push(doc.frontier) } + line.stateAfter = tooLong ? state : copyState(doc.mode, state) + } else { + if (line.text.length <= cm.options.maxHighlightLength) + { processLine(cm, line.text, state) } + line.stateAfter = doc.frontier % 5 == 0 ? copyState(doc.mode, state) : null + } + ++doc.frontier + if (+new Date > end) { + startWorker(cm, cm.options.workDelay) + return true + } + }) + if (changedLines.length) { runInOp(cm, function () { + for (var i = 0; i < changedLines.length; i++) + { regLineChange(cm, changedLines[i], "text") } + }) } +} + +// DISPLAY DRAWING + +var DisplayUpdate = function(cm, viewport, force) { + var display = cm.display + + this.viewport = viewport + // Store some values that we'll need later (but don't want to force a relayout for) + this.visible = visibleLines(display, cm.doc, viewport) + this.editorIsHidden = !display.wrapper.offsetWidth + this.wrapperHeight = display.wrapper.clientHeight + this.wrapperWidth = display.wrapper.clientWidth + this.oldDisplayWidth = displayWidth(cm) + this.force = force + this.dims = getDimensions(cm) + this.events = [] +}; + +DisplayUpdate.prototype.signal = function (emitter, type) { + if (hasHandler(emitter, type)) + { this.events.push(arguments) } +}; +DisplayUpdate.prototype.finish = function () { + var this$1 = this; + + for (var i = 0; i < this.events.length; i++) + { signal.apply(null, this$1.events[i]) } +}; + +function maybeClipScrollbars(cm) { + var display = cm.display + if (!display.scrollbarsClipped && display.scroller.offsetWidth) { + display.nativeBarWidth = display.scroller.offsetWidth - display.scroller.clientWidth + display.heightForcer.style.height = scrollGap(cm) + "px" + display.sizer.style.marginBottom = -display.nativeBarWidth + "px" + display.sizer.style.borderRightWidth = scrollGap(cm) + "px" + display.scrollbarsClipped = true + } +} + +// Does the actual updating of the line display. Bails out +// (returning false) when there is nothing to be done and forced is +// false. +function updateDisplayIfNeeded(cm, update) { + var display = cm.display, doc = cm.doc + + if (update.editorIsHidden) { + resetView(cm) + return false + } + + // Bail out if the visible area is already rendered and nothing changed. + if (!update.force && + update.visible.from >= display.viewFrom && update.visible.to <= display.viewTo && + (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo) && + display.renderedView == display.view && countDirtyView(cm) == 0) + { return false } + + if (maybeUpdateLineNumberWidth(cm)) { + resetView(cm) + update.dims = getDimensions(cm) + } + + // Compute a suitable new viewport (from & to) + var end = doc.first + doc.size + var from = Math.max(update.visible.from - cm.options.viewportMargin, doc.first) + var to = Math.min(end, update.visible.to + cm.options.viewportMargin) + if (display.viewFrom < from && from - display.viewFrom < 20) { from = Math.max(doc.first, display.viewFrom) } + if (display.viewTo > to && display.viewTo - to < 20) { to = Math.min(end, display.viewTo) } + if (sawCollapsedSpans) { + from = visualLineNo(cm.doc, from) + to = visualLineEndNo(cm.doc, to) + } + + var different = from != display.viewFrom || to != display.viewTo || + display.lastWrapHeight != update.wrapperHeight || display.lastWrapWidth != update.wrapperWidth + adjustView(cm, from, to) + + display.viewOffset = heightAtLine(getLine(cm.doc, display.viewFrom)) + // Position the mover div to align with the current scroll position + cm.display.mover.style.top = display.viewOffset + "px" + + var toUpdate = countDirtyView(cm) + if (!different && toUpdate == 0 && !update.force && display.renderedView == display.view && + (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo)) + { return false } + + // For big changes, we hide the enclosing element during the + // update, since that speeds up the operations on most browsers. + var focused = activeElt() + if (toUpdate > 4) { display.lineDiv.style.display = "none" } + patchDisplay(cm, display.updateLineNumbers, update.dims) + if (toUpdate > 4) { display.lineDiv.style.display = "" } + display.renderedView = display.view + // There might have been a widget with a focused element that got + // hidden or updated, if so re-focus it. + if (focused && activeElt() != focused && focused.offsetHeight) { focused.focus() } + + // Prevent selection and cursors from interfering with the scroll + // width and height. + removeChildren(display.cursorDiv) + removeChildren(display.selectionDiv) + display.gutters.style.height = display.sizer.style.minHeight = 0 + + if (different) { + display.lastWrapHeight = update.wrapperHeight + display.lastWrapWidth = update.wrapperWidth + startWorker(cm, 400) + } + + display.updateLineNumbers = null + + return true +} + +function postUpdateDisplay(cm, update) { + var viewport = update.viewport + + for (var first = true;; first = false) { + if (!first || !cm.options.lineWrapping || update.oldDisplayWidth == displayWidth(cm)) { + // Clip forced viewport to actual scrollable area. + if (viewport && viewport.top != null) + { viewport = {top: Math.min(cm.doc.height + paddingVert(cm.display) - displayHeight(cm), viewport.top)} } + // Updated line heights might result in the drawn area not + // actually covering the viewport. Keep looping until it does. + update.visible = visibleLines(cm.display, cm.doc, viewport) + if (update.visible.from >= cm.display.viewFrom && update.visible.to <= cm.display.viewTo) + { break } + } + if (!updateDisplayIfNeeded(cm, update)) { break } + updateHeightsInViewport(cm) + var barMeasure = measureForScrollbars(cm) + updateSelection(cm) + updateScrollbars(cm, barMeasure) + setDocumentHeight(cm, barMeasure) + } + + update.signal(cm, "update", cm) + if (cm.display.viewFrom != cm.display.reportedViewFrom || cm.display.viewTo != cm.display.reportedViewTo) { + update.signal(cm, "viewportChange", cm, cm.display.viewFrom, cm.display.viewTo) + cm.display.reportedViewFrom = cm.display.viewFrom; cm.display.reportedViewTo = cm.display.viewTo + } +} + +function updateDisplaySimple(cm, viewport) { + var update = new DisplayUpdate(cm, viewport) + if (updateDisplayIfNeeded(cm, update)) { + updateHeightsInViewport(cm) + postUpdateDisplay(cm, update) + var barMeasure = measureForScrollbars(cm) + updateSelection(cm) + updateScrollbars(cm, barMeasure) + setDocumentHeight(cm, barMeasure) + update.finish() + } +} + +// Sync the actual display DOM structure with display.view, removing +// nodes for lines that are no longer in view, and creating the ones +// that are not there yet, and updating the ones that are out of +// date. +function patchDisplay(cm, updateNumbersFrom, dims) { + var display = cm.display, lineNumbers = cm.options.lineNumbers + var container = display.lineDiv, cur = container.firstChild + + function rm(node) { + var next = node.nextSibling + // Works around a throw-scroll bug in OS X Webkit + if (webkit && mac && cm.display.currentWheelTarget == node) + { node.style.display = "none" } + else + { node.parentNode.removeChild(node) } + return next + } + + var view = display.view, lineN = display.viewFrom + // Loop over the elements in the view, syncing cur (the DOM nodes + // in display.lineDiv) with the view as we go. + for (var i = 0; i < view.length; i++) { + var lineView = view[i] + if (lineView.hidden) { + } else if (!lineView.node || lineView.node.parentNode != container) { // Not drawn yet + var node = buildLineElement(cm, lineView, lineN, dims) + container.insertBefore(node, cur) + } else { // Already drawn + while (cur != lineView.node) { cur = rm(cur) } + var updateNumber = lineNumbers && updateNumbersFrom != null && + updateNumbersFrom <= lineN && lineView.lineNumber + if (lineView.changes) { + if (indexOf(lineView.changes, "gutter") > -1) { updateNumber = false } + updateLineForChanges(cm, lineView, lineN, dims) + } + if (updateNumber) { + removeChildren(lineView.lineNumber) + lineView.lineNumber.appendChild(document.createTextNode(lineNumberFor(cm.options, lineN))) + } + cur = lineView.node.nextSibling + } + lineN += lineView.size + } + while (cur) { cur = rm(cur) } +} + +function updateGutterSpace(cm) { + var width = cm.display.gutters.offsetWidth + cm.display.sizer.style.marginLeft = width + "px" +} + +function setDocumentHeight(cm, measure) { + cm.display.sizer.style.minHeight = measure.docHeight + "px" + cm.display.heightForcer.style.top = measure.docHeight + "px" + cm.display.gutters.style.height = (measure.docHeight + cm.display.barHeight + scrollGap(cm)) + "px" +} + +// Rebuild the gutter elements, ensure the margin to the left of the +// code matches their width. +function updateGutters(cm) { + var gutters = cm.display.gutters, specs = cm.options.gutters + removeChildren(gutters) + var i = 0 + for (; i < specs.length; ++i) { + var gutterClass = specs[i] + var gElt = gutters.appendChild(elt("div", null, "CodeMirror-gutter " + gutterClass)) + if (gutterClass == "CodeMirror-linenumbers") { + cm.display.lineGutter = gElt + gElt.style.width = (cm.display.lineNumWidth || 1) + "px" + } + } + gutters.style.display = i ? "" : "none" + updateGutterSpace(cm) +} + +// Make sure the gutters options contains the element +// "CodeMirror-linenumbers" when the lineNumbers option is true. +function setGuttersForLineNumbers(options) { + var found = indexOf(options.gutters, "CodeMirror-linenumbers") + if (found == -1 && options.lineNumbers) { + options.gutters = options.gutters.concat(["CodeMirror-linenumbers"]) + } else if (found > -1 && !options.lineNumbers) { + options.gutters = options.gutters.slice(0) + options.gutters.splice(found, 1) + } +} + +// Selection objects are immutable. A new one is created every time +// the selection changes. A selection is one or more non-overlapping +// (and non-touching) ranges, sorted, and an integer that indicates +// which one is the primary selection (the one that's scrolled into +// view, that getCursor returns, etc). +function Selection(ranges, primIndex) { + this.ranges = ranges + this.primIndex = primIndex +} + +Selection.prototype = { + primary: function() { return this.ranges[this.primIndex] }, + equals: function(other) { + var this$1 = this; + + if (other == this) { return true } + if (other.primIndex != this.primIndex || other.ranges.length != this.ranges.length) { return false } + for (var i = 0; i < this.ranges.length; i++) { + var here = this$1.ranges[i], there = other.ranges[i] + if (cmp(here.anchor, there.anchor) != 0 || cmp(here.head, there.head) != 0) { return false } + } + return true + }, + deepCopy: function() { + var this$1 = this; + + var out = [] + for (var i = 0; i < this.ranges.length; i++) + { out[i] = new Range(copyPos(this$1.ranges[i].anchor), copyPos(this$1.ranges[i].head)) } + return new Selection(out, this.primIndex) + }, + somethingSelected: function() { + var this$1 = this; + + for (var i = 0; i < this.ranges.length; i++) + { if (!this$1.ranges[i].empty()) { return true } } + return false + }, + contains: function(pos, end) { + var this$1 = this; + + if (!end) { end = pos } + for (var i = 0; i < this.ranges.length; i++) { + var range = this$1.ranges[i] + if (cmp(end, range.from()) >= 0 && cmp(pos, range.to()) <= 0) + { return i } + } + return -1 + } +} + +function Range(anchor, head) { + this.anchor = anchor; this.head = head +} + +Range.prototype = { + from: function() { return minPos(this.anchor, this.head) }, + to: function() { return maxPos(this.anchor, this.head) }, + empty: function() { + return this.head.line == this.anchor.line && this.head.ch == this.anchor.ch + } +} + +// Take an unsorted, potentially overlapping set of ranges, and +// build a selection out of it. 'Consumes' ranges array (modifying +// it). +function normalizeSelection(ranges, primIndex) { + var prim = ranges[primIndex] + ranges.sort(function (a, b) { return cmp(a.from(), b.from()); }) + primIndex = indexOf(ranges, prim) + for (var i = 1; i < ranges.length; i++) { + var cur = ranges[i], prev = ranges[i - 1] + if (cmp(prev.to(), cur.from()) >= 0) { + var from = minPos(prev.from(), cur.from()), to = maxPos(prev.to(), cur.to()) + var inv = prev.empty() ? cur.from() == cur.head : prev.from() == prev.head + if (i <= primIndex) { --primIndex } + ranges.splice(--i, 2, new Range(inv ? to : from, inv ? from : to)) + } + } + return new Selection(ranges, primIndex) +} + +function simpleSelection(anchor, head) { + return new Selection([new Range(anchor, head || anchor)], 0) +} + +// Compute the position of the end of a change (its 'to' property +// refers to the pre-change end). +function changeEnd(change) { + if (!change.text) { return change.to } + return Pos(change.from.line + change.text.length - 1, + lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0)) +} + +// Adjust a position to refer to the post-change position of the +// same text, or the end of the change if the change covers it. +function adjustForChange(pos, change) { + if (cmp(pos, change.from) < 0) { return pos } + if (cmp(pos, change.to) <= 0) { return changeEnd(change) } + + var line = pos.line + change.text.length - (change.to.line - change.from.line) - 1, ch = pos.ch + if (pos.line == change.to.line) { ch += changeEnd(change).ch - change.to.ch } + return Pos(line, ch) +} + +function computeSelAfterChange(doc, change) { + var out = [] + for (var i = 0; i < doc.sel.ranges.length; i++) { + var range = doc.sel.ranges[i] + out.push(new Range(adjustForChange(range.anchor, change), + adjustForChange(range.head, change))) + } + return normalizeSelection(out, doc.sel.primIndex) +} + +function offsetPos(pos, old, nw) { + if (pos.line == old.line) + { return Pos(nw.line, pos.ch - old.ch + nw.ch) } + else + { return Pos(nw.line + (pos.line - old.line), pos.ch) } +} + +// Used by replaceSelections to allow moving the selection to the +// start or around the replaced test. Hint may be "start" or "around". +function computeReplacedSel(doc, changes, hint) { + var out = [] + var oldPrev = Pos(doc.first, 0), newPrev = oldPrev + for (var i = 0; i < changes.length; i++) { + var change = changes[i] + var from = offsetPos(change.from, oldPrev, newPrev) + var to = offsetPos(changeEnd(change), oldPrev, newPrev) + oldPrev = change.to + newPrev = to + if (hint == "around") { + var range = doc.sel.ranges[i], inv = cmp(range.head, range.anchor) < 0 + out[i] = new Range(inv ? to : from, inv ? from : to) + } else { + out[i] = new Range(from, from) + } + } + return new Selection(out, doc.sel.primIndex) +} + +// Used to get the editor into a consistent state again when options change. + +function loadMode(cm) { + cm.doc.mode = getMode(cm.options, cm.doc.modeOption) + resetModeState(cm) +} + +function resetModeState(cm) { + cm.doc.iter(function (line) { + if (line.stateAfter) { line.stateAfter = null } + if (line.styles) { line.styles = null } + }) + cm.doc.frontier = cm.doc.first + startWorker(cm, 100) + cm.state.modeGen++ + if (cm.curOp) { regChange(cm) } +} + +// DOCUMENT DATA STRUCTURE + +// By default, updates that start and end at the beginning of a line +// are treated specially, in order to make the association of line +// widgets and marker elements with the text behave more intuitive. +function isWholeLineUpdate(doc, change) { + return change.from.ch == 0 && change.to.ch == 0 && lst(change.text) == "" && + (!doc.cm || doc.cm.options.wholeLineUpdateBefore) +} + +// Perform a change on the document data structure. +function updateDoc(doc, change, markedSpans, estimateHeight) { + function spansFor(n) {return markedSpans ? markedSpans[n] : null} + function update(line, text, spans) { + updateLine(line, text, spans, estimateHeight) + signalLater(line, "change", line, change) + } + function linesFor(start, end) { + var result = [] + for (var i = start; i < end; ++i) + { result.push(new Line(text[i], spansFor(i), estimateHeight)) } + return result + } + + var from = change.from, to = change.to, text = change.text + var firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line) + var lastText = lst(text), lastSpans = spansFor(text.length - 1), nlines = to.line - from.line + + // Adjust the line structure + if (change.full) { + doc.insert(0, linesFor(0, text.length)) + doc.remove(text.length, doc.size - text.length) + } else if (isWholeLineUpdate(doc, change)) { + // This is a whole-line replace. Treated specially to make + // sure line objects move the way they are supposed to. + var added = linesFor(0, text.length - 1) + update(lastLine, lastLine.text, lastSpans) + if (nlines) { doc.remove(from.line, nlines) } + if (added.length) { doc.insert(from.line, added) } + } else if (firstLine == lastLine) { + if (text.length == 1) { + update(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.text.slice(to.ch), lastSpans) + } else { + var added$1 = linesFor(1, text.length - 1) + added$1.push(new Line(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight)) + update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)) + doc.insert(from.line + 1, added$1) + } + } else if (text.length == 1) { + update(firstLine, firstLine.text.slice(0, from.ch) + text[0] + lastLine.text.slice(to.ch), spansFor(0)) + doc.remove(from.line + 1, nlines) + } else { + update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)) + update(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans) + var added$2 = linesFor(1, text.length - 1) + if (nlines > 1) { doc.remove(from.line + 1, nlines - 1) } + doc.insert(from.line + 1, added$2) + } + + signalLater(doc, "change", doc, change) +} + +// Call f for all linked documents. +function linkedDocs(doc, f, sharedHistOnly) { + function propagate(doc, skip, sharedHist) { + if (doc.linked) { for (var i = 0; i < doc.linked.length; ++i) { + var rel = doc.linked[i] + if (rel.doc == skip) { continue } + var shared = sharedHist && rel.sharedHist + if (sharedHistOnly && !shared) { continue } + f(rel.doc, shared) + propagate(rel.doc, doc, shared) + } } + } + propagate(doc, null, true) +} + +// Attach a document to an editor. +function attachDoc(cm, doc) { + if (doc.cm) { throw new Error("This document is already in use.") } + cm.doc = doc + doc.cm = cm + estimateLineHeights(cm) + loadMode(cm) + if (!cm.options.lineWrapping) { findMaxLine(cm) } + cm.options.mode = doc.modeOption + regChange(cm) +} + +function History(startGen) { + // Arrays of change events and selections. Doing something adds an + // event to done and clears undo. Undoing moves events from done + // to undone, redoing moves them in the other direction. + this.done = []; this.undone = [] + this.undoDepth = Infinity + // Used to track when changes can be merged into a single undo + // event + this.lastModTime = this.lastSelTime = 0 + this.lastOp = this.lastSelOp = null + this.lastOrigin = this.lastSelOrigin = null + // Used by the isClean() method + this.generation = this.maxGeneration = startGen || 1 +} + +// Create a history change event from an updateDoc-style change +// object. +function historyChangeFromChange(doc, change) { + var histChange = {from: copyPos(change.from), to: changeEnd(change), text: getBetween(doc, change.from, change.to)} + attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1) + linkedDocs(doc, function (doc) { return attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1); }, true) + return histChange +} + +// Pop all selection events off the end of a history array. Stop at +// a change event. +function clearSelectionEvents(array) { + while (array.length) { + var last = lst(array) + if (last.ranges) { array.pop() } + else { break } + } +} + +// Find the top change event in the history. Pop off selection +// events that are in the way. +function lastChangeEvent(hist, force) { + if (force) { + clearSelectionEvents(hist.done) + return lst(hist.done) + } else if (hist.done.length && !lst(hist.done).ranges) { + return lst(hist.done) + } else if (hist.done.length > 1 && !hist.done[hist.done.length - 2].ranges) { + hist.done.pop() + return lst(hist.done) + } +} + +// Register a change in the history. Merges changes that are within +// a single operation, or are close together with an origin that +// allows merging (starting with "+") into a single event. +function addChangeToHistory(doc, change, selAfter, opId) { + var hist = doc.history + hist.undone.length = 0 + var time = +new Date, cur + var last + + if ((hist.lastOp == opId || + hist.lastOrigin == change.origin && change.origin && + ((change.origin.charAt(0) == "+" && doc.cm && hist.lastModTime > time - doc.cm.options.historyEventDelay) || + change.origin.charAt(0) == "*")) && + (cur = lastChangeEvent(hist, hist.lastOp == opId))) { + // Merge this change into the last event + last = lst(cur.changes) + if (cmp(change.from, change.to) == 0 && cmp(change.from, last.to) == 0) { + // Optimized case for simple insertion -- don't want to add + // new changesets for every character typed + last.to = changeEnd(change) + } else { + // Add new sub-event + cur.changes.push(historyChangeFromChange(doc, change)) + } + } else { + // Can not be merged, start a new event. + var before = lst(hist.done) + if (!before || !before.ranges) + { pushSelectionToHistory(doc.sel, hist.done) } + cur = {changes: [historyChangeFromChange(doc, change)], + generation: hist.generation} + hist.done.push(cur) + while (hist.done.length > hist.undoDepth) { + hist.done.shift() + if (!hist.done[0].ranges) { hist.done.shift() } + } + } + hist.done.push(selAfter) + hist.generation = ++hist.maxGeneration + hist.lastModTime = hist.lastSelTime = time + hist.lastOp = hist.lastSelOp = opId + hist.lastOrigin = hist.lastSelOrigin = change.origin + + if (!last) { signal(doc, "historyAdded") } +} + +function selectionEventCanBeMerged(doc, origin, prev, sel) { + var ch = origin.charAt(0) + return ch == "*" || + ch == "+" && + prev.ranges.length == sel.ranges.length && + prev.somethingSelected() == sel.somethingSelected() && + new Date - doc.history.lastSelTime <= (doc.cm ? doc.cm.options.historyEventDelay : 500) +} + +// Called whenever the selection changes, sets the new selection as +// the pending selection in the history, and pushes the old pending +// selection into the 'done' array when it was significantly +// different (in number of selected ranges, emptiness, or time). +function addSelectionToHistory(doc, sel, opId, options) { + var hist = doc.history, origin = options && options.origin + + // A new event is started when the previous origin does not match + // the current, or the origins don't allow matching. Origins + // starting with * are always merged, those starting with + are + // merged when similar and close together in time. + if (opId == hist.lastSelOp || + (origin && hist.lastSelOrigin == origin && + (hist.lastModTime == hist.lastSelTime && hist.lastOrigin == origin || + selectionEventCanBeMerged(doc, origin, lst(hist.done), sel)))) + { hist.done[hist.done.length - 1] = sel } + else + { pushSelectionToHistory(sel, hist.done) } + + hist.lastSelTime = +new Date + hist.lastSelOrigin = origin + hist.lastSelOp = opId + if (options && options.clearRedo !== false) + { clearSelectionEvents(hist.undone) } +} + +function pushSelectionToHistory(sel, dest) { + var top = lst(dest) + if (!(top && top.ranges && top.equals(sel))) + { dest.push(sel) } +} + +// Used to store marked span information in the history. +function attachLocalSpans(doc, change, from, to) { + var existing = change["spans_" + doc.id], n = 0 + doc.iter(Math.max(doc.first, from), Math.min(doc.first + doc.size, to), function (line) { + if (line.markedSpans) + { (existing || (existing = change["spans_" + doc.id] = {}))[n] = line.markedSpans } + ++n + }) +} + +// When un/re-doing restores text containing marked spans, those +// that have been explicitly cleared should not be restored. +function removeClearedSpans(spans) { + if (!spans) { return null } + var out + for (var i = 0; i < spans.length; ++i) { + if (spans[i].marker.explicitlyCleared) { if (!out) { out = spans.slice(0, i) } } + else if (out) { out.push(spans[i]) } + } + return !out ? spans : out.length ? out : null +} + +// Retrieve and filter the old marked spans stored in a change event. +function getOldSpans(doc, change) { + var found = change["spans_" + doc.id] + if (!found) { return null } + var nw = [] + for (var i = 0; i < change.text.length; ++i) + { nw.push(removeClearedSpans(found[i])) } + return nw +} + +// Used for un/re-doing changes from the history. Combines the +// result of computing the existing spans with the set of spans that +// existed in the history (so that deleting around a span and then +// undoing brings back the span). +function mergeOldSpans(doc, change) { + var old = getOldSpans(doc, change) + var stretched = stretchSpansOverChange(doc, change) + if (!old) { return stretched } + if (!stretched) { return old } + + for (var i = 0; i < old.length; ++i) { + var oldCur = old[i], stretchCur = stretched[i] + if (oldCur && stretchCur) { + spans: for (var j = 0; j < stretchCur.length; ++j) { + var span = stretchCur[j] + for (var k = 0; k < oldCur.length; ++k) + { if (oldCur[k].marker == span.marker) { continue spans } } + oldCur.push(span) + } + } else if (stretchCur) { + old[i] = stretchCur + } + } + return old +} + +// Used both to provide a JSON-safe object in .getHistory, and, when +// detaching a document, to split the history in two +function copyHistoryArray(events, newGroup, instantiateSel) { + var copy = [] + for (var i = 0; i < events.length; ++i) { + var event = events[i] + if (event.ranges) { + copy.push(instantiateSel ? Selection.prototype.deepCopy.call(event) : event) + continue + } + var changes = event.changes, newChanges = [] + copy.push({changes: newChanges}) + for (var j = 0; j < changes.length; ++j) { + var change = changes[j], m = (void 0) + newChanges.push({from: change.from, to: change.to, text: change.text}) + if (newGroup) { for (var prop in change) { if (m = prop.match(/^spans_(\d+)$/)) { + if (indexOf(newGroup, Number(m[1])) > -1) { + lst(newChanges)[prop] = change[prop] + delete change[prop] + } + } } } + } + } + return copy +} + +// The 'scroll' parameter given to many of these indicated whether +// the new cursor position should be scrolled into view after +// modifying the selection. + +// If shift is held or the extend flag is set, extends a range to +// include a given position (and optionally a second position). +// Otherwise, simply returns the range between the given positions. +// Used for cursor motion and such. +function extendRange(doc, range, head, other) { + if (doc.cm && doc.cm.display.shift || doc.extend) { + var anchor = range.anchor + if (other) { + var posBefore = cmp(head, anchor) < 0 + if (posBefore != (cmp(other, anchor) < 0)) { + anchor = head + head = other + } else if (posBefore != (cmp(head, other) < 0)) { + head = other + } + } + return new Range(anchor, head) + } else { + return new Range(other || head, head) + } +} + +// Extend the primary selection range, discard the rest. +function extendSelection(doc, head, other, options) { + setSelection(doc, new Selection([extendRange(doc, doc.sel.primary(), head, other)], 0), options) +} + +// Extend all selections (pos is an array of selections with length +// equal the number of selections) +function extendSelections(doc, heads, options) { + var out = [] + for (var i = 0; i < doc.sel.ranges.length; i++) + { out[i] = extendRange(doc, doc.sel.ranges[i], heads[i], null) } + var newSel = normalizeSelection(out, doc.sel.primIndex) + setSelection(doc, newSel, options) +} + +// Updates a single range in the selection. +function replaceOneSelection(doc, i, range, options) { + var ranges = doc.sel.ranges.slice(0) + ranges[i] = range + setSelection(doc, normalizeSelection(ranges, doc.sel.primIndex), options) +} + +// Reset the selection to a single range. +function setSimpleSelection(doc, anchor, head, options) { + setSelection(doc, simpleSelection(anchor, head), options) +} + +// Give beforeSelectionChange handlers a change to influence a +// selection update. +function filterSelectionChange(doc, sel, options) { + var obj = { + ranges: sel.ranges, + update: function(ranges) { + var this$1 = this; + + this.ranges = [] + for (var i = 0; i < ranges.length; i++) + { this$1.ranges[i] = new Range(clipPos(doc, ranges[i].anchor), + clipPos(doc, ranges[i].head)) } + }, + origin: options && options.origin + } + signal(doc, "beforeSelectionChange", doc, obj) + if (doc.cm) { signal(doc.cm, "beforeSelectionChange", doc.cm, obj) } + if (obj.ranges != sel.ranges) { return normalizeSelection(obj.ranges, obj.ranges.length - 1) } + else { return sel } +} + +function setSelectionReplaceHistory(doc, sel, options) { + var done = doc.history.done, last = lst(done) + if (last && last.ranges) { + done[done.length - 1] = sel + setSelectionNoUndo(doc, sel, options) + } else { + setSelection(doc, sel, options) + } +} + +// Set a new selection. +function setSelection(doc, sel, options) { + setSelectionNoUndo(doc, sel, options) + addSelectionToHistory(doc, doc.sel, doc.cm ? doc.cm.curOp.id : NaN, options) +} + +function setSelectionNoUndo(doc, sel, options) { + if (hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange")) + { sel = filterSelectionChange(doc, sel, options) } + + var bias = options && options.bias || + (cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1) + setSelectionInner(doc, skipAtomicInSelection(doc, sel, bias, true)) + + if (!(options && options.scroll === false) && doc.cm) + { ensureCursorVisible(doc.cm) } +} + +function setSelectionInner(doc, sel) { + if (sel.equals(doc.sel)) { return } + + doc.sel = sel + + if (doc.cm) { + doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged = true + signalCursorActivity(doc.cm) + } + signalLater(doc, "cursorActivity", doc) +} + +// Verify that the selection does not partially select any atomic +// marked ranges. +function reCheckSelection(doc) { + setSelectionInner(doc, skipAtomicInSelection(doc, doc.sel, null, false), sel_dontScroll) +} + +// Return a selection that does not partially select any atomic +// ranges. +function skipAtomicInSelection(doc, sel, bias, mayClear) { + var out + for (var i = 0; i < sel.ranges.length; i++) { + var range = sel.ranges[i] + var old = sel.ranges.length == doc.sel.ranges.length && doc.sel.ranges[i] + var newAnchor = skipAtomic(doc, range.anchor, old && old.anchor, bias, mayClear) + var newHead = skipAtomic(doc, range.head, old && old.head, bias, mayClear) + if (out || newAnchor != range.anchor || newHead != range.head) { + if (!out) { out = sel.ranges.slice(0, i) } + out[i] = new Range(newAnchor, newHead) + } + } + return out ? normalizeSelection(out, sel.primIndex) : sel +} + +function skipAtomicInner(doc, pos, oldPos, dir, mayClear) { + var line = getLine(doc, pos.line) + if (line.markedSpans) { for (var i = 0; i < line.markedSpans.length; ++i) { + var sp = line.markedSpans[i], m = sp.marker + if ((sp.from == null || (m.inclusiveLeft ? sp.from <= pos.ch : sp.from < pos.ch)) && + (sp.to == null || (m.inclusiveRight ? sp.to >= pos.ch : sp.to > pos.ch))) { + if (mayClear) { + signal(m, "beforeCursorEnter") + if (m.explicitlyCleared) { + if (!line.markedSpans) { break } + else {--i; continue} + } + } + if (!m.atomic) { continue } + + if (oldPos) { + var near = m.find(dir < 0 ? 1 : -1), diff = (void 0) + if (dir < 0 ? m.inclusiveRight : m.inclusiveLeft) + { near = movePos(doc, near, -dir, near && near.line == pos.line ? line : null) } + if (near && near.line == pos.line && (diff = cmp(near, oldPos)) && (dir < 0 ? diff < 0 : diff > 0)) + { return skipAtomicInner(doc, near, pos, dir, mayClear) } + } + + var far = m.find(dir < 0 ? -1 : 1) + if (dir < 0 ? m.inclusiveLeft : m.inclusiveRight) + { far = movePos(doc, far, dir, far.line == pos.line ? line : null) } + return far ? skipAtomicInner(doc, far, pos, dir, mayClear) : null + } + } } + return pos +} + +// Ensure a given position is not inside an atomic range. +function skipAtomic(doc, pos, oldPos, bias, mayClear) { + var dir = bias || 1 + var found = skipAtomicInner(doc, pos, oldPos, dir, mayClear) || + (!mayClear && skipAtomicInner(doc, pos, oldPos, dir, true)) || + skipAtomicInner(doc, pos, oldPos, -dir, mayClear) || + (!mayClear && skipAtomicInner(doc, pos, oldPos, -dir, true)) + if (!found) { + doc.cantEdit = true + return Pos(doc.first, 0) + } + return found +} + +function movePos(doc, pos, dir, line) { + if (dir < 0 && pos.ch == 0) { + if (pos.line > doc.first) { return clipPos(doc, Pos(pos.line - 1)) } + else { return null } + } else if (dir > 0 && pos.ch == (line || getLine(doc, pos.line)).text.length) { + if (pos.line < doc.first + doc.size - 1) { return Pos(pos.line + 1, 0) } + else { return null } + } else { + return new Pos(pos.line, pos.ch + dir) + } +} + +function selectAll(cm) { + cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.lastLine()), sel_dontScroll) +} + +// UPDATING + +// Allow "beforeChange" event handlers to influence a change +function filterChange(doc, change, update) { + var obj = { + canceled: false, + from: change.from, + to: change.to, + text: change.text, + origin: change.origin, + cancel: function () { return obj.canceled = true; } + } + if (update) { obj.update = function (from, to, text, origin) { + if (from) { obj.from = clipPos(doc, from) } + if (to) { obj.to = clipPos(doc, to) } + if (text) { obj.text = text } + if (origin !== undefined) { obj.origin = origin } + } } + signal(doc, "beforeChange", doc, obj) + if (doc.cm) { signal(doc.cm, "beforeChange", doc.cm, obj) } + + if (obj.canceled) { return null } + return {from: obj.from, to: obj.to, text: obj.text, origin: obj.origin} +} + +// Apply a change to a document, and add it to the document's +// history, and propagating it to all linked documents. +function makeChange(doc, change, ignoreReadOnly) { + if (doc.cm) { + if (!doc.cm.curOp) { return operation(doc.cm, makeChange)(doc, change, ignoreReadOnly) } + if (doc.cm.state.suppressEdits) { return } + } + + if (hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange")) { + change = filterChange(doc, change, true) + if (!change) { return } + } + + // Possibly split or suppress the update based on the presence + // of read-only spans in its range. + var split = sawReadOnlySpans && !ignoreReadOnly && removeReadOnlyRanges(doc, change.from, change.to) + if (split) { + for (var i = split.length - 1; i >= 0; --i) + { makeChangeInner(doc, {from: split[i].from, to: split[i].to, text: i ? [""] : change.text}) } + } else { + makeChangeInner(doc, change) + } +} + +function makeChangeInner(doc, change) { + if (change.text.length == 1 && change.text[0] == "" && cmp(change.from, change.to) == 0) { return } + var selAfter = computeSelAfterChange(doc, change) + addChangeToHistory(doc, change, selAfter, doc.cm ? doc.cm.curOp.id : NaN) + + makeChangeSingleDoc(doc, change, selAfter, stretchSpansOverChange(doc, change)) + var rebased = [] + + linkedDocs(doc, function (doc, sharedHist) { + if (!sharedHist && indexOf(rebased, doc.history) == -1) { + rebaseHist(doc.history, change) + rebased.push(doc.history) + } + makeChangeSingleDoc(doc, change, null, stretchSpansOverChange(doc, change)) + }) +} + +// Revert a change stored in a document's history. +function makeChangeFromHistory(doc, type, allowSelectionOnly) { + if (doc.cm && doc.cm.state.suppressEdits && !allowSelectionOnly) { return } + + var hist = doc.history, event, selAfter = doc.sel + var source = type == "undo" ? hist.done : hist.undone, dest = type == "undo" ? hist.undone : hist.done + + // Verify that there is a useable event (so that ctrl-z won't + // needlessly clear selection events) + var i = 0 + for (; i < source.length; i++) { + event = source[i] + if (allowSelectionOnly ? event.ranges && !event.equals(doc.sel) : !event.ranges) + { break } + } + if (i == source.length) { return } + hist.lastOrigin = hist.lastSelOrigin = null + + for (;;) { + event = source.pop() + if (event.ranges) { + pushSelectionToHistory(event, dest) + if (allowSelectionOnly && !event.equals(doc.sel)) { + setSelection(doc, event, {clearRedo: false}) + return + } + selAfter = event + } + else { break } + } + + // Build up a reverse change object to add to the opposite history + // stack (redo when undoing, and vice versa). + var antiChanges = [] + pushSelectionToHistory(selAfter, dest) + dest.push({changes: antiChanges, generation: hist.generation}) + hist.generation = event.generation || ++hist.maxGeneration + + var filter = hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange") + + var loop = function ( i ) { + var change = event.changes[i] + change.origin = type + if (filter && !filterChange(doc, change, false)) { + source.length = 0 + return {} + } + + antiChanges.push(historyChangeFromChange(doc, change)) + + var after = i ? computeSelAfterChange(doc, change) : lst(source) + makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change)) + if (!i && doc.cm) { doc.cm.scrollIntoView({from: change.from, to: changeEnd(change)}) } + var rebased = [] + + // Propagate to the linked documents + linkedDocs(doc, function (doc, sharedHist) { + if (!sharedHist && indexOf(rebased, doc.history) == -1) { + rebaseHist(doc.history, change) + rebased.push(doc.history) + } + makeChangeSingleDoc(doc, change, null, mergeOldSpans(doc, change)) + }) + }; + + for (var i$1 = event.changes.length - 1; i$1 >= 0; --i$1) { + var returned = loop( i$1 ); + + if ( returned ) return returned.v; + } +} + +// Sub-views need their line numbers shifted when text is added +// above or below them in the parent document. +function shiftDoc(doc, distance) { + if (distance == 0) { return } + doc.first += distance + doc.sel = new Selection(map(doc.sel.ranges, function (range) { return new Range( + Pos(range.anchor.line + distance, range.anchor.ch), + Pos(range.head.line + distance, range.head.ch) + ); }), doc.sel.primIndex) + if (doc.cm) { + regChange(doc.cm, doc.first, doc.first - distance, distance) + for (var d = doc.cm.display, l = d.viewFrom; l < d.viewTo; l++) + { regLineChange(doc.cm, l, "gutter") } + } +} + +// More lower-level change function, handling only a single document +// (not linked ones). +function makeChangeSingleDoc(doc, change, selAfter, spans) { + if (doc.cm && !doc.cm.curOp) + { return operation(doc.cm, makeChangeSingleDoc)(doc, change, selAfter, spans) } + + if (change.to.line < doc.first) { + shiftDoc(doc, change.text.length - 1 - (change.to.line - change.from.line)) + return + } + if (change.from.line > doc.lastLine()) { return } + + // Clip the change to the size of this doc + if (change.from.line < doc.first) { + var shift = change.text.length - 1 - (doc.first - change.from.line) + shiftDoc(doc, shift) + change = {from: Pos(doc.first, 0), to: Pos(change.to.line + shift, change.to.ch), + text: [lst(change.text)], origin: change.origin} + } + var last = doc.lastLine() + if (change.to.line > last) { + change = {from: change.from, to: Pos(last, getLine(doc, last).text.length), + text: [change.text[0]], origin: change.origin} + } + + change.removed = getBetween(doc, change.from, change.to) + + if (!selAfter) { selAfter = computeSelAfterChange(doc, change) } + if (doc.cm) { makeChangeSingleDocInEditor(doc.cm, change, spans) } + else { updateDoc(doc, change, spans) } + setSelectionNoUndo(doc, selAfter, sel_dontScroll) +} + +// Handle the interaction of a change to a document with the editor +// that this document is part of. +function makeChangeSingleDocInEditor(cm, change, spans) { + var doc = cm.doc, display = cm.display, from = change.from, to = change.to + + var recomputeMaxLength = false, checkWidthStart = from.line + if (!cm.options.lineWrapping) { + checkWidthStart = lineNo(visualLine(getLine(doc, from.line))) + doc.iter(checkWidthStart, to.line + 1, function (line) { + if (line == display.maxLine) { + recomputeMaxLength = true + return true + } + }) + } + + if (doc.sel.contains(change.from, change.to) > -1) + { signalCursorActivity(cm) } + + updateDoc(doc, change, spans, estimateHeight(cm)) + + if (!cm.options.lineWrapping) { + doc.iter(checkWidthStart, from.line + change.text.length, function (line) { + var len = lineLength(line) + if (len > display.maxLineLength) { + display.maxLine = line + display.maxLineLength = len + display.maxLineChanged = true + recomputeMaxLength = false + } + }) + if (recomputeMaxLength) { cm.curOp.updateMaxLine = true } + } + + // Adjust frontier, schedule worker + doc.frontier = Math.min(doc.frontier, from.line) + startWorker(cm, 400) + + var lendiff = change.text.length - (to.line - from.line) - 1 + // Remember that these lines changed, for updating the display + if (change.full) + { regChange(cm) } + else if (from.line == to.line && change.text.length == 1 && !isWholeLineUpdate(cm.doc, change)) + { regLineChange(cm, from.line, "text") } + else + { regChange(cm, from.line, to.line + 1, lendiff) } + + var changesHandler = hasHandler(cm, "changes"), changeHandler = hasHandler(cm, "change") + if (changeHandler || changesHandler) { + var obj = { + from: from, to: to, + text: change.text, + removed: change.removed, + origin: change.origin + } + if (changeHandler) { signalLater(cm, "change", cm, obj) } + if (changesHandler) { (cm.curOp.changeObjs || (cm.curOp.changeObjs = [])).push(obj) } + } + cm.display.selForContextMenu = null +} + +function replaceRange(doc, code, from, to, origin) { + if (!to) { to = from } + if (cmp(to, from) < 0) { var tmp = to; to = from; from = tmp } + if (typeof code == "string") { code = doc.splitLines(code) } + makeChange(doc, {from: from, to: to, text: code, origin: origin}) +} + +// Rebasing/resetting history to deal with externally-sourced changes + +function rebaseHistSelSingle(pos, from, to, diff) { + if (to < pos.line) { + pos.line += diff + } else if (from < pos.line) { + pos.line = from + pos.ch = 0 + } +} + +// Tries to rebase an array of history events given a change in the +// document. If the change touches the same lines as the event, the +// event, and everything 'behind' it, is discarded. If the change is +// before the event, the event's positions are updated. Uses a +// copy-on-write scheme for the positions, to avoid having to +// reallocate them all on every rebase, but also avoid problems with +// shared position objects being unsafely updated. +function rebaseHistArray(array, from, to, diff) { + for (var i = 0; i < array.length; ++i) { + var sub = array[i], ok = true + if (sub.ranges) { + if (!sub.copied) { sub = array[i] = sub.deepCopy(); sub.copied = true } + for (var j = 0; j < sub.ranges.length; j++) { + rebaseHistSelSingle(sub.ranges[j].anchor, from, to, diff) + rebaseHistSelSingle(sub.ranges[j].head, from, to, diff) + } + continue + } + for (var j$1 = 0; j$1 < sub.changes.length; ++j$1) { + var cur = sub.changes[j$1] + if (to < cur.from.line) { + cur.from = Pos(cur.from.line + diff, cur.from.ch) + cur.to = Pos(cur.to.line + diff, cur.to.ch) + } else if (from <= cur.to.line) { + ok = false + break + } + } + if (!ok) { + array.splice(0, i + 1) + i = 0 + } + } +} + +function rebaseHist(hist, change) { + var from = change.from.line, to = change.to.line, diff = change.text.length - (to - from) - 1 + rebaseHistArray(hist.done, from, to, diff) + rebaseHistArray(hist.undone, from, to, diff) +} + +// Utility for applying a change to a line by handle or number, +// returning the number and optionally registering the line as +// changed. +function changeLine(doc, handle, changeType, op) { + var no = handle, line = handle + if (typeof handle == "number") { line = getLine(doc, clipLine(doc, handle)) } + else { no = lineNo(handle) } + if (no == null) { return null } + if (op(line, no) && doc.cm) { regLineChange(doc.cm, no, changeType) } + return line +} + +// The document is represented as a BTree consisting of leaves, with +// chunk of lines in them, and branches, with up to ten leaves or +// other branch nodes below them. The top node is always a branch +// node, and is the document object itself (meaning it has +// additional methods and properties). +// +// All nodes have parent links. The tree is used both to go from +// line numbers to line objects, and to go from objects to numbers. +// It also indexes by height, and is used to convert between height +// and line object, and to find the total height of the document. +// +// See also http://marijnhaverbeke.nl/blog/codemirror-line-tree.html + +function LeafChunk(lines) { + var this$1 = this; + + this.lines = lines + this.parent = null + var height = 0 + for (var i = 0; i < lines.length; ++i) { + lines[i].parent = this$1 + height += lines[i].height + } + this.height = height +} + +LeafChunk.prototype = { + chunkSize: function() { return this.lines.length }, + // Remove the n lines at offset 'at'. + removeInner: function(at, n) { + var this$1 = this; + + for (var i = at, e = at + n; i < e; ++i) { + var line = this$1.lines[i] + this$1.height -= line.height + cleanUpLine(line) + signalLater(line, "delete") + } + this.lines.splice(at, n) + }, + // Helper used to collapse a small branch into a single leaf. + collapse: function(lines) { + lines.push.apply(lines, this.lines) + }, + // Insert the given array of lines at offset 'at', count them as + // having the given height. + insertInner: function(at, lines, height) { + var this$1 = this; + + this.height += height + this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at)) + for (var i = 0; i < lines.length; ++i) { lines[i].parent = this$1 } + }, + // Used to iterate over a part of the tree. + iterN: function(at, n, op) { + var this$1 = this; + + for (var e = at + n; at < e; ++at) + { if (op(this$1.lines[at])) { return true } } + } +} + +function BranchChunk(children) { + var this$1 = this; + + this.children = children + var size = 0, height = 0 + for (var i = 0; i < children.length; ++i) { + var ch = children[i] + size += ch.chunkSize(); height += ch.height + ch.parent = this$1 + } + this.size = size + this.height = height + this.parent = null +} + +BranchChunk.prototype = { + chunkSize: function() { return this.size }, + removeInner: function(at, n) { + var this$1 = this; + + this.size -= n + for (var i = 0; i < this.children.length; ++i) { + var child = this$1.children[i], sz = child.chunkSize() + if (at < sz) { + var rm = Math.min(n, sz - at), oldHeight = child.height + child.removeInner(at, rm) + this$1.height -= oldHeight - child.height + if (sz == rm) { this$1.children.splice(i--, 1); child.parent = null } + if ((n -= rm) == 0) { break } + at = 0 + } else { at -= sz } + } + // If the result is smaller than 25 lines, ensure that it is a + // single leaf node. + if (this.size - n < 25 && + (this.children.length > 1 || !(this.children[0] instanceof LeafChunk))) { + var lines = [] + this.collapse(lines) + this.children = [new LeafChunk(lines)] + this.children[0].parent = this + } + }, + collapse: function(lines) { + var this$1 = this; + + for (var i = 0; i < this.children.length; ++i) { this$1.children[i].collapse(lines) } + }, + insertInner: function(at, lines, height) { + var this$1 = this; + + this.size += lines.length + this.height += height + for (var i = 0; i < this.children.length; ++i) { + var child = this$1.children[i], sz = child.chunkSize() + if (at <= sz) { + child.insertInner(at, lines, height) + if (child.lines && child.lines.length > 50) { + // To avoid memory thrashing when child.lines is huge (e.g. first view of a large file), it's never spliced. + // Instead, small slices are taken. They're taken in order because sequential memory accesses are fastest. + var remaining = child.lines.length % 25 + 25 + for (var pos = remaining; pos < child.lines.length;) { + var leaf = new LeafChunk(child.lines.slice(pos, pos += 25)) + child.height -= leaf.height + this$1.children.splice(++i, 0, leaf) + leaf.parent = this$1 + } + child.lines = child.lines.slice(0, remaining) + this$1.maybeSpill() + } + break + } + at -= sz + } + }, + // When a node has grown, check whether it should be split. + maybeSpill: function() { + if (this.children.length <= 10) { return } + var me = this + do { + var spilled = me.children.splice(me.children.length - 5, 5) + var sibling = new BranchChunk(spilled) + if (!me.parent) { // Become the parent node + var copy = new BranchChunk(me.children) + copy.parent = me + me.children = [copy, sibling] + me = copy + } else { + me.size -= sibling.size + me.height -= sibling.height + var myIndex = indexOf(me.parent.children, me) + me.parent.children.splice(myIndex + 1, 0, sibling) + } + sibling.parent = me.parent + } while (me.children.length > 10) + me.parent.maybeSpill() + }, + iterN: function(at, n, op) { + var this$1 = this; + + for (var i = 0; i < this.children.length; ++i) { + var child = this$1.children[i], sz = child.chunkSize() + if (at < sz) { + var used = Math.min(n, sz - at) + if (child.iterN(at, used, op)) { return true } + if ((n -= used) == 0) { break } + at = 0 + } else { at -= sz } + } + } +} + +// Line widgets are block elements displayed above or below a line. + +function LineWidget(doc, node, options) { + var this$1 = this; + + if (options) { for (var opt in options) { if (options.hasOwnProperty(opt)) + { this$1[opt] = options[opt] } } } + this.doc = doc + this.node = node +} +eventMixin(LineWidget) + +function adjustScrollWhenAboveVisible(cm, line, diff) { + if (heightAtLine(line) < ((cm.curOp && cm.curOp.scrollTop) || cm.doc.scrollTop)) + { addToScrollPos(cm, null, diff) } +} + +LineWidget.prototype.clear = function() { + var this$1 = this; + + var cm = this.doc.cm, ws = this.line.widgets, line = this.line, no = lineNo(line) + if (no == null || !ws) { return } + for (var i = 0; i < ws.length; ++i) { if (ws[i] == this$1) { ws.splice(i--, 1) } } + if (!ws.length) { line.widgets = null } + var height = widgetHeight(this) + updateLineHeight(line, Math.max(0, line.height - height)) + if (cm) { runInOp(cm, function () { + adjustScrollWhenAboveVisible(cm, line, -height) + regLineChange(cm, no, "widget") + }) } +} +LineWidget.prototype.changed = function() { + var oldH = this.height, cm = this.doc.cm, line = this.line + this.height = null + var diff = widgetHeight(this) - oldH + if (!diff) { return } + updateLineHeight(line, line.height + diff) + if (cm) { runInOp(cm, function () { + cm.curOp.forceUpdate = true + adjustScrollWhenAboveVisible(cm, line, diff) + }) } +} + +function addLineWidget(doc, handle, node, options) { + var widget = new LineWidget(doc, node, options) + var cm = doc.cm + if (cm && widget.noHScroll) { cm.display.alignWidgets = true } + changeLine(doc, handle, "widget", function (line) { + var widgets = line.widgets || (line.widgets = []) + if (widget.insertAt == null) { widgets.push(widget) } + else { widgets.splice(Math.min(widgets.length - 1, Math.max(0, widget.insertAt)), 0, widget) } + widget.line = line + if (cm && !lineIsHidden(doc, line)) { + var aboveVisible = heightAtLine(line) < doc.scrollTop + updateLineHeight(line, line.height + widgetHeight(widget)) + if (aboveVisible) { addToScrollPos(cm, null, widget.height) } + cm.curOp.forceUpdate = true + } + return true + }) + return widget +} + +// TEXTMARKERS + +// Created with markText and setBookmark methods. A TextMarker is a +// handle that can be used to clear or find a marked position in the +// document. Line objects hold arrays (markedSpans) containing +// {from, to, marker} object pointing to such marker objects, and +// indicating that such a marker is present on that line. Multiple +// lines may point to the same marker when it spans across lines. +// The spans will have null for their from/to properties when the +// marker continues beyond the start/end of the line. Markers have +// links back to the lines they currently touch. + +// Collapsed markers have unique ids, in order to be able to order +// them, which is needed for uniquely determining an outer marker +// when they overlap (they may nest, but not partially overlap). +var nextMarkerId = 0 + +function TextMarker(doc, type) { + this.lines = [] + this.type = type + this.doc = doc + this.id = ++nextMarkerId +} +eventMixin(TextMarker) + +// Clear the marker. +TextMarker.prototype.clear = function() { + var this$1 = this; + + if (this.explicitlyCleared) { return } + var cm = this.doc.cm, withOp = cm && !cm.curOp + if (withOp) { startOperation(cm) } + if (hasHandler(this, "clear")) { + var found = this.find() + if (found) { signalLater(this, "clear", found.from, found.to) } + } + var min = null, max = null + for (var i = 0; i < this.lines.length; ++i) { + var line = this$1.lines[i] + var span = getMarkedSpanFor(line.markedSpans, this$1) + if (cm && !this$1.collapsed) { regLineChange(cm, lineNo(line), "text") } + else if (cm) { + if (span.to != null) { max = lineNo(line) } + if (span.from != null) { min = lineNo(line) } + } + line.markedSpans = removeMarkedSpan(line.markedSpans, span) + if (span.from == null && this$1.collapsed && !lineIsHidden(this$1.doc, line) && cm) + { updateLineHeight(line, textHeight(cm.display)) } + } + if (cm && this.collapsed && !cm.options.lineWrapping) { for (var i$1 = 0; i$1 < this.lines.length; ++i$1) { + var visual = visualLine(this$1.lines[i$1]), len = lineLength(visual) + if (len > cm.display.maxLineLength) { + cm.display.maxLine = visual + cm.display.maxLineLength = len + cm.display.maxLineChanged = true + } + } } + + if (min != null && cm && this.collapsed) { regChange(cm, min, max + 1) } + this.lines.length = 0 + this.explicitlyCleared = true + if (this.atomic && this.doc.cantEdit) { + this.doc.cantEdit = false + if (cm) { reCheckSelection(cm.doc) } + } + if (cm) { signalLater(cm, "markerCleared", cm, this) } + if (withOp) { endOperation(cm) } + if (this.parent) { this.parent.clear() } +} + +// Find the position of the marker in the document. Returns a {from, +// to} object by default. Side can be passed to get a specific side +// -- 0 (both), -1 (left), or 1 (right). When lineObj is true, the +// Pos objects returned contain a line object, rather than a line +// number (used to prevent looking up the same line twice). +TextMarker.prototype.find = function(side, lineObj) { + var this$1 = this; + + if (side == null && this.type == "bookmark") { side = 1 } + var from, to + for (var i = 0; i < this.lines.length; ++i) { + var line = this$1.lines[i] + var span = getMarkedSpanFor(line.markedSpans, this$1) + if (span.from != null) { + from = Pos(lineObj ? line : lineNo(line), span.from) + if (side == -1) { return from } + } + if (span.to != null) { + to = Pos(lineObj ? line : lineNo(line), span.to) + if (side == 1) { return to } + } + } + return from && {from: from, to: to} +} + +// Signals that the marker's widget changed, and surrounding layout +// should be recomputed. +TextMarker.prototype.changed = function() { + var pos = this.find(-1, true), widget = this, cm = this.doc.cm + if (!pos || !cm) { return } + runInOp(cm, function () { + var line = pos.line, lineN = lineNo(pos.line) + var view = findViewForLine(cm, lineN) + if (view) { + clearLineMeasurementCacheFor(view) + cm.curOp.selectionChanged = cm.curOp.forceUpdate = true + } + cm.curOp.updateMaxLine = true + if (!lineIsHidden(widget.doc, line) && widget.height != null) { + var oldHeight = widget.height + widget.height = null + var dHeight = widgetHeight(widget) - oldHeight + if (dHeight) + { updateLineHeight(line, line.height + dHeight) } + } + }) +} + +TextMarker.prototype.attachLine = function(line) { + if (!this.lines.length && this.doc.cm) { + var op = this.doc.cm.curOp + if (!op.maybeHiddenMarkers || indexOf(op.maybeHiddenMarkers, this) == -1) + { (op.maybeUnhiddenMarkers || (op.maybeUnhiddenMarkers = [])).push(this) } + } + this.lines.push(line) +} +TextMarker.prototype.detachLine = function(line) { + this.lines.splice(indexOf(this.lines, line), 1) + if (!this.lines.length && this.doc.cm) { + var op = this.doc.cm.curOp + ;(op.maybeHiddenMarkers || (op.maybeHiddenMarkers = [])).push(this) + } +} + +// Create a marker, wire it up to the right lines, and +function markText(doc, from, to, options, type) { + // Shared markers (across linked documents) are handled separately + // (markTextShared will call out to this again, once per + // document). + if (options && options.shared) { return markTextShared(doc, from, to, options, type) } + // Ensure we are in an operation. + if (doc.cm && !doc.cm.curOp) { return operation(doc.cm, markText)(doc, from, to, options, type) } + + var marker = new TextMarker(doc, type), diff = cmp(from, to) + if (options) { copyObj(options, marker, false) } + // Don't connect empty markers unless clearWhenEmpty is false + if (diff > 0 || diff == 0 && marker.clearWhenEmpty !== false) + { return marker } + if (marker.replacedWith) { + // Showing up as a widget implies collapsed (widget replaces text) + marker.collapsed = true + marker.widgetNode = elt("span", [marker.replacedWith], "CodeMirror-widget") + marker.widgetNode.setAttribute("role", "presentation") // hide from accessibility tree + if (!options.handleMouseEvents) { marker.widgetNode.setAttribute("cm-ignore-events", "true") } + if (options.insertLeft) { marker.widgetNode.insertLeft = true } + } + if (marker.collapsed) { + if (conflictingCollapsedRange(doc, from.line, from, to, marker) || + from.line != to.line && conflictingCollapsedRange(doc, to.line, from, to, marker)) + { throw new Error("Inserting collapsed marker partially overlapping an existing one") } + seeCollapsedSpans() + } + + if (marker.addToHistory) + { addChangeToHistory(doc, {from: from, to: to, origin: "markText"}, doc.sel, NaN) } + + var curLine = from.line, cm = doc.cm, updateMaxLine + doc.iter(curLine, to.line + 1, function (line) { + if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(line) == cm.display.maxLine) + { updateMaxLine = true } + if (marker.collapsed && curLine != from.line) { updateLineHeight(line, 0) } + addMarkedSpan(line, new MarkedSpan(marker, + curLine == from.line ? from.ch : null, + curLine == to.line ? to.ch : null)) + ++curLine + }) + // lineIsHidden depends on the presence of the spans, so needs a second pass + if (marker.collapsed) { doc.iter(from.line, to.line + 1, function (line) { + if (lineIsHidden(doc, line)) { updateLineHeight(line, 0) } + }) } + + if (marker.clearOnEnter) { on(marker, "beforeCursorEnter", function () { return marker.clear(); }) } + + if (marker.readOnly) { + seeReadOnlySpans() + if (doc.history.done.length || doc.history.undone.length) + { doc.clearHistory() } + } + if (marker.collapsed) { + marker.id = ++nextMarkerId + marker.atomic = true + } + if (cm) { + // Sync editor state + if (updateMaxLine) { cm.curOp.updateMaxLine = true } + if (marker.collapsed) + { regChange(cm, from.line, to.line + 1) } + else if (marker.className || marker.title || marker.startStyle || marker.endStyle || marker.css) + { for (var i = from.line; i <= to.line; i++) { regLineChange(cm, i, "text") } } + if (marker.atomic) { reCheckSelection(cm.doc) } + signalLater(cm, "markerAdded", cm, marker) + } + return marker +} + +// SHARED TEXTMARKERS + +// A shared marker spans multiple linked documents. It is +// implemented as a meta-marker-object controlling multiple normal +// markers. +function SharedTextMarker(markers, primary) { + var this$1 = this; + + this.markers = markers + this.primary = primary + for (var i = 0; i < markers.length; ++i) + { markers[i].parent = this$1 } +} +eventMixin(SharedTextMarker) + +SharedTextMarker.prototype.clear = function() { + var this$1 = this; + + if (this.explicitlyCleared) { return } + this.explicitlyCleared = true + for (var i = 0; i < this.markers.length; ++i) + { this$1.markers[i].clear() } + signalLater(this, "clear") +} +SharedTextMarker.prototype.find = function(side, lineObj) { + return this.primary.find(side, lineObj) +} + +function markTextShared(doc, from, to, options, type) { + options = copyObj(options) + options.shared = false + var markers = [markText(doc, from, to, options, type)], primary = markers[0] + var widget = options.widgetNode + linkedDocs(doc, function (doc) { + if (widget) { options.widgetNode = widget.cloneNode(true) } + markers.push(markText(doc, clipPos(doc, from), clipPos(doc, to), options, type)) + for (var i = 0; i < doc.linked.length; ++i) + { if (doc.linked[i].isParent) { return } } + primary = lst(markers) + }) + return new SharedTextMarker(markers, primary) +} + +function findSharedMarkers(doc) { + return doc.findMarks(Pos(doc.first, 0), doc.clipPos(Pos(doc.lastLine())), function (m) { return m.parent; }) +} + +function copySharedMarkers(doc, markers) { + for (var i = 0; i < markers.length; i++) { + var marker = markers[i], pos = marker.find() + var mFrom = doc.clipPos(pos.from), mTo = doc.clipPos(pos.to) + if (cmp(mFrom, mTo)) { + var subMark = markText(doc, mFrom, mTo, marker.primary, marker.primary.type) + marker.markers.push(subMark) + subMark.parent = marker + } + } +} + +function detachSharedMarkers(markers) { + var loop = function ( i ) { + var marker = markers[i], linked = [marker.primary.doc] + linkedDocs(marker.primary.doc, function (d) { return linked.push(d); }) + for (var j = 0; j < marker.markers.length; j++) { + var subMarker = marker.markers[j] + if (indexOf(linked, subMarker.doc) == -1) { + subMarker.parent = null + marker.markers.splice(j--, 1) + } + } + }; + + for (var i = 0; i < markers.length; i++) loop( i ); +} + +var nextDocId = 0 +var Doc = function(text, mode, firstLine, lineSep) { + if (!(this instanceof Doc)) { return new Doc(text, mode, firstLine, lineSep) } + if (firstLine == null) { firstLine = 0 } + + BranchChunk.call(this, [new LeafChunk([new Line("", null)])]) + this.first = firstLine + this.scrollTop = this.scrollLeft = 0 + this.cantEdit = false + this.cleanGeneration = 1 + this.frontier = firstLine + var start = Pos(firstLine, 0) + this.sel = simpleSelection(start) + this.history = new History(null) + this.id = ++nextDocId + this.modeOption = mode + this.lineSep = lineSep + this.extend = false + + if (typeof text == "string") { text = this.splitLines(text) } + updateDoc(this, {from: start, to: start, text: text}) + setSelection(this, simpleSelection(start), sel_dontScroll) +} + +Doc.prototype = createObj(BranchChunk.prototype, { + constructor: Doc, + // Iterate over the document. Supports two forms -- with only one + // argument, it calls that for each line in the document. With + // three, it iterates over the range given by the first two (with + // the second being non-inclusive). + iter: function(from, to, op) { + if (op) { this.iterN(from - this.first, to - from, op) } + else { this.iterN(this.first, this.first + this.size, from) } + }, + + // Non-public interface for adding and removing lines. + insert: function(at, lines) { + var height = 0 + for (var i = 0; i < lines.length; ++i) { height += lines[i].height } + this.insertInner(at - this.first, lines, height) + }, + remove: function(at, n) { this.removeInner(at - this.first, n) }, + + // From here, the methods are part of the public interface. Most + // are also available from CodeMirror (editor) instances. + + getValue: function(lineSep) { + var lines = getLines(this, this.first, this.first + this.size) + if (lineSep === false) { return lines } + return lines.join(lineSep || this.lineSeparator()) + }, + setValue: docMethodOp(function(code) { + var top = Pos(this.first, 0), last = this.first + this.size - 1 + makeChange(this, {from: top, to: Pos(last, getLine(this, last).text.length), + text: this.splitLines(code), origin: "setValue", full: true}, true) + setSelection(this, simpleSelection(top)) + }), + replaceRange: function(code, from, to, origin) { + from = clipPos(this, from) + to = to ? clipPos(this, to) : from + replaceRange(this, code, from, to, origin) + }, + getRange: function(from, to, lineSep) { + var lines = getBetween(this, clipPos(this, from), clipPos(this, to)) + if (lineSep === false) { return lines } + return lines.join(lineSep || this.lineSeparator()) + }, + + getLine: function(line) {var l = this.getLineHandle(line); return l && l.text}, + + getLineHandle: function(line) {if (isLine(this, line)) { return getLine(this, line) }}, + getLineNumber: function(line) {return lineNo(line)}, + + getLineHandleVisualStart: function(line) { + if (typeof line == "number") { line = getLine(this, line) } + return visualLine(line) + }, + + lineCount: function() {return this.size}, + firstLine: function() {return this.first}, + lastLine: function() {return this.first + this.size - 1}, + + clipPos: function(pos) {return clipPos(this, pos)}, + + getCursor: function(start) { + var range = this.sel.primary(), pos + if (start == null || start == "head") { pos = range.head } + else if (start == "anchor") { pos = range.anchor } + else if (start == "end" || start == "to" || start === false) { pos = range.to() } + else { pos = range.from() } + return pos + }, + listSelections: function() { return this.sel.ranges }, + somethingSelected: function() {return this.sel.somethingSelected()}, + + setCursor: docMethodOp(function(line, ch, options) { + setSimpleSelection(this, clipPos(this, typeof line == "number" ? Pos(line, ch || 0) : line), null, options) + }), + setSelection: docMethodOp(function(anchor, head, options) { + setSimpleSelection(this, clipPos(this, anchor), clipPos(this, head || anchor), options) + }), + extendSelection: docMethodOp(function(head, other, options) { + extendSelection(this, clipPos(this, head), other && clipPos(this, other), options) + }), + extendSelections: docMethodOp(function(heads, options) { + extendSelections(this, clipPosArray(this, heads), options) + }), + extendSelectionsBy: docMethodOp(function(f, options) { + var heads = map(this.sel.ranges, f) + extendSelections(this, clipPosArray(this, heads), options) + }), + setSelections: docMethodOp(function(ranges, primary, options) { + var this$1 = this; + + if (!ranges.length) { return } + var out = [] + for (var i = 0; i < ranges.length; i++) + { out[i] = new Range(clipPos(this$1, ranges[i].anchor), + clipPos(this$1, ranges[i].head)) } + if (primary == null) { primary = Math.min(ranges.length - 1, this.sel.primIndex) } + setSelection(this, normalizeSelection(out, primary), options) + }), + addSelection: docMethodOp(function(anchor, head, options) { + var ranges = this.sel.ranges.slice(0) + ranges.push(new Range(clipPos(this, anchor), clipPos(this, head || anchor))) + setSelection(this, normalizeSelection(ranges, ranges.length - 1), options) + }), + + getSelection: function(lineSep) { + var this$1 = this; + + var ranges = this.sel.ranges, lines + for (var i = 0; i < ranges.length; i++) { + var sel = getBetween(this$1, ranges[i].from(), ranges[i].to()) + lines = lines ? lines.concat(sel) : sel + } + if (lineSep === false) { return lines } + else { return lines.join(lineSep || this.lineSeparator()) } + }, + getSelections: function(lineSep) { + var this$1 = this; + + var parts = [], ranges = this.sel.ranges + for (var i = 0; i < ranges.length; i++) { + var sel = getBetween(this$1, ranges[i].from(), ranges[i].to()) + if (lineSep !== false) { sel = sel.join(lineSep || this$1.lineSeparator()) } + parts[i] = sel + } + return parts + }, + replaceSelection: function(code, collapse, origin) { + var dup = [] + for (var i = 0; i < this.sel.ranges.length; i++) + { dup[i] = code } + this.replaceSelections(dup, collapse, origin || "+input") + }, + replaceSelections: docMethodOp(function(code, collapse, origin) { + var this$1 = this; + + var changes = [], sel = this.sel + for (var i = 0; i < sel.ranges.length; i++) { + var range = sel.ranges[i] + changes[i] = {from: range.from(), to: range.to(), text: this$1.splitLines(code[i]), origin: origin} + } + var newSel = collapse && collapse != "end" && computeReplacedSel(this, changes, collapse) + for (var i$1 = changes.length - 1; i$1 >= 0; i$1--) + { makeChange(this$1, changes[i$1]) } + if (newSel) { setSelectionReplaceHistory(this, newSel) } + else if (this.cm) { ensureCursorVisible(this.cm) } + }), + undo: docMethodOp(function() {makeChangeFromHistory(this, "undo")}), + redo: docMethodOp(function() {makeChangeFromHistory(this, "redo")}), + undoSelection: docMethodOp(function() {makeChangeFromHistory(this, "undo", true)}), + redoSelection: docMethodOp(function() {makeChangeFromHistory(this, "redo", true)}), + + setExtending: function(val) {this.extend = val}, + getExtending: function() {return this.extend}, + + historySize: function() { + var hist = this.history, done = 0, undone = 0 + for (var i = 0; i < hist.done.length; i++) { if (!hist.done[i].ranges) { ++done } } + for (var i$1 = 0; i$1 < hist.undone.length; i$1++) { if (!hist.undone[i$1].ranges) { ++undone } } + return {undo: done, redo: undone} + }, + clearHistory: function() {this.history = new History(this.history.maxGeneration)}, + + markClean: function() { + this.cleanGeneration = this.changeGeneration(true) + }, + changeGeneration: function(forceSplit) { + if (forceSplit) + { this.history.lastOp = this.history.lastSelOp = this.history.lastOrigin = null } + return this.history.generation + }, + isClean: function (gen) { + return this.history.generation == (gen || this.cleanGeneration) + }, + + getHistory: function() { + return {done: copyHistoryArray(this.history.done), + undone: copyHistoryArray(this.history.undone)} + }, + setHistory: function(histData) { + var hist = this.history = new History(this.history.maxGeneration) + hist.done = copyHistoryArray(histData.done.slice(0), null, true) + hist.undone = copyHistoryArray(histData.undone.slice(0), null, true) + }, + + setGutterMarker: docMethodOp(function(line, gutterID, value) { + return changeLine(this, line, "gutter", function (line) { + var markers = line.gutterMarkers || (line.gutterMarkers = {}) + markers[gutterID] = value + if (!value && isEmpty(markers)) { line.gutterMarkers = null } + return true + }) + }), + + clearGutter: docMethodOp(function(gutterID) { + var this$1 = this; + + this.iter(function (line) { + if (line.gutterMarkers && line.gutterMarkers[gutterID]) { + changeLine(this$1, line, "gutter", function () { + line.gutterMarkers[gutterID] = null + if (isEmpty(line.gutterMarkers)) { line.gutterMarkers = null } + return true + }) + } + }) + }), + + lineInfo: function(line) { + var n + if (typeof line == "number") { + if (!isLine(this, line)) { return null } + n = line + line = getLine(this, line) + if (!line) { return null } + } else { + n = lineNo(line) + if (n == null) { return null } + } + return {line: n, handle: line, text: line.text, gutterMarkers: line.gutterMarkers, + textClass: line.textClass, bgClass: line.bgClass, wrapClass: line.wrapClass, + widgets: line.widgets} + }, + + addLineClass: docMethodOp(function(handle, where, cls) { + return changeLine(this, handle, where == "gutter" ? "gutter" : "class", function (line) { + var prop = where == "text" ? "textClass" + : where == "background" ? "bgClass" + : where == "gutter" ? "gutterClass" : "wrapClass" + if (!line[prop]) { line[prop] = cls } + else if (classTest(cls).test(line[prop])) { return false } + else { line[prop] += " " + cls } + return true + }) + }), + removeLineClass: docMethodOp(function(handle, where, cls) { + return changeLine(this, handle, where == "gutter" ? "gutter" : "class", function (line) { + var prop = where == "text" ? "textClass" + : where == "background" ? "bgClass" + : where == "gutter" ? "gutterClass" : "wrapClass" + var cur = line[prop] + if (!cur) { return false } + else if (cls == null) { line[prop] = null } + else { + var found = cur.match(classTest(cls)) + if (!found) { return false } + var end = found.index + found[0].length + line[prop] = cur.slice(0, found.index) + (!found.index || end == cur.length ? "" : " ") + cur.slice(end) || null + } + return true + }) + }), + + addLineWidget: docMethodOp(function(handle, node, options) { + return addLineWidget(this, handle, node, options) + }), + removeLineWidget: function(widget) { widget.clear() }, + + markText: function(from, to, options) { + return markText(this, clipPos(this, from), clipPos(this, to), options, options && options.type || "range") + }, + setBookmark: function(pos, options) { + var realOpts = {replacedWith: options && (options.nodeType == null ? options.widget : options), + insertLeft: options && options.insertLeft, + clearWhenEmpty: false, shared: options && options.shared, + handleMouseEvents: options && options.handleMouseEvents} + pos = clipPos(this, pos) + return markText(this, pos, pos, realOpts, "bookmark") + }, + findMarksAt: function(pos) { + pos = clipPos(this, pos) + var markers = [], spans = getLine(this, pos.line).markedSpans + if (spans) { for (var i = 0; i < spans.length; ++i) { + var span = spans[i] + if ((span.from == null || span.from <= pos.ch) && + (span.to == null || span.to >= pos.ch)) + { markers.push(span.marker.parent || span.marker) } + } } + return markers + }, + findMarks: function(from, to, filter) { + from = clipPos(this, from); to = clipPos(this, to) + var found = [], lineNo = from.line + this.iter(from.line, to.line + 1, function (line) { + var spans = line.markedSpans + if (spans) { for (var i = 0; i < spans.length; i++) { + var span = spans[i] + if (!(span.to != null && lineNo == from.line && from.ch >= span.to || + span.from == null && lineNo != from.line || + span.from != null && lineNo == to.line && span.from >= to.ch) && + (!filter || filter(span.marker))) + { found.push(span.marker.parent || span.marker) } + } } + ++lineNo + }) + return found + }, + getAllMarks: function() { + var markers = [] + this.iter(function (line) { + var sps = line.markedSpans + if (sps) { for (var i = 0; i < sps.length; ++i) + { if (sps[i].from != null) { markers.push(sps[i].marker) } } } + }) + return markers + }, + + posFromIndex: function(off) { + var ch, lineNo = this.first, sepSize = this.lineSeparator().length + this.iter(function (line) { + var sz = line.text.length + sepSize + if (sz > off) { ch = off; return true } + off -= sz + ++lineNo + }) + return clipPos(this, Pos(lineNo, ch)) + }, + indexFromPos: function (coords) { + coords = clipPos(this, coords) + var index = coords.ch + if (coords.line < this.first || coords.ch < 0) { return 0 } + var sepSize = this.lineSeparator().length + this.iter(this.first, coords.line, function (line) { // iter aborts when callback returns a truthy value + index += line.text.length + sepSize + }) + return index + }, + + copy: function(copyHistory) { + var doc = new Doc(getLines(this, this.first, this.first + this.size), + this.modeOption, this.first, this.lineSep) + doc.scrollTop = this.scrollTop; doc.scrollLeft = this.scrollLeft + doc.sel = this.sel + doc.extend = false + if (copyHistory) { + doc.history.undoDepth = this.history.undoDepth + doc.setHistory(this.getHistory()) + } + return doc + }, + + linkedDoc: function(options) { + if (!options) { options = {} } + var from = this.first, to = this.first + this.size + if (options.from != null && options.from > from) { from = options.from } + if (options.to != null && options.to < to) { to = options.to } + var copy = new Doc(getLines(this, from, to), options.mode || this.modeOption, from, this.lineSep) + if (options.sharedHist) { copy.history = this.history + ; }(this.linked || (this.linked = [])).push({doc: copy, sharedHist: options.sharedHist}) + copy.linked = [{doc: this, isParent: true, sharedHist: options.sharedHist}] + copySharedMarkers(copy, findSharedMarkers(this)) + return copy + }, + unlinkDoc: function(other) { + var this$1 = this; + + if (other instanceof CodeMirror) { other = other.doc } + if (this.linked) { for (var i = 0; i < this.linked.length; ++i) { + var link = this$1.linked[i] + if (link.doc != other) { continue } + this$1.linked.splice(i, 1) + other.unlinkDoc(this$1) + detachSharedMarkers(findSharedMarkers(this$1)) + break + } } + // If the histories were shared, split them again + if (other.history == this.history) { + var splitIds = [other.id] + linkedDocs(other, function (doc) { return splitIds.push(doc.id); }, true) + other.history = new History(null) + other.history.done = copyHistoryArray(this.history.done, splitIds) + other.history.undone = copyHistoryArray(this.history.undone, splitIds) + } + }, + iterLinkedDocs: function(f) {linkedDocs(this, f)}, + + getMode: function() {return this.mode}, + getEditor: function() {return this.cm}, + + splitLines: function(str) { + if (this.lineSep) { return str.split(this.lineSep) } + return splitLinesAuto(str) + }, + lineSeparator: function() { return this.lineSep || "\n" } +}) + +// Public alias. +Doc.prototype.eachLine = Doc.prototype.iter + +// Kludge to work around strange IE behavior where it'll sometimes +// re-fire a series of drag-related events right after the drop (#1551) +var lastDrop = 0 + +function onDrop(e) { + var cm = this + clearDragCursor(cm) + if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) + { return } + e_preventDefault(e) + if (ie) { lastDrop = +new Date } + var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files + if (!pos || cm.isReadOnly()) { return } + // Might be a file drop, in which case we simply extract the text + // and insert it. + if (files && files.length && window.FileReader && window.File) { + var n = files.length, text = Array(n), read = 0 + var loadFile = function (file, i) { + if (cm.options.allowDropFileTypes && + indexOf(cm.options.allowDropFileTypes, file.type) == -1) + { return } + + var reader = new FileReader + reader.onload = operation(cm, function () { + var content = reader.result + if (/[\x00-\x08\x0e-\x1f]{2}/.test(content)) { content = "" } + text[i] = content + if (++read == n) { + pos = clipPos(cm.doc, pos) + var change = {from: pos, to: pos, + text: cm.doc.splitLines(text.join(cm.doc.lineSeparator())), + origin: "paste"} + makeChange(cm.doc, change) + setSelectionReplaceHistory(cm.doc, simpleSelection(pos, changeEnd(change))) + } + }) + reader.readAsText(file) + } + for (var i = 0; i < n; ++i) { loadFile(files[i], i) } + } else { // Normal drop + // Don't do a replace if the drop happened inside of the selected text. + if (cm.state.draggingText && cm.doc.sel.contains(pos) > -1) { + cm.state.draggingText(e) + // Ensure the editor is re-focused + setTimeout(function () { return cm.display.input.focus(); }, 20) + return + } + try { + var text$1 = e.dataTransfer.getData("Text") + if (text$1) { + var selected + if (cm.state.draggingText && !cm.state.draggingText.copy) + { selected = cm.listSelections() } + setSelectionNoUndo(cm.doc, simpleSelection(pos, pos)) + if (selected) { for (var i$1 = 0; i$1 < selected.length; ++i$1) + { replaceRange(cm.doc, "", selected[i$1].anchor, selected[i$1].head, "drag") } } + cm.replaceSelection(text$1, "around", "paste") + cm.display.input.focus() + } + } + catch(e){} + } +} + +function onDragStart(cm, e) { + if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return } + if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) { return } + + e.dataTransfer.setData("Text", cm.getSelection()) + e.dataTransfer.effectAllowed = "copyMove" + + // Use dummy image instead of default browsers image. + // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there. + if (e.dataTransfer.setDragImage && !safari) { + var img = elt("img", null, null, "position: fixed; left: 0; top: 0;") + img.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" + if (presto) { + img.width = img.height = 1 + cm.display.wrapper.appendChild(img) + // Force a relayout, or Opera won't use our image for some obscure reason + img._top = img.offsetTop + } + e.dataTransfer.setDragImage(img, 0, 0) + if (presto) { img.parentNode.removeChild(img) } + } +} + +function onDragOver(cm, e) { + var pos = posFromMouse(cm, e) + if (!pos) { return } + var frag = document.createDocumentFragment() + drawSelectionCursor(cm, pos, frag) + if (!cm.display.dragCursor) { + cm.display.dragCursor = elt("div", null, "CodeMirror-cursors CodeMirror-dragcursors") + cm.display.lineSpace.insertBefore(cm.display.dragCursor, cm.display.cursorDiv) + } + removeChildrenAndAdd(cm.display.dragCursor, frag) +} + +function clearDragCursor(cm) { + if (cm.display.dragCursor) { + cm.display.lineSpace.removeChild(cm.display.dragCursor) + cm.display.dragCursor = null + } +} + +// These must be handled carefully, because naively registering a +// handler for each editor will cause the editors to never be +// garbage collected. + +function forEachCodeMirror(f) { + if (!document.body.getElementsByClassName) { return } + var byClass = document.body.getElementsByClassName("CodeMirror") + for (var i = 0; i < byClass.length; i++) { + var cm = byClass[i].CodeMirror + if (cm) { f(cm) } + } +} + +var globalsRegistered = false +function ensureGlobalHandlers() { + if (globalsRegistered) { return } + registerGlobalHandlers() + globalsRegistered = true +} +function registerGlobalHandlers() { + // When the window resizes, we need to refresh active editors. + var resizeTimer + on(window, "resize", function () { + if (resizeTimer == null) { resizeTimer = setTimeout(function () { + resizeTimer = null + forEachCodeMirror(onResize) + }, 100) } + }) + // When the window loses focus, we want to show the editor as blurred + on(window, "blur", function () { return forEachCodeMirror(onBlur); }) +} +// Called when the window resizes +function onResize(cm) { + var d = cm.display + if (d.lastWrapHeight == d.wrapper.clientHeight && d.lastWrapWidth == d.wrapper.clientWidth) + { return } + // Might be a text scaling operation, clear size caches. + d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null + d.scrollbarsClipped = false + cm.setSize() +} + +var keyNames = { + 3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt", + 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End", + 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert", + 46: "Delete", 59: ";", 61: "=", 91: "Mod", 92: "Mod", 93: "Mod", + 106: "*", 107: "=", 109: "-", 110: ".", 111: "/", 127: "Delete", + 173: "-", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", + 221: "]", 222: "'", 63232: "Up", 63233: "Down", 63234: "Left", 63235: "Right", 63272: "Delete", + 63273: "Home", 63275: "End", 63276: "PageUp", 63277: "PageDown", 63302: "Insert" +} + +// Number keys +for (var i = 0; i < 10; i++) { keyNames[i + 48] = keyNames[i + 96] = String(i) } +// Alphabetic keys +for (var i$1 = 65; i$1 <= 90; i$1++) { keyNames[i$1] = String.fromCharCode(i$1) } +// Function keys +for (var i$2 = 1; i$2 <= 12; i$2++) { keyNames[i$2 + 111] = keyNames[i$2 + 63235] = "F" + i$2 } + +var keyMap = {} + +keyMap.basic = { + "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown", + "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown", + "Delete": "delCharAfter", "Backspace": "delCharBefore", "Shift-Backspace": "delCharBefore", + "Tab": "defaultTab", "Shift-Tab": "indentAuto", + "Enter": "newlineAndIndent", "Insert": "toggleOverwrite", + "Esc": "singleSelection" +} +// Note that the save and find-related commands aren't defined by +// default. User code or addons can define them. Unknown commands +// are simply ignored. +keyMap.pcDefault = { + "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo", + "Ctrl-Home": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Up": "goLineUp", "Ctrl-Down": "goLineDown", + "Ctrl-Left": "goGroupLeft", "Ctrl-Right": "goGroupRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd", + "Ctrl-Backspace": "delGroupBefore", "Ctrl-Delete": "delGroupAfter", "Ctrl-S": "save", "Ctrl-F": "find", + "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll", + "Ctrl-[": "indentLess", "Ctrl-]": "indentMore", + "Ctrl-U": "undoSelection", "Shift-Ctrl-U": "redoSelection", "Alt-U": "redoSelection", + fallthrough: "basic" +} +// Very basic readline/emacs-style bindings, which are standard on Mac. +keyMap.emacsy = { + "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown", + "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd", + "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharAfter", "Ctrl-H": "delCharBefore", + "Alt-D": "delWordAfter", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars", + "Ctrl-O": "openLine" +} +keyMap.macDefault = { + "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo", + "Cmd-Home": "goDocStart", "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goGroupLeft", + "Alt-Right": "goGroupRight", "Cmd-Left": "goLineLeft", "Cmd-Right": "goLineRight", "Alt-Backspace": "delGroupBefore", + "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S": "save", "Cmd-F": "find", + "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll", + "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delWrappedLineLeft", "Cmd-Delete": "delWrappedLineRight", + "Cmd-U": "undoSelection", "Shift-Cmd-U": "redoSelection", "Ctrl-Up": "goDocStart", "Ctrl-Down": "goDocEnd", + fallthrough: ["basic", "emacsy"] +} +keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault + +// KEYMAP DISPATCH + +function normalizeKeyName(name) { + var parts = name.split(/-(?!$)/) + name = parts[parts.length - 1] + var alt, ctrl, shift, cmd + for (var i = 0; i < parts.length - 1; i++) { + var mod = parts[i] + if (/^(cmd|meta|m)$/i.test(mod)) { cmd = true } + else if (/^a(lt)?$/i.test(mod)) { alt = true } + else if (/^(c|ctrl|control)$/i.test(mod)) { ctrl = true } + else if (/^s(hift)?$/i.test(mod)) { shift = true } + else { throw new Error("Unrecognized modifier name: " + mod) } + } + if (alt) { name = "Alt-" + name } + if (ctrl) { name = "Ctrl-" + name } + if (cmd) { name = "Cmd-" + name } + if (shift) { name = "Shift-" + name } + return name +} + +// This is a kludge to keep keymaps mostly working as raw objects +// (backwards compatibility) while at the same time support features +// like normalization and multi-stroke key bindings. It compiles a +// new normalized keymap, and then updates the old object to reflect +// this. +function normalizeKeyMap(keymap) { + var copy = {} + for (var keyname in keymap) { if (keymap.hasOwnProperty(keyname)) { + var value = keymap[keyname] + if (/^(name|fallthrough|(de|at)tach)$/.test(keyname)) { continue } + if (value == "...") { delete keymap[keyname]; continue } + + var keys = map(keyname.split(" "), normalizeKeyName) + for (var i = 0; i < keys.length; i++) { + var val = (void 0), name = (void 0) + if (i == keys.length - 1) { + name = keys.join(" ") + val = value + } else { + name = keys.slice(0, i + 1).join(" ") + val = "..." + } + var prev = copy[name] + if (!prev) { copy[name] = val } + else if (prev != val) { throw new Error("Inconsistent bindings for " + name) } + } + delete keymap[keyname] + } } + for (var prop in copy) { keymap[prop] = copy[prop] } + return keymap +} + +function lookupKey(key, map, handle, context) { + map = getKeyMap(map) + var found = map.call ? map.call(key, context) : map[key] + if (found === false) { return "nothing" } + if (found === "...") { return "multi" } + if (found != null && handle(found)) { return "handled" } + + if (map.fallthrough) { + if (Object.prototype.toString.call(map.fallthrough) != "[object Array]") + { return lookupKey(key, map.fallthrough, handle, context) } + for (var i = 0; i < map.fallthrough.length; i++) { + var result = lookupKey(key, map.fallthrough[i], handle, context) + if (result) { return result } + } + } +} + +// Modifier key presses don't count as 'real' key presses for the +// purpose of keymap fallthrough. +function isModifierKey(value) { + var name = typeof value == "string" ? value : keyNames[value.keyCode] + return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod" +} + +// Look up the name of a key as indicated by an event object. +function keyName(event, noShift) { + if (presto && event.keyCode == 34 && event["char"]) { return false } + var base = keyNames[event.keyCode], name = base + if (name == null || event.altGraphKey) { return false } + if (event.altKey && base != "Alt") { name = "Alt-" + name } + if ((flipCtrlCmd ? event.metaKey : event.ctrlKey) && base != "Ctrl") { name = "Ctrl-" + name } + if ((flipCtrlCmd ? event.ctrlKey : event.metaKey) && base != "Cmd") { name = "Cmd-" + name } + if (!noShift && event.shiftKey && base != "Shift") { name = "Shift-" + name } + return name +} + +function getKeyMap(val) { + return typeof val == "string" ? keyMap[val] : val +} + +// Helper for deleting text near the selection(s), used to implement +// backspace, delete, and similar functionality. +function deleteNearSelection(cm, compute) { + var ranges = cm.doc.sel.ranges, kill = [] + // Build up a set of ranges to kill first, merging overlapping + // ranges. + for (var i = 0; i < ranges.length; i++) { + var toKill = compute(ranges[i]) + while (kill.length && cmp(toKill.from, lst(kill).to) <= 0) { + var replaced = kill.pop() + if (cmp(replaced.from, toKill.from) < 0) { + toKill.from = replaced.from + break + } + } + kill.push(toKill) + } + // Next, remove those actual ranges. + runInOp(cm, function () { + for (var i = kill.length - 1; i >= 0; i--) + { replaceRange(cm.doc, "", kill[i].from, kill[i].to, "+delete") } + ensureCursorVisible(cm) + }) +} + +// Commands are parameter-less actions that can be performed on an +// editor, mostly used for keybindings. +var commands = { + selectAll: selectAll, + singleSelection: function (cm) { return cm.setSelection(cm.getCursor("anchor"), cm.getCursor("head"), sel_dontScroll); }, + killLine: function (cm) { return deleteNearSelection(cm, function (range) { + if (range.empty()) { + var len = getLine(cm.doc, range.head.line).text.length + if (range.head.ch == len && range.head.line < cm.lastLine()) + { return {from: range.head, to: Pos(range.head.line + 1, 0)} } + else + { return {from: range.head, to: Pos(range.head.line, len)} } + } else { + return {from: range.from(), to: range.to()} + } + }); }, + deleteLine: function (cm) { return deleteNearSelection(cm, function (range) { return ({ + from: Pos(range.from().line, 0), + to: clipPos(cm.doc, Pos(range.to().line + 1, 0)) + }); }); }, + delLineLeft: function (cm) { return deleteNearSelection(cm, function (range) { return ({ + from: Pos(range.from().line, 0), to: range.from() + }); }); }, + delWrappedLineLeft: function (cm) { return deleteNearSelection(cm, function (range) { + var top = cm.charCoords(range.head, "div").top + 5 + var leftPos = cm.coordsChar({left: 0, top: top}, "div") + return {from: leftPos, to: range.from()} + }); }, + delWrappedLineRight: function (cm) { return deleteNearSelection(cm, function (range) { + var top = cm.charCoords(range.head, "div").top + 5 + var rightPos = cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div") + return {from: range.from(), to: rightPos } + }); }, + undo: function (cm) { return cm.undo(); }, + redo: function (cm) { return cm.redo(); }, + undoSelection: function (cm) { return cm.undoSelection(); }, + redoSelection: function (cm) { return cm.redoSelection(); }, + goDocStart: function (cm) { return cm.extendSelection(Pos(cm.firstLine(), 0)); }, + goDocEnd: function (cm) { return cm.extendSelection(Pos(cm.lastLine())); }, + goLineStart: function (cm) { return cm.extendSelectionsBy(function (range) { return lineStart(cm, range.head.line); }, + {origin: "+move", bias: 1} + ); }, + goLineStartSmart: function (cm) { return cm.extendSelectionsBy(function (range) { return lineStartSmart(cm, range.head); }, + {origin: "+move", bias: 1} + ); }, + goLineEnd: function (cm) { return cm.extendSelectionsBy(function (range) { return lineEnd(cm, range.head.line); }, + {origin: "+move", bias: -1} + ); }, + goLineRight: function (cm) { return cm.extendSelectionsBy(function (range) { + var top = cm.charCoords(range.head, "div").top + 5 + return cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div") + }, sel_move); }, + goLineLeft: function (cm) { return cm.extendSelectionsBy(function (range) { + var top = cm.charCoords(range.head, "div").top + 5 + return cm.coordsChar({left: 0, top: top}, "div") + }, sel_move); }, + goLineLeftSmart: function (cm) { return cm.extendSelectionsBy(function (range) { + var top = cm.charCoords(range.head, "div").top + 5 + var pos = cm.coordsChar({left: 0, top: top}, "div") + if (pos.ch < cm.getLine(pos.line).search(/\S/)) { return lineStartSmart(cm, range.head) } + return pos + }, sel_move); }, + goLineUp: function (cm) { return cm.moveV(-1, "line"); }, + goLineDown: function (cm) { return cm.moveV(1, "line"); }, + goPageUp: function (cm) { return cm.moveV(-1, "page"); }, + goPageDown: function (cm) { return cm.moveV(1, "page"); }, + goCharLeft: function (cm) { return cm.moveH(-1, "char"); }, + goCharRight: function (cm) { return cm.moveH(1, "char"); }, + goColumnLeft: function (cm) { return cm.moveH(-1, "column"); }, + goColumnRight: function (cm) { return cm.moveH(1, "column"); }, + goWordLeft: function (cm) { return cm.moveH(-1, "word"); }, + goGroupRight: function (cm) { return cm.moveH(1, "group"); }, + goGroupLeft: function (cm) { return cm.moveH(-1, "group"); }, + goWordRight: function (cm) { return cm.moveH(1, "word"); }, + delCharBefore: function (cm) { return cm.deleteH(-1, "char"); }, + delCharAfter: function (cm) { return cm.deleteH(1, "char"); }, + delWordBefore: function (cm) { return cm.deleteH(-1, "word"); }, + delWordAfter: function (cm) { return cm.deleteH(1, "word"); }, + delGroupBefore: function (cm) { return cm.deleteH(-1, "group"); }, + delGroupAfter: function (cm) { return cm.deleteH(1, "group"); }, + indentAuto: function (cm) { return cm.indentSelection("smart"); }, + indentMore: function (cm) { return cm.indentSelection("add"); }, + indentLess: function (cm) { return cm.indentSelection("subtract"); }, + insertTab: function (cm) { return cm.replaceSelection("\t"); }, + insertSoftTab: function (cm) { + var spaces = [], ranges = cm.listSelections(), tabSize = cm.options.tabSize + for (var i = 0; i < ranges.length; i++) { + var pos = ranges[i].from() + var col = countColumn(cm.getLine(pos.line), pos.ch, tabSize) + spaces.push(spaceStr(tabSize - col % tabSize)) + } + cm.replaceSelections(spaces) + }, + defaultTab: function (cm) { + if (cm.somethingSelected()) { cm.indentSelection("add") } + else { cm.execCommand("insertTab") } + }, + // Swap the two chars left and right of each selection's head. + // Move cursor behind the two swapped characters afterwards. + // + // Doesn't consider line feeds a character. + // Doesn't scan more than one line above to find a character. + // Doesn't do anything on an empty line. + // Doesn't do anything with non-empty selections. + transposeChars: function (cm) { return runInOp(cm, function () { + var ranges = cm.listSelections(), newSel = [] + for (var i = 0; i < ranges.length; i++) { + if (!ranges[i].empty()) { continue } + var cur = ranges[i].head, line = getLine(cm.doc, cur.line).text + if (line) { + if (cur.ch == line.length) { cur = new Pos(cur.line, cur.ch - 1) } + if (cur.ch > 0) { + cur = new Pos(cur.line, cur.ch + 1) + cm.replaceRange(line.charAt(cur.ch - 1) + line.charAt(cur.ch - 2), + Pos(cur.line, cur.ch - 2), cur, "+transpose") + } else if (cur.line > cm.doc.first) { + var prev = getLine(cm.doc, cur.line - 1).text + if (prev) { + cur = new Pos(cur.line, 1) + cm.replaceRange(line.charAt(0) + cm.doc.lineSeparator() + + prev.charAt(prev.length - 1), + Pos(cur.line - 1, prev.length - 1), cur, "+transpose") + } + } + } + newSel.push(new Range(cur, cur)) + } + cm.setSelections(newSel) + }); }, + newlineAndIndent: function (cm) { return runInOp(cm, function () { + var sels = cm.listSelections() + for (var i = sels.length - 1; i >= 0; i--) + { cm.replaceRange(cm.doc.lineSeparator(), sels[i].anchor, sels[i].head, "+input") } + sels = cm.listSelections() + for (var i$1 = 0; i$1 < sels.length; i$1++) + { cm.indentLine(sels[i$1].from().line, null, true) } + ensureCursorVisible(cm) + }); }, + openLine: function (cm) { return cm.replaceSelection("\n", "start"); }, + toggleOverwrite: function (cm) { return cm.toggleOverwrite(); } +} + + +function lineStart(cm, lineN) { + var line = getLine(cm.doc, lineN) + var visual = visualLine(line) + if (visual != line) { lineN = lineNo(visual) } + var order = getOrder(visual) + var ch = !order ? 0 : order[0].level % 2 ? lineRight(visual) : lineLeft(visual) + return Pos(lineN, ch) +} +function lineEnd(cm, lineN) { + var merged, line = getLine(cm.doc, lineN) + while (merged = collapsedSpanAtEnd(line)) { + line = merged.find(1, true).line + lineN = null + } + var order = getOrder(line) + var ch = !order ? line.text.length : order[0].level % 2 ? lineLeft(line) : lineRight(line) + return Pos(lineN == null ? lineNo(line) : lineN, ch) +} +function lineStartSmart(cm, pos) { + var start = lineStart(cm, pos.line) + var line = getLine(cm.doc, start.line) + var order = getOrder(line) + if (!order || order[0].level == 0) { + var firstNonWS = Math.max(0, line.text.search(/\S/)) + var inWS = pos.line == start.line && pos.ch <= firstNonWS && pos.ch + return Pos(start.line, inWS ? 0 : firstNonWS) + } + return start +} + +// Run a handler that was bound to a key. +function doHandleBinding(cm, bound, dropShift) { + if (typeof bound == "string") { + bound = commands[bound] + if (!bound) { return false } + } + // Ensure previous input has been read, so that the handler sees a + // consistent view of the document + cm.display.input.ensurePolled() + var prevShift = cm.display.shift, done = false + try { + if (cm.isReadOnly()) { cm.state.suppressEdits = true } + if (dropShift) { cm.display.shift = false } + done = bound(cm) != Pass + } finally { + cm.display.shift = prevShift + cm.state.suppressEdits = false + } + return done +} + +function lookupKeyForEditor(cm, name, handle) { + for (var i = 0; i < cm.state.keyMaps.length; i++) { + var result = lookupKey(name, cm.state.keyMaps[i], handle, cm) + if (result) { return result } + } + return (cm.options.extraKeys && lookupKey(name, cm.options.extraKeys, handle, cm)) + || lookupKey(name, cm.options.keyMap, handle, cm) +} + +var stopSeq = new Delayed +function dispatchKey(cm, name, e, handle) { + var seq = cm.state.keySeq + if (seq) { + if (isModifierKey(name)) { return "handled" } + stopSeq.set(50, function () { + if (cm.state.keySeq == seq) { + cm.state.keySeq = null + cm.display.input.reset() + } + }) + name = seq + " " + name + } + var result = lookupKeyForEditor(cm, name, handle) + + if (result == "multi") + { cm.state.keySeq = name } + if (result == "handled") + { signalLater(cm, "keyHandled", cm, name, e) } + + if (result == "handled" || result == "multi") { + e_preventDefault(e) + restartBlink(cm) + } + + if (seq && !result && /\'$/.test(name)) { + e_preventDefault(e) + return true + } + return !!result +} + +// Handle a key from the keydown event. +function handleKeyBinding(cm, e) { + var name = keyName(e, true) + if (!name) { return false } + + if (e.shiftKey && !cm.state.keySeq) { + // First try to resolve full name (including 'Shift-'). Failing + // that, see if there is a cursor-motion command (starting with + // 'go') bound to the keyname without 'Shift-'. + return dispatchKey(cm, "Shift-" + name, e, function (b) { return doHandleBinding(cm, b, true); }) + || dispatchKey(cm, name, e, function (b) { + if (typeof b == "string" ? /^go[A-Z]/.test(b) : b.motion) + { return doHandleBinding(cm, b) } + }) + } else { + return dispatchKey(cm, name, e, function (b) { return doHandleBinding(cm, b); }) + } +} + +// Handle a key from the keypress event +function handleCharBinding(cm, e, ch) { + return dispatchKey(cm, "'" + ch + "'", e, function (b) { return doHandleBinding(cm, b, true); }) +} + +var lastStoppedKey = null +function onKeyDown(e) { + var cm = this + cm.curOp.focus = activeElt() + if (signalDOMEvent(cm, e)) { return } + // IE does strange things with escape. + if (ie && ie_version < 11 && e.keyCode == 27) { e.returnValue = false } + var code = e.keyCode + cm.display.shift = code == 16 || e.shiftKey + var handled = handleKeyBinding(cm, e) + if (presto) { + lastStoppedKey = handled ? code : null + // Opera has no cut event... we try to at least catch the key combo + if (!handled && code == 88 && !hasCopyEvent && (mac ? e.metaKey : e.ctrlKey)) + { cm.replaceSelection("", null, "cut") } + } + + // Turn mouse into crosshair when Alt is held on Mac. + if (code == 18 && !/\bCodeMirror-crosshair\b/.test(cm.display.lineDiv.className)) + { showCrossHair(cm) } +} + +function showCrossHair(cm) { + var lineDiv = cm.display.lineDiv + addClass(lineDiv, "CodeMirror-crosshair") + + function up(e) { + if (e.keyCode == 18 || !e.altKey) { + rmClass(lineDiv, "CodeMirror-crosshair") + off(document, "keyup", up) + off(document, "mouseover", up) + } + } + on(document, "keyup", up) + on(document, "mouseover", up) +} + +function onKeyUp(e) { + if (e.keyCode == 16) { this.doc.sel.shift = false } + signalDOMEvent(this, e) +} + +function onKeyPress(e) { + var cm = this + if (eventInWidget(cm.display, e) || signalDOMEvent(cm, e) || e.ctrlKey && !e.altKey || mac && e.metaKey) { return } + var keyCode = e.keyCode, charCode = e.charCode + if (presto && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return} + if ((presto && (!e.which || e.which < 10)) && handleKeyBinding(cm, e)) { return } + var ch = String.fromCharCode(charCode == null ? keyCode : charCode) + // Some browsers fire keypress events for backspace + if (ch == "\x08") { return } + if (handleCharBinding(cm, e, ch)) { return } + cm.display.input.onKeyPress(e) +} + +// A mouse down can be a single click, double click, triple click, +// start of selection drag, start of text drag, new cursor +// (ctrl-click), rectangle drag (alt-drag), or xwin +// middle-click-paste. Or it might be a click on something we should +// not interfere with, such as a scrollbar or widget. +function onMouseDown(e) { + var cm = this, display = cm.display + if (signalDOMEvent(cm, e) || display.activeTouch && display.input.supportsTouch()) { return } + display.input.ensurePolled() + display.shift = e.shiftKey + + if (eventInWidget(display, e)) { + if (!webkit) { + // Briefly turn off draggability, to allow widgets to do + // normal dragging things. + display.scroller.draggable = false + setTimeout(function () { return display.scroller.draggable = true; }, 100) + } + return + } + if (clickInGutter(cm, e)) { return } + var start = posFromMouse(cm, e) + window.focus() + + switch (e_button(e)) { + case 1: + // #3261: make sure, that we're not starting a second selection + if (cm.state.selectingText) + { cm.state.selectingText(e) } + else if (start) + { leftButtonDown(cm, e, start) } + else if (e_target(e) == display.scroller) + { e_preventDefault(e) } + break + case 2: + if (webkit) { cm.state.lastMiddleDown = +new Date } + if (start) { extendSelection(cm.doc, start) } + setTimeout(function () { return display.input.focus(); }, 20) + e_preventDefault(e) + break + case 3: + if (captureRightClick) { onContextMenu(cm, e) } + else { delayBlurEvent(cm) } + break + } +} + +var lastClick; +var lastDoubleClick; +function leftButtonDown(cm, e, start) { + if (ie) { setTimeout(bind(ensureFocus, cm), 0) } + else { cm.curOp.focus = activeElt() } + + var now = +new Date, type + if (lastDoubleClick && lastDoubleClick.time > now - 400 && cmp(lastDoubleClick.pos, start) == 0) { + type = "triple" + } else if (lastClick && lastClick.time > now - 400 && cmp(lastClick.pos, start) == 0) { + type = "double" + lastDoubleClick = {time: now, pos: start} + } else { + type = "single" + lastClick = {time: now, pos: start} + } + + var sel = cm.doc.sel, modifier = mac ? e.metaKey : e.ctrlKey, contained + if (cm.options.dragDrop && dragAndDrop && !cm.isReadOnly() && + type == "single" && (contained = sel.contains(start)) > -1 && + (cmp((contained = sel.ranges[contained]).from(), start) < 0 || start.xRel > 0) && + (cmp(contained.to(), start) > 0 || start.xRel < 0)) + { leftButtonStartDrag(cm, e, start, modifier) } + else + { leftButtonSelect(cm, e, start, type, modifier) } +} + +// Start a text drag. When it ends, see if any dragging actually +// happen, and treat as a click if it didn't. +function leftButtonStartDrag(cm, e, start, modifier) { + var display = cm.display, startTime = +new Date + var dragEnd = operation(cm, function (e2) { + if (webkit) { display.scroller.draggable = false } + cm.state.draggingText = false + off(document, "mouseup", dragEnd) + off(display.scroller, "drop", dragEnd) + if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) { + e_preventDefault(e2) + if (!modifier && +new Date - 200 < startTime) + { extendSelection(cm.doc, start) } + // Work around unexplainable focus problem in IE9 (#2127) and Chrome (#3081) + if (webkit || ie && ie_version == 9) + { setTimeout(function () {document.body.focus(); display.input.focus()}, 20) } + else + { display.input.focus() } + } + }) + // Let the drag handler handle this. + if (webkit) { display.scroller.draggable = true } + cm.state.draggingText = dragEnd + dragEnd.copy = mac ? e.altKey : e.ctrlKey + // IE's approach to draggable + if (display.scroller.dragDrop) { display.scroller.dragDrop() } + on(document, "mouseup", dragEnd) + on(display.scroller, "drop", dragEnd) +} + +// Normal selection, as opposed to text dragging. +function leftButtonSelect(cm, e, start, type, addNew) { + var display = cm.display, doc = cm.doc + e_preventDefault(e) + + var ourRange, ourIndex, startSel = doc.sel, ranges = startSel.ranges + if (addNew && !e.shiftKey) { + ourIndex = doc.sel.contains(start) + if (ourIndex > -1) + { ourRange = ranges[ourIndex] } + else + { ourRange = new Range(start, start) } + } else { + ourRange = doc.sel.primary() + ourIndex = doc.sel.primIndex + } + + if (chromeOS ? e.shiftKey && e.metaKey : e.altKey) { + type = "rect" + if (!addNew) { ourRange = new Range(start, start) } + start = posFromMouse(cm, e, true, true) + ourIndex = -1 + } else if (type == "double") { + var word = cm.findWordAt(start) + if (cm.display.shift || doc.extend) + { ourRange = extendRange(doc, ourRange, word.anchor, word.head) } + else + { ourRange = word } + } else if (type == "triple") { + var line = new Range(Pos(start.line, 0), clipPos(doc, Pos(start.line + 1, 0))) + if (cm.display.shift || doc.extend) + { ourRange = extendRange(doc, ourRange, line.anchor, line.head) } + else + { ourRange = line } + } else { + ourRange = extendRange(doc, ourRange, start) + } + + if (!addNew) { + ourIndex = 0 + setSelection(doc, new Selection([ourRange], 0), sel_mouse) + startSel = doc.sel + } else if (ourIndex == -1) { + ourIndex = ranges.length + setSelection(doc, normalizeSelection(ranges.concat([ourRange]), ourIndex), + {scroll: false, origin: "*mouse"}) + } else if (ranges.length > 1 && ranges[ourIndex].empty() && type == "single" && !e.shiftKey) { + setSelection(doc, normalizeSelection(ranges.slice(0, ourIndex).concat(ranges.slice(ourIndex + 1)), 0), + {scroll: false, origin: "*mouse"}) + startSel = doc.sel + } else { + replaceOneSelection(doc, ourIndex, ourRange, sel_mouse) + } + + var lastPos = start + function extendTo(pos) { + if (cmp(lastPos, pos) == 0) { return } + lastPos = pos + + if (type == "rect") { + var ranges = [], tabSize = cm.options.tabSize + var startCol = countColumn(getLine(doc, start.line).text, start.ch, tabSize) + var posCol = countColumn(getLine(doc, pos.line).text, pos.ch, tabSize) + var left = Math.min(startCol, posCol), right = Math.max(startCol, posCol) + for (var line = Math.min(start.line, pos.line), end = Math.min(cm.lastLine(), Math.max(start.line, pos.line)); + line <= end; line++) { + var text = getLine(doc, line).text, leftPos = findColumn(text, left, tabSize) + if (left == right) + { ranges.push(new Range(Pos(line, leftPos), Pos(line, leftPos))) } + else if (text.length > leftPos) + { ranges.push(new Range(Pos(line, leftPos), Pos(line, findColumn(text, right, tabSize)))) } + } + if (!ranges.length) { ranges.push(new Range(start, start)) } + setSelection(doc, normalizeSelection(startSel.ranges.slice(0, ourIndex).concat(ranges), ourIndex), + {origin: "*mouse", scroll: false}) + cm.scrollIntoView(pos) + } else { + var oldRange = ourRange + var anchor = oldRange.anchor, head = pos + if (type != "single") { + var range + if (type == "double") + { range = cm.findWordAt(pos) } + else + { range = new Range(Pos(pos.line, 0), clipPos(doc, Pos(pos.line + 1, 0))) } + if (cmp(range.anchor, anchor) > 0) { + head = range.head + anchor = minPos(oldRange.from(), range.anchor) + } else { + head = range.anchor + anchor = maxPos(oldRange.to(), range.head) + } + } + var ranges$1 = startSel.ranges.slice(0) + ranges$1[ourIndex] = new Range(clipPos(doc, anchor), head) + setSelection(doc, normalizeSelection(ranges$1, ourIndex), sel_mouse) + } + } + + var editorSize = display.wrapper.getBoundingClientRect() + // Used to ensure timeout re-tries don't fire when another extend + // happened in the meantime (clearTimeout isn't reliable -- at + // least on Chrome, the timeouts still happen even when cleared, + // if the clear happens after their scheduled firing time). + var counter = 0 + + function extend(e) { + var curCount = ++counter + var cur = posFromMouse(cm, e, true, type == "rect") + if (!cur) { return } + if (cmp(cur, lastPos) != 0) { + cm.curOp.focus = activeElt() + extendTo(cur) + var visible = visibleLines(display, doc) + if (cur.line >= visible.to || cur.line < visible.from) + { setTimeout(operation(cm, function () {if (counter == curCount) { extend(e) }}), 150) } + } else { + var outside = e.clientY < editorSize.top ? -20 : e.clientY > editorSize.bottom ? 20 : 0 + if (outside) { setTimeout(operation(cm, function () { + if (counter != curCount) { return } + display.scroller.scrollTop += outside + extend(e) + }), 50) } + } + } + + function done(e) { + cm.state.selectingText = false + counter = Infinity + e_preventDefault(e) + display.input.focus() + off(document, "mousemove", move) + off(document, "mouseup", up) + doc.history.lastSelOrigin = null + } + + var move = operation(cm, function (e) { + if (!e_button(e)) { done(e) } + else { extend(e) } + }) + var up = operation(cm, done) + cm.state.selectingText = up + on(document, "mousemove", move) + on(document, "mouseup", up) +} + + +// Determines whether an event happened in the gutter, and fires the +// handlers for the corresponding event. +function gutterEvent(cm, e, type, prevent) { + var mX, mY + try { mX = e.clientX; mY = e.clientY } + catch(e) { return false } + if (mX >= Math.floor(cm.display.gutters.getBoundingClientRect().right)) { return false } + if (prevent) { e_preventDefault(e) } + + var display = cm.display + var lineBox = display.lineDiv.getBoundingClientRect() + + if (mY > lineBox.bottom || !hasHandler(cm, type)) { return e_defaultPrevented(e) } + mY -= lineBox.top - display.viewOffset + + for (var i = 0; i < cm.options.gutters.length; ++i) { + var g = display.gutters.childNodes[i] + if (g && g.getBoundingClientRect().right >= mX) { + var line = lineAtHeight(cm.doc, mY) + var gutter = cm.options.gutters[i] + signal(cm, type, cm, line, gutter, e) + return e_defaultPrevented(e) + } + } +} + +function clickInGutter(cm, e) { + return gutterEvent(cm, e, "gutterClick", true) +} + +// CONTEXT MENU HANDLING + +// To make the context menu work, we need to briefly unhide the +// textarea (making it as unobtrusive as possible) to let the +// right-click take effect on it. +function onContextMenu(cm, e) { + if (eventInWidget(cm.display, e) || contextMenuInGutter(cm, e)) { return } + if (signalDOMEvent(cm, e, "contextmenu")) { return } + cm.display.input.onContextMenu(e) +} + +function contextMenuInGutter(cm, e) { + if (!hasHandler(cm, "gutterContextMenu")) { return false } + return gutterEvent(cm, e, "gutterContextMenu", false) +} + +function themeChanged(cm) { + cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-s-\S+/g, "") + + cm.options.theme.replace(/(^|\s)\s*/g, " cm-s-") + clearCaches(cm) +} + +var Init = {toString: function(){return "CodeMirror.Init"}} + +var defaults = {} +var optionHandlers = {} + +function defineOptions(CodeMirror) { + var optionHandlers = CodeMirror.optionHandlers + + function option(name, deflt, handle, notOnInit) { + CodeMirror.defaults[name] = deflt + if (handle) { optionHandlers[name] = + notOnInit ? function (cm, val, old) {if (old != Init) { handle(cm, val, old) }} : handle } + } + + CodeMirror.defineOption = option + + // Passed to option handlers when there is no old value. + CodeMirror.Init = Init + + // These two are, on init, called from the constructor because they + // have to be initialized before the editor can start at all. + option("value", "", function (cm, val) { return cm.setValue(val); }, true) + option("mode", null, function (cm, val) { + cm.doc.modeOption = val + loadMode(cm) + }, true) + + option("indentUnit", 2, loadMode, true) + option("indentWithTabs", false) + option("smartIndent", true) + option("tabSize", 4, function (cm) { + resetModeState(cm) + clearCaches(cm) + regChange(cm) + }, true) + option("lineSeparator", null, function (cm, val) { + cm.doc.lineSep = val + if (!val) { return } + var newBreaks = [], lineNo = cm.doc.first + cm.doc.iter(function (line) { + for (var pos = 0;;) { + var found = line.text.indexOf(val, pos) + if (found == -1) { break } + pos = found + val.length + newBreaks.push(Pos(lineNo, found)) + } + lineNo++ + }) + for (var i = newBreaks.length - 1; i >= 0; i--) + { replaceRange(cm.doc, val, newBreaks[i], Pos(newBreaks[i].line, newBreaks[i].ch + val.length)) } + }) + option("specialChars", /[\u0000-\u001f\u007f\u00ad\u061c\u200b-\u200f\u2028\u2029\ufeff]/g, function (cm, val, old) { + cm.state.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"), "g") + if (old != Init) { cm.refresh() } + }) + option("specialCharPlaceholder", defaultSpecialCharPlaceholder, function (cm) { return cm.refresh(); }, true) + option("electricChars", true) + option("inputStyle", mobile ? "contenteditable" : "textarea", function () { + throw new Error("inputStyle can not (yet) be changed in a running editor") // FIXME + }, true) + option("spellcheck", false, function (cm, val) { return cm.getInputField().spellcheck = val; }, true) + option("rtlMoveVisually", !windows) + option("wholeLineUpdateBefore", true) + + option("theme", "default", function (cm) { + themeChanged(cm) + guttersChanged(cm) + }, true) + option("keyMap", "default", function (cm, val, old) { + var next = getKeyMap(val) + var prev = old != Init && getKeyMap(old) + if (prev && prev.detach) { prev.detach(cm, next) } + if (next.attach) { next.attach(cm, prev || null) } + }) + option("extraKeys", null) + + option("lineWrapping", false, wrappingChanged, true) + option("gutters", [], function (cm) { + setGuttersForLineNumbers(cm.options) + guttersChanged(cm) + }, true) + option("fixedGutter", true, function (cm, val) { + cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px" : "0" + cm.refresh() + }, true) + option("coverGutterNextToScrollbar", false, function (cm) { return updateScrollbars(cm); }, true) + option("scrollbarStyle", "native", function (cm) { + initScrollbars(cm) + updateScrollbars(cm) + cm.display.scrollbars.setScrollTop(cm.doc.scrollTop) + cm.display.scrollbars.setScrollLeft(cm.doc.scrollLeft) + }, true) + option("lineNumbers", false, function (cm) { + setGuttersForLineNumbers(cm.options) + guttersChanged(cm) + }, true) + option("firstLineNumber", 1, guttersChanged, true) + option("lineNumberFormatter", function (integer) { return integer; }, guttersChanged, true) + option("showCursorWhenSelecting", false, updateSelection, true) + + option("resetSelectionOnContextMenu", true) + option("lineWiseCopyCut", true) + + option("readOnly", false, function (cm, val) { + if (val == "nocursor") { + onBlur(cm) + cm.display.input.blur() + cm.display.disabled = true + } else { + cm.display.disabled = false + } + cm.display.input.readOnlyChanged(val) + }) + option("disableInput", false, function (cm, val) {if (!val) { cm.display.input.reset() }}, true) + option("dragDrop", true, dragDropChanged) + option("allowDropFileTypes", null) + + option("cursorBlinkRate", 530) + option("cursorScrollMargin", 0) + option("cursorHeight", 1, updateSelection, true) + option("singleCursorHeightPerLine", true, updateSelection, true) + option("workTime", 100) + option("workDelay", 100) + option("flattenSpans", true, resetModeState, true) + option("addModeClass", false, resetModeState, true) + option("pollInterval", 100) + option("undoDepth", 200, function (cm, val) { return cm.doc.history.undoDepth = val; }) + option("historyEventDelay", 1250) + option("viewportMargin", 10, function (cm) { return cm.refresh(); }, true) + option("maxHighlightLength", 10000, resetModeState, true) + option("moveInputWithCursor", true, function (cm, val) { + if (!val) { cm.display.input.resetPosition() } + }) + + option("tabindex", null, function (cm, val) { return cm.display.input.getField().tabIndex = val || ""; }) + option("autofocus", null) +} + +function guttersChanged(cm) { + updateGutters(cm) + regChange(cm) + alignHorizontally(cm) +} + +function dragDropChanged(cm, value, old) { + var wasOn = old && old != Init + if (!value != !wasOn) { + var funcs = cm.display.dragFunctions + var toggle = value ? on : off + toggle(cm.display.scroller, "dragstart", funcs.start) + toggle(cm.display.scroller, "dragenter", funcs.enter) + toggle(cm.display.scroller, "dragover", funcs.over) + toggle(cm.display.scroller, "dragleave", funcs.leave) + toggle(cm.display.scroller, "drop", funcs.drop) + } +} + +function wrappingChanged(cm) { + if (cm.options.lineWrapping) { + addClass(cm.display.wrapper, "CodeMirror-wrap") + cm.display.sizer.style.minWidth = "" + cm.display.sizerWidth = null + } else { + rmClass(cm.display.wrapper, "CodeMirror-wrap") + findMaxLine(cm) + } + estimateLineHeights(cm) + regChange(cm) + clearCaches(cm) + setTimeout(function () { return updateScrollbars(cm); }, 100) +} + +// A CodeMirror instance represents an editor. This is the object +// that user code is usually dealing with. + +function CodeMirror(place, options) { + var this$1 = this; + + if (!(this instanceof CodeMirror)) { return new CodeMirror(place, options) } + + this.options = options = options ? copyObj(options) : {} + // Determine effective options based on given values and defaults. + copyObj(defaults, options, false) + setGuttersForLineNumbers(options) + + var doc = options.value + if (typeof doc == "string") { doc = new Doc(doc, options.mode, null, options.lineSeparator) } + this.doc = doc + + var input = new CodeMirror.inputStyles[options.inputStyle](this) + var display = this.display = new Display(place, doc, input) + display.wrapper.CodeMirror = this + updateGutters(this) + themeChanged(this) + if (options.lineWrapping) + { this.display.wrapper.className += " CodeMirror-wrap" } + initScrollbars(this) + + this.state = { + keyMaps: [], // stores maps added by addKeyMap + overlays: [], // highlighting overlays, as added by addOverlay + modeGen: 0, // bumped when mode/overlay changes, used to invalidate highlighting info + overwrite: false, + delayingBlurEvent: false, + focused: false, + suppressEdits: false, // used to disable editing during key handlers when in readOnly mode + pasteIncoming: false, cutIncoming: false, // help recognize paste/cut edits in input.poll + selectingText: false, + draggingText: false, + highlight: new Delayed(), // stores highlight worker timeout + keySeq: null, // Unfinished key sequence + specialChars: null + } + + if (options.autofocus && !mobile) { display.input.focus() } + + // Override magic textarea content restore that IE sometimes does + // on our hidden textarea on reload + if (ie && ie_version < 11) { setTimeout(function () { return this$1.display.input.reset(true); }, 20) } + + registerEventHandlers(this) + ensureGlobalHandlers() + + startOperation(this) + this.curOp.forceUpdate = true + attachDoc(this, doc) + + if ((options.autofocus && !mobile) || this.hasFocus()) + { setTimeout(bind(onFocus, this), 20) } + else + { onBlur(this) } + + for (var opt in optionHandlers) { if (optionHandlers.hasOwnProperty(opt)) + { optionHandlers[opt](this$1, options[opt], Init) } } + maybeUpdateLineNumberWidth(this) + if (options.finishInit) { options.finishInit(this) } + for (var i = 0; i < initHooks.length; ++i) { initHooks[i](this$1) } + endOperation(this) + // Suppress optimizelegibility in Webkit, since it breaks text + // measuring on line wrapping boundaries. + if (webkit && options.lineWrapping && + getComputedStyle(display.lineDiv).textRendering == "optimizelegibility") + { display.lineDiv.style.textRendering = "auto" } +} + +// The default configuration options. +CodeMirror.defaults = defaults +// Functions to run when options are changed. +CodeMirror.optionHandlers = optionHandlers + +// Attach the necessary event handlers when initializing the editor +function registerEventHandlers(cm) { + var d = cm.display + on(d.scroller, "mousedown", operation(cm, onMouseDown)) + // Older IE's will not fire a second mousedown for a double click + if (ie && ie_version < 11) + { on(d.scroller, "dblclick", operation(cm, function (e) { + if (signalDOMEvent(cm, e)) { return } + var pos = posFromMouse(cm, e) + if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) { return } + e_preventDefault(e) + var word = cm.findWordAt(pos) + extendSelection(cm.doc, word.anchor, word.head) + })) } + else + { on(d.scroller, "dblclick", function (e) { return signalDOMEvent(cm, e) || e_preventDefault(e); }) } + // Some browsers fire contextmenu *after* opening the menu, at + // which point we can't mess with it anymore. Context menu is + // handled in onMouseDown for these browsers. + if (!captureRightClick) { on(d.scroller, "contextmenu", function (e) { return onContextMenu(cm, e); }) } + + // Used to suppress mouse event handling when a touch happens + var touchFinished, prevTouch = {end: 0} + function finishTouch() { + if (d.activeTouch) { + touchFinished = setTimeout(function () { return d.activeTouch = null; }, 1000) + prevTouch = d.activeTouch + prevTouch.end = +new Date + } + } + function isMouseLikeTouchEvent(e) { + if (e.touches.length != 1) { return false } + var touch = e.touches[0] + return touch.radiusX <= 1 && touch.radiusY <= 1 + } + function farAway(touch, other) { + if (other.left == null) { return true } + var dx = other.left - touch.left, dy = other.top - touch.top + return dx * dx + dy * dy > 20 * 20 + } + on(d.scroller, "touchstart", function (e) { + if (!signalDOMEvent(cm, e) && !isMouseLikeTouchEvent(e)) { + d.input.ensurePolled() + clearTimeout(touchFinished) + var now = +new Date + d.activeTouch = {start: now, moved: false, + prev: now - prevTouch.end <= 300 ? prevTouch : null} + if (e.touches.length == 1) { + d.activeTouch.left = e.touches[0].pageX + d.activeTouch.top = e.touches[0].pageY + } + } + }) + on(d.scroller, "touchmove", function () { + if (d.activeTouch) { d.activeTouch.moved = true } + }) + on(d.scroller, "touchend", function (e) { + var touch = d.activeTouch + if (touch && !eventInWidget(d, e) && touch.left != null && + !touch.moved && new Date - touch.start < 300) { + var pos = cm.coordsChar(d.activeTouch, "page"), range + if (!touch.prev || farAway(touch, touch.prev)) // Single tap + { range = new Range(pos, pos) } + else if (!touch.prev.prev || farAway(touch, touch.prev.prev)) // Double tap + { range = cm.findWordAt(pos) } + else // Triple tap + { range = new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1, 0))) } + cm.setSelection(range.anchor, range.head) + cm.focus() + e_preventDefault(e) + } + finishTouch() + }) + on(d.scroller, "touchcancel", finishTouch) + + // Sync scrolling between fake scrollbars and real scrollable + // area, ensure viewport is updated when scrolling. + on(d.scroller, "scroll", function () { + if (d.scroller.clientHeight) { + setScrollTop(cm, d.scroller.scrollTop) + setScrollLeft(cm, d.scroller.scrollLeft, true) + signal(cm, "scroll", cm) + } + }) + + // Listen to wheel events in order to try and update the viewport on time. + on(d.scroller, "mousewheel", function (e) { return onScrollWheel(cm, e); }) + on(d.scroller, "DOMMouseScroll", function (e) { return onScrollWheel(cm, e); }) + + // Prevent wrapper from ever scrolling + on(d.wrapper, "scroll", function () { return d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; }) + + d.dragFunctions = { + enter: function (e) {if (!signalDOMEvent(cm, e)) { e_stop(e) }}, + over: function (e) {if (!signalDOMEvent(cm, e)) { onDragOver(cm, e); e_stop(e) }}, + start: function (e) { return onDragStart(cm, e); }, + drop: operation(cm, onDrop), + leave: function (e) {if (!signalDOMEvent(cm, e)) { clearDragCursor(cm) }} + } + + var inp = d.input.getField() + on(inp, "keyup", function (e) { return onKeyUp.call(cm, e); }) + on(inp, "keydown", operation(cm, onKeyDown)) + on(inp, "keypress", operation(cm, onKeyPress)) + on(inp, "focus", function (e) { return onFocus(cm, e); }) + on(inp, "blur", function (e) { return onBlur(cm, e); }) +} + +var initHooks = [] +CodeMirror.defineInitHook = function (f) { return initHooks.push(f); } + +// Indent the given line. The how parameter can be "smart", +// "add"/null, "subtract", or "prev". When aggressive is false +// (typically set to true for forced single-line indents), empty +// lines are not indented, and places where the mode returns Pass +// are left alone. +function indentLine(cm, n, how, aggressive) { + var doc = cm.doc, state + if (how == null) { how = "add" } + if (how == "smart") { + // Fall back to "prev" when the mode doesn't have an indentation + // method. + if (!doc.mode.indent) { how = "prev" } + else { state = getStateBefore(cm, n) } + } + + var tabSize = cm.options.tabSize + var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize) + if (line.stateAfter) { line.stateAfter = null } + var curSpaceString = line.text.match(/^\s*/)[0], indentation + if (!aggressive && !/\S/.test(line.text)) { + indentation = 0 + how = "not" + } else if (how == "smart") { + indentation = doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text) + if (indentation == Pass || indentation > 150) { + if (!aggressive) { return } + how = "prev" + } + } + if (how == "prev") { + if (n > doc.first) { indentation = countColumn(getLine(doc, n-1).text, null, tabSize) } + else { indentation = 0 } + } else if (how == "add") { + indentation = curSpace + cm.options.indentUnit + } else if (how == "subtract") { + indentation = curSpace - cm.options.indentUnit + } else if (typeof how == "number") { + indentation = curSpace + how + } + indentation = Math.max(0, indentation) + + var indentString = "", pos = 0 + if (cm.options.indentWithTabs) + { for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; indentString += "\t"} } + if (pos < indentation) { indentString += spaceStr(indentation - pos) } + + if (indentString != curSpaceString) { + replaceRange(doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input") + line.stateAfter = null + return true + } else { + // Ensure that, if the cursor was in the whitespace at the start + // of the line, it is moved to the end of that space. + for (var i$1 = 0; i$1 < doc.sel.ranges.length; i$1++) { + var range = doc.sel.ranges[i$1] + if (range.head.line == n && range.head.ch < curSpaceString.length) { + var pos$1 = Pos(n, curSpaceString.length) + replaceOneSelection(doc, i$1, new Range(pos$1, pos$1)) + break + } + } + } +} + +// This will be set to a {lineWise: bool, text: [string]} object, so +// that, when pasting, we know what kind of selections the copied +// text was made out of. +var lastCopied = null + +function setLastCopied(newLastCopied) { + lastCopied = newLastCopied +} + +function applyTextInput(cm, inserted, deleted, sel, origin) { + var doc = cm.doc + cm.display.shift = false + if (!sel) { sel = doc.sel } + + var paste = cm.state.pasteIncoming || origin == "paste" + var textLines = splitLinesAuto(inserted), multiPaste = null + // When pasing N lines into N selections, insert one line per selection + if (paste && sel.ranges.length > 1) { + if (lastCopied && lastCopied.text.join("\n") == inserted) { + if (sel.ranges.length % lastCopied.text.length == 0) { + multiPaste = [] + for (var i = 0; i < lastCopied.text.length; i++) + { multiPaste.push(doc.splitLines(lastCopied.text[i])) } + } + } else if (textLines.length == sel.ranges.length) { + multiPaste = map(textLines, function (l) { return [l]; }) + } + } + + var updateInput + // Normal behavior is to insert the new text into every selection + for (var i$1 = sel.ranges.length - 1; i$1 >= 0; i$1--) { + var range = sel.ranges[i$1] + var from = range.from(), to = range.to() + if (range.empty()) { + if (deleted && deleted > 0) // Handle deletion + { from = Pos(from.line, from.ch - deleted) } + else if (cm.state.overwrite && !paste) // Handle overwrite + { to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + lst(textLines).length)) } + else if (lastCopied && lastCopied.lineWise && lastCopied.text.join("\n") == inserted) + { from = to = Pos(from.line, 0) } + } + updateInput = cm.curOp.updateInput + var changeEvent = {from: from, to: to, text: multiPaste ? multiPaste[i$1 % multiPaste.length] : textLines, + origin: origin || (paste ? "paste" : cm.state.cutIncoming ? "cut" : "+input")} + makeChange(cm.doc, changeEvent) + signalLater(cm, "inputRead", cm, changeEvent) + } + if (inserted && !paste) + { triggerElectric(cm, inserted) } + + ensureCursorVisible(cm) + cm.curOp.updateInput = updateInput + cm.curOp.typing = true + cm.state.pasteIncoming = cm.state.cutIncoming = false +} + +function handlePaste(e, cm) { + var pasted = e.clipboardData && e.clipboardData.getData("Text") + if (pasted) { + e.preventDefault() + if (!cm.isReadOnly() && !cm.options.disableInput) + { runInOp(cm, function () { return applyTextInput(cm, pasted, 0, null, "paste"); }) } + return true + } +} + +function triggerElectric(cm, inserted) { + // When an 'electric' character is inserted, immediately trigger a reindent + if (!cm.options.electricChars || !cm.options.smartIndent) { return } + var sel = cm.doc.sel + + for (var i = sel.ranges.length - 1; i >= 0; i--) { + var range = sel.ranges[i] + if (range.head.ch > 100 || (i && sel.ranges[i - 1].head.line == range.head.line)) { continue } + var mode = cm.getModeAt(range.head) + var indented = false + if (mode.electricChars) { + for (var j = 0; j < mode.electricChars.length; j++) + { if (inserted.indexOf(mode.electricChars.charAt(j)) > -1) { + indented = indentLine(cm, range.head.line, "smart") + break + } } + } else if (mode.electricInput) { + if (mode.electricInput.test(getLine(cm.doc, range.head.line).text.slice(0, range.head.ch))) + { indented = indentLine(cm, range.head.line, "smart") } + } + if (indented) { signalLater(cm, "electricInput", cm, range.head.line) } + } +} + +function copyableRanges(cm) { + var text = [], ranges = [] + for (var i = 0; i < cm.doc.sel.ranges.length; i++) { + var line = cm.doc.sel.ranges[i].head.line + var lineRange = {anchor: Pos(line, 0), head: Pos(line + 1, 0)} + ranges.push(lineRange) + text.push(cm.getRange(lineRange.anchor, lineRange.head)) + } + return {text: text, ranges: ranges} +} + +function disableBrowserMagic(field, spellcheck) { + field.setAttribute("autocorrect", "off") + field.setAttribute("autocapitalize", "off") + field.setAttribute("spellcheck", !!spellcheck) +} + +function hiddenTextarea() { + var te = elt("textarea", null, null, "position: absolute; bottom: -1em; padding: 0; width: 1px; height: 1em; outline: none") + var div = elt("div", [te], null, "overflow: hidden; position: relative; width: 3px; height: 0px;") + // The textarea is kept positioned near the cursor to prevent the + // fact that it'll be scrolled into view on input from scrolling + // our fake cursor out of view. On webkit, when wrap=off, paste is + // very slow. So make the area wide instead. + if (webkit) { te.style.width = "1000px" } + else { te.setAttribute("wrap", "off") } + // If border: 0; -- iOS fails to open keyboard (issue #1287) + if (ios) { te.style.border = "1px solid black" } + disableBrowserMagic(te) + return div +} + +// The publicly visible API. Note that methodOp(f) means +// 'wrap f in an operation, performed on its `this` parameter'. + +// This is not the complete set of editor methods. Most of the +// methods defined on the Doc type are also injected into +// CodeMirror.prototype, for backwards compatibility and +// convenience. + +function addEditorMethods(CodeMirror) { + var optionHandlers = CodeMirror.optionHandlers + + var helpers = CodeMirror.helpers = {} + + CodeMirror.prototype = { + constructor: CodeMirror, + focus: function(){window.focus(); this.display.input.focus()}, + + setOption: function(option, value) { + var options = this.options, old = options[option] + if (options[option] == value && option != "mode") { return } + options[option] = value + if (optionHandlers.hasOwnProperty(option)) + { operation(this, optionHandlers[option])(this, value, old) } + signal(this, "optionChange", this, option) + }, + + getOption: function(option) {return this.options[option]}, + getDoc: function() {return this.doc}, + + addKeyMap: function(map, bottom) { + this.state.keyMaps[bottom ? "push" : "unshift"](getKeyMap(map)) + }, + removeKeyMap: function(map) { + var maps = this.state.keyMaps + for (var i = 0; i < maps.length; ++i) + { if (maps[i] == map || maps[i].name == map) { + maps.splice(i, 1) + return true + } } + }, + + addOverlay: methodOp(function(spec, options) { + var mode = spec.token ? spec : CodeMirror.getMode(this.options, spec) + if (mode.startState) { throw new Error("Overlays may not be stateful.") } + insertSorted(this.state.overlays, + {mode: mode, modeSpec: spec, opaque: options && options.opaque, + priority: (options && options.priority) || 0}, + function (overlay) { return overlay.priority; }) + this.state.modeGen++ + regChange(this) + }), + removeOverlay: methodOp(function(spec) { + var this$1 = this; + + var overlays = this.state.overlays + for (var i = 0; i < overlays.length; ++i) { + var cur = overlays[i].modeSpec + if (cur == spec || typeof spec == "string" && cur.name == spec) { + overlays.splice(i, 1) + this$1.state.modeGen++ + regChange(this$1) + return + } + } + }), + + indentLine: methodOp(function(n, dir, aggressive) { + if (typeof dir != "string" && typeof dir != "number") { + if (dir == null) { dir = this.options.smartIndent ? "smart" : "prev" } + else { dir = dir ? "add" : "subtract" } + } + if (isLine(this.doc, n)) { indentLine(this, n, dir, aggressive) } + }), + indentSelection: methodOp(function(how) { + var this$1 = this; + + var ranges = this.doc.sel.ranges, end = -1 + for (var i = 0; i < ranges.length; i++) { + var range = ranges[i] + if (!range.empty()) { + var from = range.from(), to = range.to() + var start = Math.max(end, from.line) + end = Math.min(this$1.lastLine(), to.line - (to.ch ? 0 : 1)) + 1 + for (var j = start; j < end; ++j) + { indentLine(this$1, j, how) } + var newRanges = this$1.doc.sel.ranges + if (from.ch == 0 && ranges.length == newRanges.length && newRanges[i].from().ch > 0) + { replaceOneSelection(this$1.doc, i, new Range(from, newRanges[i].to()), sel_dontScroll) } + } else if (range.head.line > end) { + indentLine(this$1, range.head.line, how, true) + end = range.head.line + if (i == this$1.doc.sel.primIndex) { ensureCursorVisible(this$1) } + } + } + }), + + // Fetch the parser token for a given character. Useful for hacks + // that want to inspect the mode state (say, for completion). + getTokenAt: function(pos, precise) { + return takeToken(this, pos, precise) + }, + + getLineTokens: function(line, precise) { + return takeToken(this, Pos(line), precise, true) + }, + + getTokenTypeAt: function(pos) { + pos = clipPos(this.doc, pos) + var styles = getLineStyles(this, getLine(this.doc, pos.line)) + var before = 0, after = (styles.length - 1) / 2, ch = pos.ch + var type + if (ch == 0) { type = styles[2] } + else { for (;;) { + var mid = (before + after) >> 1 + if ((mid ? styles[mid * 2 - 1] : 0) >= ch) { after = mid } + else if (styles[mid * 2 + 1] < ch) { before = mid + 1 } + else { type = styles[mid * 2 + 2]; break } + } } + var cut = type ? type.indexOf("overlay ") : -1 + return cut < 0 ? type : cut == 0 ? null : type.slice(0, cut - 1) + }, + + getModeAt: function(pos) { + var mode = this.doc.mode + if (!mode.innerMode) { return mode } + return CodeMirror.innerMode(mode, this.getTokenAt(pos).state).mode + }, + + getHelper: function(pos, type) { + return this.getHelpers(pos, type)[0] + }, + + getHelpers: function(pos, type) { + var this$1 = this; + + var found = [] + if (!helpers.hasOwnProperty(type)) { return found } + var help = helpers[type], mode = this.getModeAt(pos) + if (typeof mode[type] == "string") { + if (help[mode[type]]) { found.push(help[mode[type]]) } + } else if (mode[type]) { + for (var i = 0; i < mode[type].length; i++) { + var val = help[mode[type][i]] + if (val) { found.push(val) } + } + } else if (mode.helperType && help[mode.helperType]) { + found.push(help[mode.helperType]) + } else if (help[mode.name]) { + found.push(help[mode.name]) + } + for (var i$1 = 0; i$1 < help._global.length; i$1++) { + var cur = help._global[i$1] + if (cur.pred(mode, this$1) && indexOf(found, cur.val) == -1) + { found.push(cur.val) } + } + return found + }, + + getStateAfter: function(line, precise) { + var doc = this.doc + line = clipLine(doc, line == null ? doc.first + doc.size - 1: line) + return getStateBefore(this, line + 1, precise) + }, + + cursorCoords: function(start, mode) { + var pos, range = this.doc.sel.primary() + if (start == null) { pos = range.head } + else if (typeof start == "object") { pos = clipPos(this.doc, start) } + else { pos = start ? range.from() : range.to() } + return cursorCoords(this, pos, mode || "page") + }, + + charCoords: function(pos, mode) { + return charCoords(this, clipPos(this.doc, pos), mode || "page") + }, + + coordsChar: function(coords, mode) { + coords = fromCoordSystem(this, coords, mode || "page") + return coordsChar(this, coords.left, coords.top) + }, + + lineAtHeight: function(height, mode) { + height = fromCoordSystem(this, {top: height, left: 0}, mode || "page").top + return lineAtHeight(this.doc, height + this.display.viewOffset) + }, + heightAtLine: function(line, mode, includeWidgets) { + var end = false, lineObj + if (typeof line == "number") { + var last = this.doc.first + this.doc.size - 1 + if (line < this.doc.first) { line = this.doc.first } + else if (line > last) { line = last; end = true } + lineObj = getLine(this.doc, line) + } else { + lineObj = line + } + return intoCoordSystem(this, lineObj, {top: 0, left: 0}, mode || "page", includeWidgets).top + + (end ? this.doc.height - heightAtLine(lineObj) : 0) + }, + + defaultTextHeight: function() { return textHeight(this.display) }, + defaultCharWidth: function() { return charWidth(this.display) }, + + getViewport: function() { return {from: this.display.viewFrom, to: this.display.viewTo}}, + + addWidget: function(pos, node, scroll, vert, horiz) { + var display = this.display + pos = cursorCoords(this, clipPos(this.doc, pos)) + var top = pos.bottom, left = pos.left + node.style.position = "absolute" + node.setAttribute("cm-ignore-events", "true") + this.display.input.setUneditable(node) + display.sizer.appendChild(node) + if (vert == "over") { + top = pos.top + } else if (vert == "above" || vert == "near") { + var vspace = Math.max(display.wrapper.clientHeight, this.doc.height), + hspace = Math.max(display.sizer.clientWidth, display.lineSpace.clientWidth) + // Default to positioning above (if specified and possible); otherwise default to positioning below + if ((vert == 'above' || pos.bottom + node.offsetHeight > vspace) && pos.top > node.offsetHeight) + { top = pos.top - node.offsetHeight } + else if (pos.bottom + node.offsetHeight <= vspace) + { top = pos.bottom } + if (left + node.offsetWidth > hspace) + { left = hspace - node.offsetWidth } + } + node.style.top = top + "px" + node.style.left = node.style.right = "" + if (horiz == "right") { + left = display.sizer.clientWidth - node.offsetWidth + node.style.right = "0px" + } else { + if (horiz == "left") { left = 0 } + else if (horiz == "middle") { left = (display.sizer.clientWidth - node.offsetWidth) / 2 } + node.style.left = left + "px" + } + if (scroll) + { scrollIntoView(this, left, top, left + node.offsetWidth, top + node.offsetHeight) } + }, + + triggerOnKeyDown: methodOp(onKeyDown), + triggerOnKeyPress: methodOp(onKeyPress), + triggerOnKeyUp: onKeyUp, + + execCommand: function(cmd) { + if (commands.hasOwnProperty(cmd)) + { return commands[cmd].call(null, this) } + }, + + triggerElectric: methodOp(function(text) { triggerElectric(this, text) }), + + findPosH: function(from, amount, unit, visually) { + var this$1 = this; + + var dir = 1 + if (amount < 0) { dir = -1; amount = -amount } + var cur = clipPos(this.doc, from) + for (var i = 0; i < amount; ++i) { + cur = findPosH(this$1.doc, cur, dir, unit, visually) + if (cur.hitSide) { break } + } + return cur + }, + + moveH: methodOp(function(dir, unit) { + var this$1 = this; + + this.extendSelectionsBy(function (range) { + if (this$1.display.shift || this$1.doc.extend || range.empty()) + { return findPosH(this$1.doc, range.head, dir, unit, this$1.options.rtlMoveVisually) } + else + { return dir < 0 ? range.from() : range.to() } + }, sel_move) + }), + + deleteH: methodOp(function(dir, unit) { + var sel = this.doc.sel, doc = this.doc + if (sel.somethingSelected()) + { doc.replaceSelection("", null, "+delete") } + else + { deleteNearSelection(this, function (range) { + var other = findPosH(doc, range.head, dir, unit, false) + return dir < 0 ? {from: other, to: range.head} : {from: range.head, to: other} + }) } + }), + + findPosV: function(from, amount, unit, goalColumn) { + var this$1 = this; + + var dir = 1, x = goalColumn + if (amount < 0) { dir = -1; amount = -amount } + var cur = clipPos(this.doc, from) + for (var i = 0; i < amount; ++i) { + var coords = cursorCoords(this$1, cur, "div") + if (x == null) { x = coords.left } + else { coords.left = x } + cur = findPosV(this$1, coords, dir, unit) + if (cur.hitSide) { break } + } + return cur + }, + + moveV: methodOp(function(dir, unit) { + var this$1 = this; + + var doc = this.doc, goals = [] + var collapse = !this.display.shift && !doc.extend && doc.sel.somethingSelected() + doc.extendSelectionsBy(function (range) { + if (collapse) + { return dir < 0 ? range.from() : range.to() } + var headPos = cursorCoords(this$1, range.head, "div") + if (range.goalColumn != null) { headPos.left = range.goalColumn } + goals.push(headPos.left) + var pos = findPosV(this$1, headPos, dir, unit) + if (unit == "page" && range == doc.sel.primary()) + { addToScrollPos(this$1, null, charCoords(this$1, pos, "div").top - headPos.top) } + return pos + }, sel_move) + if (goals.length) { for (var i = 0; i < doc.sel.ranges.length; i++) + { doc.sel.ranges[i].goalColumn = goals[i] } } + }), + + // Find the word at the given position (as returned by coordsChar). + findWordAt: function(pos) { + var doc = this.doc, line = getLine(doc, pos.line).text + var start = pos.ch, end = pos.ch + if (line) { + var helper = this.getHelper(pos, "wordChars") + if ((pos.xRel < 0 || end == line.length) && start) { --start; } else { ++end } + var startChar = line.charAt(start) + var check = isWordChar(startChar, helper) + ? function (ch) { return isWordChar(ch, helper); } + : /\s/.test(startChar) ? function (ch) { return /\s/.test(ch); } + : function (ch) { return (!/\s/.test(ch) && !isWordChar(ch)); } + while (start > 0 && check(line.charAt(start - 1))) { --start } + while (end < line.length && check(line.charAt(end))) { ++end } + } + return new Range(Pos(pos.line, start), Pos(pos.line, end)) + }, + + toggleOverwrite: function(value) { + if (value != null && value == this.state.overwrite) { return } + if (this.state.overwrite = !this.state.overwrite) + { addClass(this.display.cursorDiv, "CodeMirror-overwrite") } + else + { rmClass(this.display.cursorDiv, "CodeMirror-overwrite") } + + signal(this, "overwriteToggle", this, this.state.overwrite) + }, + hasFocus: function() { return this.display.input.getField() == activeElt() }, + isReadOnly: function() { return !!(this.options.readOnly || this.doc.cantEdit) }, + + scrollTo: methodOp(function(x, y) { + if (x != null || y != null) { resolveScrollToPos(this) } + if (x != null) { this.curOp.scrollLeft = x } + if (y != null) { this.curOp.scrollTop = y } + }), + getScrollInfo: function() { + var scroller = this.display.scroller + return {left: scroller.scrollLeft, top: scroller.scrollTop, + height: scroller.scrollHeight - scrollGap(this) - this.display.barHeight, + width: scroller.scrollWidth - scrollGap(this) - this.display.barWidth, + clientHeight: displayHeight(this), clientWidth: displayWidth(this)} + }, + + scrollIntoView: methodOp(function(range, margin) { + if (range == null) { + range = {from: this.doc.sel.primary().head, to: null} + if (margin == null) { margin = this.options.cursorScrollMargin } + } else if (typeof range == "number") { + range = {from: Pos(range, 0), to: null} + } else if (range.from == null) { + range = {from: range, to: null} + } + if (!range.to) { range.to = range.from } + range.margin = margin || 0 + + if (range.from.line != null) { + resolveScrollToPos(this) + this.curOp.scrollToPos = range + } else { + var sPos = calculateScrollPos(this, Math.min(range.from.left, range.to.left), + Math.min(range.from.top, range.to.top) - range.margin, + Math.max(range.from.right, range.to.right), + Math.max(range.from.bottom, range.to.bottom) + range.margin) + this.scrollTo(sPos.scrollLeft, sPos.scrollTop) + } + }), + + setSize: methodOp(function(width, height) { + var this$1 = this; + + var interpret = function (val) { return typeof val == "number" || /^\d+$/.test(String(val)) ? val + "px" : val; } + if (width != null) { this.display.wrapper.style.width = interpret(width) } + if (height != null) { this.display.wrapper.style.height = interpret(height) } + if (this.options.lineWrapping) { clearLineMeasurementCache(this) } + var lineNo = this.display.viewFrom + this.doc.iter(lineNo, this.display.viewTo, function (line) { + if (line.widgets) { for (var i = 0; i < line.widgets.length; i++) + { if (line.widgets[i].noHScroll) { regLineChange(this$1, lineNo, "widget"); break } } } + ++lineNo + }) + this.curOp.forceUpdate = true + signal(this, "refresh", this) + }), + + operation: function(f){return runInOp(this, f)}, + + refresh: methodOp(function() { + var oldHeight = this.display.cachedTextHeight + regChange(this) + this.curOp.forceUpdate = true + clearCaches(this) + this.scrollTo(this.doc.scrollLeft, this.doc.scrollTop) + updateGutterSpace(this) + if (oldHeight == null || Math.abs(oldHeight - textHeight(this.display)) > .5) + { estimateLineHeights(this) } + signal(this, "refresh", this) + }), + + swapDoc: methodOp(function(doc) { + var old = this.doc + old.cm = null + attachDoc(this, doc) + clearCaches(this) + this.display.input.reset() + this.scrollTo(doc.scrollLeft, doc.scrollTop) + this.curOp.forceScroll = true + signalLater(this, "swapDoc", this, old) + return old + }), + + getInputField: function(){return this.display.input.getField()}, + getWrapperElement: function(){return this.display.wrapper}, + getScrollerElement: function(){return this.display.scroller}, + getGutterElement: function(){return this.display.gutters} + } + eventMixin(CodeMirror) + + CodeMirror.registerHelper = function(type, name, value) { + if (!helpers.hasOwnProperty(type)) { helpers[type] = CodeMirror[type] = {_global: []} } + helpers[type][name] = value + } + CodeMirror.registerGlobalHelper = function(type, name, predicate, value) { + CodeMirror.registerHelper(type, name, value) + helpers[type]._global.push({pred: predicate, val: value}) + } +} + +// Used for horizontal relative motion. Dir is -1 or 1 (left or +// right), unit can be "char", "column" (like char, but doesn't +// cross line boundaries), "word" (across next word), or "group" (to +// the start of next group of word or non-word-non-whitespace +// chars). The visually param controls whether, in right-to-left +// text, direction 1 means to move towards the next index in the +// string, or towards the character to the right of the current +// position. The resulting position will have a hitSide=true +// property if it reached the end of the document. +function findPosH(doc, pos, dir, unit, visually) { + var line = pos.line, ch = pos.ch, origDir = dir + var lineObj = getLine(doc, line) + function findNextLine() { + var l = line + dir + if (l < doc.first || l >= doc.first + doc.size) { return false } + line = l + return lineObj = getLine(doc, l) + } + function moveOnce(boundToLine) { + var next = (visually ? moveVisually : moveLogically)(lineObj, ch, dir, true) + if (next == null) { + if (!boundToLine && findNextLine()) { + if (visually) { ch = (dir < 0 ? lineRight : lineLeft)(lineObj) } + else { ch = dir < 0 ? lineObj.text.length : 0 } + } else { return false } + } else { ch = next } + return true + } + + if (unit == "char") { + moveOnce() + } else if (unit == "column") { + moveOnce(true) + } else if (unit == "word" || unit == "group") { + var sawType = null, group = unit == "group" + var helper = doc.cm && doc.cm.getHelper(pos, "wordChars") + for (var first = true;; first = false) { + if (dir < 0 && !moveOnce(!first)) { break } + var cur = lineObj.text.charAt(ch) || "\n" + var type = isWordChar(cur, helper) ? "w" + : group && cur == "\n" ? "n" + : !group || /\s/.test(cur) ? null + : "p" + if (group && !first && !type) { type = "s" } + if (sawType && sawType != type) { + if (dir < 0) {dir = 1; moveOnce()} + break + } + + if (type) { sawType = type } + if (dir > 0 && !moveOnce(!first)) { break } + } + } + var result = skipAtomic(doc, Pos(line, ch), pos, origDir, true) + if (!cmp(pos, result)) { result.hitSide = true } + return result +} + +// For relative vertical movement. Dir may be -1 or 1. Unit can be +// "page" or "line". The resulting position will have a hitSide=true +// property if it reached the end of the document. +function findPosV(cm, pos, dir, unit) { + var doc = cm.doc, x = pos.left, y + if (unit == "page") { + var pageSize = Math.min(cm.display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight) + var moveAmount = Math.max(pageSize - .5 * textHeight(cm.display), 3) + y = (dir > 0 ? pos.bottom : pos.top) + dir * moveAmount + + } else if (unit == "line") { + y = dir > 0 ? pos.bottom + 3 : pos.top - 3 + } + var target + for (;;) { + target = coordsChar(cm, x, y) + if (!target.outside) { break } + if (dir < 0 ? y <= 0 : y >= doc.height) { target.hitSide = true; break } + y += dir * 5 + } + return target +} + +// CONTENTEDITABLE INPUT STYLE + +var ContentEditableInput = function(cm) { + this.cm = cm + this.lastAnchorNode = this.lastAnchorOffset = this.lastFocusNode = this.lastFocusOffset = null + this.polling = new Delayed() + this.composing = null + this.gracePeriod = false + this.readDOMTimeout = null +}; + +ContentEditableInput.prototype.init = function (display) { + var this$1 = this; + + var input = this, cm = input.cm + var div = input.div = display.lineDiv + disableBrowserMagic(div, cm.options.spellcheck) + + on(div, "paste", function (e) { + if (signalDOMEvent(cm, e) || handlePaste(e, cm)) { return } + // IE doesn't fire input events, so we schedule a read for the pasted content in this way + if (ie_version <= 11) { setTimeout(operation(cm, function () { + if (!input.pollContent()) { regChange(cm) } + }), 20) } + }) + + on(div, "compositionstart", function (e) { + this$1.composing = {data: e.data, done: false} + }) + on(div, "compositionupdate", function (e) { + if (!this$1.composing) { this$1.composing = {data: e.data, done: false} } + }) + on(div, "compositionend", function (e) { + if (this$1.composing) { + if (e.data != this$1.composing.data) { this$1.readFromDOMSoon() } + this$1.composing.done = true + } + }) + + on(div, "touchstart", function () { return input.forceCompositionEnd(); }) + + on(div, "input", function () { + if (!this$1.composing) { this$1.readFromDOMSoon() } + }) + + function onCopyCut(e) { + if (signalDOMEvent(cm, e)) { return } + if (cm.somethingSelected()) { + setLastCopied({lineWise: false, text: cm.getSelections()}) + if (e.type == "cut") { cm.replaceSelection("", null, "cut") } + } else if (!cm.options.lineWiseCopyCut) { + return + } else { + var ranges = copyableRanges(cm) + setLastCopied({lineWise: true, text: ranges.text}) + if (e.type == "cut") { + cm.operation(function () { + cm.setSelections(ranges.ranges, 0, sel_dontScroll) + cm.replaceSelection("", null, "cut") + }) + } + } + if (e.clipboardData) { + e.clipboardData.clearData() + var content = lastCopied.text.join("\n") + // iOS exposes the clipboard API, but seems to discard content inserted into it + e.clipboardData.setData("Text", content) + if (e.clipboardData.getData("Text") == content) { + e.preventDefault() + return + } + } + // Old-fashioned briefly-focus-a-textarea hack + var kludge = hiddenTextarea(), te = kludge.firstChild + cm.display.lineSpace.insertBefore(kludge, cm.display.lineSpace.firstChild) + te.value = lastCopied.text.join("\n") + var hadFocus = document.activeElement + selectInput(te) + setTimeout(function () { + cm.display.lineSpace.removeChild(kludge) + hadFocus.focus() + if (hadFocus == div) { input.showPrimarySelection() } + }, 50) + } + on(div, "copy", onCopyCut) + on(div, "cut", onCopyCut) +}; + +ContentEditableInput.prototype.prepareSelection = function () { + var result = prepareSelection(this.cm, false) + result.focus = this.cm.state.focused + return result +}; + +ContentEditableInput.prototype.showSelection = function (info, takeFocus) { + if (!info || !this.cm.display.view.length) { return } + if (info.focus || takeFocus) { this.showPrimarySelection() } + this.showMultipleSelections(info) +}; + +ContentEditableInput.prototype.showPrimarySelection = function () { + var sel = window.getSelection(), prim = this.cm.doc.sel.primary() + var curAnchor = domToPos(this.cm, sel.anchorNode, sel.anchorOffset) + var curFocus = domToPos(this.cm, sel.focusNode, sel.focusOffset) + if (curAnchor && !curAnchor.bad && curFocus && !curFocus.bad && + cmp(minPos(curAnchor, curFocus), prim.from()) == 0 && + cmp(maxPos(curAnchor, curFocus), prim.to()) == 0) + { return } + + var start = posToDOM(this.cm, prim.from()) + var end = posToDOM(this.cm, prim.to()) + if (!start && !end) { return } + + var view = this.cm.display.view + var old = sel.rangeCount && sel.getRangeAt(0) + if (!start) { + start = {node: view[0].measure.map[2], offset: 0} + } else if (!end) { // FIXME dangerously hacky + var measure = view[view.length - 1].measure + var map = measure.maps ? measure.maps[measure.maps.length - 1] : measure.map + end = {node: map[map.length - 1], offset: map[map.length - 2] - map[map.length - 3]} + } + + var rng + try { rng = range(start.node, start.offset, end.offset, end.node) } + catch(e) {} // Our model of the DOM might be outdated, in which case the range we try to set can be impossible + if (rng) { + if (!gecko && this.cm.state.focused) { + sel.collapse(start.node, start.offset) + if (!rng.collapsed) { + sel.removeAllRanges() + sel.addRange(rng) + } + } else { + sel.removeAllRanges() + sel.addRange(rng) + } + if (old && sel.anchorNode == null) { sel.addRange(old) } + else if (gecko) { this.startGracePeriod() } + } + this.rememberSelection() +}; + +ContentEditableInput.prototype.startGracePeriod = function () { + var this$1 = this; + + clearTimeout(this.gracePeriod) + this.gracePeriod = setTimeout(function () { + this$1.gracePeriod = false + if (this$1.selectionChanged()) + { this$1.cm.operation(function () { return this$1.cm.curOp.selectionChanged = true; }) } + }, 20) +}; + +ContentEditableInput.prototype.showMultipleSelections = function (info) { + removeChildrenAndAdd(this.cm.display.cursorDiv, info.cursors) + removeChildrenAndAdd(this.cm.display.selectionDiv, info.selection) +}; + +ContentEditableInput.prototype.rememberSelection = function () { + var sel = window.getSelection() + this.lastAnchorNode = sel.anchorNode; this.lastAnchorOffset = sel.anchorOffset + this.lastFocusNode = sel.focusNode; this.lastFocusOffset = sel.focusOffset +}; + +ContentEditableInput.prototype.selectionInEditor = function () { + var sel = window.getSelection() + if (!sel.rangeCount) { return false } + var node = sel.getRangeAt(0).commonAncestorContainer + return contains(this.div, node) +}; + +ContentEditableInput.prototype.focus = function () { + if (this.cm.options.readOnly != "nocursor") { + if (!this.selectionInEditor()) + { this.showSelection(this.prepareSelection(), true) } + this.div.focus() + } +}; +ContentEditableInput.prototype.blur = function () { this.div.blur() }; +ContentEditableInput.prototype.getField = function () { return this.div }; + +ContentEditableInput.prototype.supportsTouch = function () { return true }; + +ContentEditableInput.prototype.receivedFocus = function () { + var input = this + if (this.selectionInEditor()) + { this.pollSelection() } + else + { runInOp(this.cm, function () { return input.cm.curOp.selectionChanged = true; }) } + + function poll() { + if (input.cm.state.focused) { + input.pollSelection() + input.polling.set(input.cm.options.pollInterval, poll) + } + } + this.polling.set(this.cm.options.pollInterval, poll) +}; + +ContentEditableInput.prototype.selectionChanged = function () { + var sel = window.getSelection() + return sel.anchorNode != this.lastAnchorNode || sel.anchorOffset != this.lastAnchorOffset || + sel.focusNode != this.lastFocusNode || sel.focusOffset != this.lastFocusOffset +}; + +ContentEditableInput.prototype.pollSelection = function () { + if (!this.composing && this.readDOMTimeout == null && !this.gracePeriod && this.selectionChanged()) { + var sel = window.getSelection(), cm = this.cm + this.rememberSelection() + var anchor = domToPos(cm, sel.anchorNode, sel.anchorOffset) + var head = domToPos(cm, sel.focusNode, sel.focusOffset) + if (anchor && head) { runInOp(cm, function () { + setSelection(cm.doc, simpleSelection(anchor, head), sel_dontScroll) + if (anchor.bad || head.bad) { cm.curOp.selectionChanged = true } + }) } + } +}; + +ContentEditableInput.prototype.pollContent = function () { + if (this.readDOMTimeout != null) { + clearTimeout(this.readDOMTimeout) + this.readDOMTimeout = null + } + + var cm = this.cm, display = cm.display, sel = cm.doc.sel.primary() + var from = sel.from(), to = sel.to() + if (from.ch == 0 && from.line > cm.firstLine()) + { from = Pos(from.line - 1, getLine(cm.doc, from.line - 1).length) } + if (to.ch == getLine(cm.doc, to.line).text.length && to.line < cm.lastLine()) + { to = Pos(to.line + 1, 0) } + if (from.line < display.viewFrom || to.line > display.viewTo - 1) { return false } + + var fromIndex, fromLine, fromNode + if (from.line == display.viewFrom || (fromIndex = findViewIndex(cm, from.line)) == 0) { + fromLine = lineNo(display.view[0].line) + fromNode = display.view[0].node + } else { + fromLine = lineNo(display.view[fromIndex].line) + fromNode = display.view[fromIndex - 1].node.nextSibling + } + var toIndex = findViewIndex(cm, to.line) + var toLine, toNode + if (toIndex == display.view.length - 1) { + toLine = display.viewTo - 1 + toNode = display.lineDiv.lastChild + } else { + toLine = lineNo(display.view[toIndex + 1].line) - 1 + toNode = display.view[toIndex + 1].node.previousSibling + } + + if (!fromNode) { return false } + var newText = cm.doc.splitLines(domTextBetween(cm, fromNode, toNode, fromLine, toLine)) + var oldText = getBetween(cm.doc, Pos(fromLine, 0), Pos(toLine, getLine(cm.doc, toLine).text.length)) + while (newText.length > 1 && oldText.length > 1) { + if (lst(newText) == lst(oldText)) { newText.pop(); oldText.pop(); toLine-- } + else if (newText[0] == oldText[0]) { newText.shift(); oldText.shift(); fromLine++ } + else { break } + } + + var cutFront = 0, cutEnd = 0 + var newTop = newText[0], oldTop = oldText[0], maxCutFront = Math.min(newTop.length, oldTop.length) + while (cutFront < maxCutFront && newTop.charCodeAt(cutFront) == oldTop.charCodeAt(cutFront)) + { ++cutFront } + var newBot = lst(newText), oldBot = lst(oldText) + var maxCutEnd = Math.min(newBot.length - (newText.length == 1 ? cutFront : 0), + oldBot.length - (oldText.length == 1 ? cutFront : 0)) + while (cutEnd < maxCutEnd && + newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldBot.length - cutEnd - 1)) + { ++cutEnd } + + newText[newText.length - 1] = newBot.slice(0, newBot.length - cutEnd).replace(/^\u200b+/, "") + newText[0] = newText[0].slice(cutFront).replace(/\u200b+$/, "") + + var chFrom = Pos(fromLine, cutFront) + var chTo = Pos(toLine, oldText.length ? lst(oldText).length - cutEnd : 0) + if (newText.length > 1 || newText[0] || cmp(chFrom, chTo)) { + replaceRange(cm.doc, newText, chFrom, chTo, "+input") + return true + } +}; + +ContentEditableInput.prototype.ensurePolled = function () { + this.forceCompositionEnd() +}; +ContentEditableInput.prototype.reset = function () { + this.forceCompositionEnd() +}; +ContentEditableInput.prototype.forceCompositionEnd = function () { + if (!this.composing) { return } + clearTimeout(this.readDOMTimeout) + this.composing = null + if (!this.pollContent()) { regChange(this.cm) } + this.div.blur() + this.div.focus() +}; +ContentEditableInput.prototype.readFromDOMSoon = function () { + var this$1 = this; + + if (this.readDOMTimeout != null) { return } + this.readDOMTimeout = setTimeout(function () { + this$1.readDOMTimeout = null + if (this$1.composing) { + if (this$1.composing.done) { this$1.composing = null } + else { return } + } + if (this$1.cm.isReadOnly() || !this$1.pollContent()) + { runInOp(this$1.cm, function () { return regChange(this$1.cm); }) } + }, 80) +}; + +ContentEditableInput.prototype.setUneditable = function (node) { + node.contentEditable = "false" +}; + +ContentEditableInput.prototype.onKeyPress = function (e) { + e.preventDefault() + if (!this.cm.isReadOnly()) + { operation(this.cm, applyTextInput)(this.cm, String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode), 0) } +}; + +ContentEditableInput.prototype.readOnlyChanged = function (val) { + this.div.contentEditable = String(val != "nocursor") +}; + +ContentEditableInput.prototype.onContextMenu = function () {}; +ContentEditableInput.prototype.resetPosition = function () {}; + +ContentEditableInput.prototype.needsContentAttribute = true + +function posToDOM(cm, pos) { + var view = findViewForLine(cm, pos.line) + if (!view || view.hidden) { return null } + var line = getLine(cm.doc, pos.line) + var info = mapFromLineView(view, line, pos.line) + + var order = getOrder(line), side = "left" + if (order) { + var partPos = getBidiPartAt(order, pos.ch) + side = partPos % 2 ? "right" : "left" + } + var result = nodeAndOffsetInLineMap(info.map, pos.ch, side) + result.offset = result.collapse == "right" ? result.end : result.start + return result +} + +function badPos(pos, bad) { if (bad) { pos.bad = true; } return pos } + +function domTextBetween(cm, from, to, fromLine, toLine) { + var text = "", closing = false, lineSep = cm.doc.lineSeparator() + function recognizeMarker(id) { return function (marker) { return marker.id == id; } } + function walk(node) { + if (node.nodeType == 1) { + var cmText = node.getAttribute("cm-text") + if (cmText != null) { + if (cmText == "") { text += node.textContent.replace(/\u200b/g, "") } + else { text += cmText } + return + } + var markerID = node.getAttribute("cm-marker"), range + if (markerID) { + var found = cm.findMarks(Pos(fromLine, 0), Pos(toLine + 1, 0), recognizeMarker(+markerID)) + if (found.length && (range = found[0].find())) + { text += getBetween(cm.doc, range.from, range.to).join(lineSep) } + return + } + if (node.getAttribute("contenteditable") == "false") { return } + for (var i = 0; i < node.childNodes.length; i++) + { walk(node.childNodes[i]) } + if (/^(pre|div|p)$/i.test(node.nodeName)) + { closing = true } + } else if (node.nodeType == 3) { + var val = node.nodeValue + if (!val) { return } + if (closing) { + text += lineSep + closing = false + } + text += val + } + } + for (;;) { + walk(from) + if (from == to) { break } + from = from.nextSibling + } + return text +} + +function domToPos(cm, node, offset) { + var lineNode + if (node == cm.display.lineDiv) { + lineNode = cm.display.lineDiv.childNodes[offset] + if (!lineNode) { return badPos(cm.clipPos(Pos(cm.display.viewTo - 1)), true) } + node = null; offset = 0 + } else { + for (lineNode = node;; lineNode = lineNode.parentNode) { + if (!lineNode || lineNode == cm.display.lineDiv) { return null } + if (lineNode.parentNode && lineNode.parentNode == cm.display.lineDiv) { break } + } + } + for (var i = 0; i < cm.display.view.length; i++) { + var lineView = cm.display.view[i] + if (lineView.node == lineNode) + { return locateNodeInLineView(lineView, node, offset) } + } +} + +function locateNodeInLineView(lineView, node, offset) { + var wrapper = lineView.text.firstChild, bad = false + if (!node || !contains(wrapper, node)) { return badPos(Pos(lineNo(lineView.line), 0), true) } + if (node == wrapper) { + bad = true + node = wrapper.childNodes[offset] + offset = 0 + if (!node) { + var line = lineView.rest ? lst(lineView.rest) : lineView.line + return badPos(Pos(lineNo(line), line.text.length), bad) + } + } + + var textNode = node.nodeType == 3 ? node : null, topNode = node + if (!textNode && node.childNodes.length == 1 && node.firstChild.nodeType == 3) { + textNode = node.firstChild + if (offset) { offset = textNode.nodeValue.length } + } + while (topNode.parentNode != wrapper) { topNode = topNode.parentNode } + var measure = lineView.measure, maps = measure.maps + + function find(textNode, topNode, offset) { + for (var i = -1; i < (maps ? maps.length : 0); i++) { + var map = i < 0 ? measure.map : maps[i] + for (var j = 0; j < map.length; j += 3) { + var curNode = map[j + 2] + if (curNode == textNode || curNode == topNode) { + var line = lineNo(i < 0 ? lineView.line : lineView.rest[i]) + var ch = map[j] + offset + if (offset < 0 || curNode != textNode) { ch = map[j + (offset ? 1 : 0)] } + return Pos(line, ch) + } + } + } + } + var found = find(textNode, topNode, offset) + if (found) { return badPos(found, bad) } + + // FIXME this is all really shaky. might handle the few cases it needs to handle, but likely to cause problems + for (var after = topNode.nextSibling, dist = textNode ? textNode.nodeValue.length - offset : 0; after; after = after.nextSibling) { + found = find(after, after.firstChild, 0) + if (found) + { return badPos(Pos(found.line, found.ch - dist), bad) } + else + { dist += after.textContent.length } + } + for (var before = topNode.previousSibling, dist$1 = offset; before; before = before.previousSibling) { + found = find(before, before.firstChild, -1) + if (found) + { return badPos(Pos(found.line, found.ch + dist$1), bad) } + else + { dist$1 += before.textContent.length } + } +} + +// TEXTAREA INPUT STYLE + +var TextareaInput = function(cm) { + this.cm = cm + // See input.poll and input.reset + this.prevInput = "" + + // Flag that indicates whether we expect input to appear real soon + // now (after some event like 'keypress' or 'input') and are + // polling intensively. + this.pollingFast = false + // Self-resetting timeout for the poller + this.polling = new Delayed() + // Tracks when input.reset has punted to just putting a short + // string into the textarea instead of the full selection. + this.inaccurateSelection = false + // Used to work around IE issue with selection being forgotten when focus moves away from textarea + this.hasSelection = false + this.composing = null +}; + +TextareaInput.prototype.init = function (display) { + var this$1 = this; + + var input = this, cm = this.cm + + // Wraps and hides input textarea + var div = this.wrapper = hiddenTextarea() + // The semihidden textarea that is focused when the editor is + // focused, and receives input. + var te = this.textarea = div.firstChild + display.wrapper.insertBefore(div, display.wrapper.firstChild) + + // Needed to hide big blue blinking cursor on Mobile Safari (doesn't seem to work in iOS 8 anymore) + if (ios) { te.style.width = "0px" } + + on(te, "input", function () { + if (ie && ie_version >= 9 && this$1.hasSelection) { this$1.hasSelection = null } + input.poll() + }) + + on(te, "paste", function (e) { + if (signalDOMEvent(cm, e) || handlePaste(e, cm)) { return } + + cm.state.pasteIncoming = true + input.fastPoll() + }) + + function prepareCopyCut(e) { + if (signalDOMEvent(cm, e)) { return } + if (cm.somethingSelected()) { + setLastCopied({lineWise: false, text: cm.getSelections()}) + if (input.inaccurateSelection) { + input.prevInput = "" + input.inaccurateSelection = false + te.value = lastCopied.text.join("\n") + selectInput(te) + } + } else if (!cm.options.lineWiseCopyCut) { + return + } else { + var ranges = copyableRanges(cm) + setLastCopied({lineWise: true, text: ranges.text}) + if (e.type == "cut") { + cm.setSelections(ranges.ranges, null, sel_dontScroll) + } else { + input.prevInput = "" + te.value = ranges.text.join("\n") + selectInput(te) + } + } + if (e.type == "cut") { cm.state.cutIncoming = true } + } + on(te, "cut", prepareCopyCut) + on(te, "copy", prepareCopyCut) + + on(display.scroller, "paste", function (e) { + if (eventInWidget(display, e) || signalDOMEvent(cm, e)) { return } + cm.state.pasteIncoming = true + input.focus() + }) + + // Prevent normal selection in the editor (we handle our own) + on(display.lineSpace, "selectstart", function (e) { + if (!eventInWidget(display, e)) { e_preventDefault(e) } + }) + + on(te, "compositionstart", function () { + var start = cm.getCursor("from") + if (input.composing) { input.composing.range.clear() } + input.composing = { + start: start, + range: cm.markText(start, cm.getCursor("to"), {className: "CodeMirror-composing"}) + } + }) + on(te, "compositionend", function () { + if (input.composing) { + input.poll() + input.composing.range.clear() + input.composing = null + } + }) +}; + +TextareaInput.prototype.prepareSelection = function () { + // Redraw the selection and/or cursor + var cm = this.cm, display = cm.display, doc = cm.doc + var result = prepareSelection(cm) + + // Move the hidden textarea near the cursor to prevent scrolling artifacts + if (cm.options.moveInputWithCursor) { + var headPos = cursorCoords(cm, doc.sel.primary().head, "div") + var wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display.lineDiv.getBoundingClientRect() + result.teTop = Math.max(0, Math.min(display.wrapper.clientHeight - 10, + headPos.top + lineOff.top - wrapOff.top)) + result.teLeft = Math.max(0, Math.min(display.wrapper.clientWidth - 10, + headPos.left + lineOff.left - wrapOff.left)) + } + + return result +}; + +TextareaInput.prototype.showSelection = function (drawn) { + var cm = this.cm, display = cm.display + removeChildrenAndAdd(display.cursorDiv, drawn.cursors) + removeChildrenAndAdd(display.selectionDiv, drawn.selection) + if (drawn.teTop != null) { + this.wrapper.style.top = drawn.teTop + "px" + this.wrapper.style.left = drawn.teLeft + "px" + } +}; + +// Reset the input to correspond to the selection (or to be empty, +// when not typing and nothing is selected) +TextareaInput.prototype.reset = function (typing) { + if (this.contextMenuPending) { return } + var minimal, selected, cm = this.cm, doc = cm.doc + if (cm.somethingSelected()) { + this.prevInput = "" + var range = doc.sel.primary() + minimal = hasCopyEvent && + (range.to().line - range.from().line > 100 || (selected = cm.getSelection()).length > 1000) + var content = minimal ? "-" : selected || cm.getSelection() + this.textarea.value = content + if (cm.state.focused) { selectInput(this.textarea) } + if (ie && ie_version >= 9) { this.hasSelection = content } + } else if (!typing) { + this.prevInput = this.textarea.value = "" + if (ie && ie_version >= 9) { this.hasSelection = null } + } + this.inaccurateSelection = minimal +}; + +TextareaInput.prototype.getField = function () { return this.textarea }; + +TextareaInput.prototype.supportsTouch = function () { return false }; + +TextareaInput.prototype.focus = function () { + if (this.cm.options.readOnly != "nocursor" && (!mobile || activeElt() != this.textarea)) { + try { this.textarea.focus() } + catch (e) {} // IE8 will throw if the textarea is display: none or not in DOM + } +}; + +TextareaInput.prototype.blur = function () { this.textarea.blur() }; + +TextareaInput.prototype.resetPosition = function () { + this.wrapper.style.top = this.wrapper.style.left = 0 +}; + +TextareaInput.prototype.receivedFocus = function () { this.slowPoll() }; + +// Poll for input changes, using the normal rate of polling. This +// runs as long as the editor is focused. +TextareaInput.prototype.slowPoll = function () { + var this$1 = this; + + if (this.pollingFast) { return } + this.polling.set(this.cm.options.pollInterval, function () { + this$1.poll() + if (this$1.cm.state.focused) { this$1.slowPoll() } + }) +}; + +// When an event has just come in that is likely to add or change +// something in the input textarea, we poll faster, to ensure that +// the change appears on the screen quickly. +TextareaInput.prototype.fastPoll = function () { + var missed = false, input = this + input.pollingFast = true + function p() { + var changed = input.poll() + if (!changed && !missed) {missed = true; input.polling.set(60, p)} + else {input.pollingFast = false; input.slowPoll()} + } + input.polling.set(20, p) +}; + +// Read input from the textarea, and update the document to match. +// When something is selected, it is present in the textarea, and +// selected (unless it is huge, in which case a placeholder is +// used). When nothing is selected, the cursor sits after previously +// seen text (can be empty), which is stored in prevInput (we must +// not reset the textarea when typing, because that breaks IME). +TextareaInput.prototype.poll = function () { + var this$1 = this; + + var cm = this.cm, input = this.textarea, prevInput = this.prevInput + // Since this is called a *lot*, try to bail out as cheaply as + // possible when it is clear that nothing happened. hasSelection + // will be the case when there is a lot of text in the textarea, + // in which case reading its value would be expensive. + if (this.contextMenuPending || !cm.state.focused || + (hasSelection(input) && !prevInput && !this.composing) || + cm.isReadOnly() || cm.options.disableInput || cm.state.keySeq) + { return false } + + var text = input.value + // If nothing changed, bail. + if (text == prevInput && !cm.somethingSelected()) { return false } + // Work around nonsensical selection resetting in IE9/10, and + // inexplicable appearance of private area unicode characters on + // some key combos in Mac (#2689). + if (ie && ie_version >= 9 && this.hasSelection === text || + mac && /[\uf700-\uf7ff]/.test(text)) { + cm.display.input.reset() + return false + } + + if (cm.doc.sel == cm.display.selForContextMenu) { + var first = text.charCodeAt(0) + if (first == 0x200b && !prevInput) { prevInput = "\u200b" } + if (first == 0x21da) { this.reset(); return this.cm.execCommand("undo") } + } + // Find the part of the input that is actually new + var same = 0, l = Math.min(prevInput.length, text.length) + while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) { ++same } + + runInOp(cm, function () { + applyTextInput(cm, text.slice(same), prevInput.length - same, + null, this$1.composing ? "*compose" : null) + + // Don't leave long text in the textarea, since it makes further polling slow + if (text.length > 1000 || text.indexOf("\n") > -1) { input.value = this$1.prevInput = "" } + else { this$1.prevInput = text } + + if (this$1.composing) { + this$1.composing.range.clear() + this$1.composing.range = cm.markText(this$1.composing.start, cm.getCursor("to"), + {className: "CodeMirror-composing"}) + } + }) + return true +}; + +TextareaInput.prototype.ensurePolled = function () { + if (this.pollingFast && this.poll()) { this.pollingFast = false } +}; + +TextareaInput.prototype.onKeyPress = function () { + if (ie && ie_version >= 9) { this.hasSelection = null } + this.fastPoll() +}; + +TextareaInput.prototype.onContextMenu = function (e) { + var input = this, cm = input.cm, display = cm.display, te = input.textarea + var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop + if (!pos || presto) { return } // Opera is difficult. + + // Reset the current text selection only if the click is done outside of the selection + // and 'resetSelectionOnContextMenu' option is true. + var reset = cm.options.resetSelectionOnContextMenu + if (reset && cm.doc.sel.contains(pos) == -1) + { operation(cm, setSelection)(cm.doc, simpleSelection(pos), sel_dontScroll) } + + var oldCSS = te.style.cssText, oldWrapperCSS = input.wrapper.style.cssText + input.wrapper.style.cssText = "position: absolute" + var wrapperBox = input.wrapper.getBoundingClientRect() + te.style.cssText = "position: absolute; width: 30px; height: 30px;\n top: " + (e.clientY - wrapperBox.top - 5) + "px; left: " + (e.clientX - wrapperBox.left - 5) + "px;\n z-index: 1000; background: " + (ie ? "rgba(255, 255, 255, .05)" : "transparent") + ";\n outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);" + var oldScrollY + if (webkit) { oldScrollY = window.scrollY } // Work around Chrome issue (#2712) + display.input.focus() + if (webkit) { window.scrollTo(null, oldScrollY) } + display.input.reset() + // Adds "Select all" to context menu in FF + if (!cm.somethingSelected()) { te.value = input.prevInput = " " } + input.contextMenuPending = true + display.selForContextMenu = cm.doc.sel + clearTimeout(display.detectingSelectAll) + + // Select-all will be greyed out if there's nothing to select, so + // this adds a zero-width space so that we can later check whether + // it got selected. + function prepareSelectAllHack() { + if (te.selectionStart != null) { + var selected = cm.somethingSelected() + var extval = "\u200b" + (selected ? te.value : "") + te.value = "\u21da" // Used to catch context-menu undo + te.value = extval + input.prevInput = selected ? "" : "\u200b" + te.selectionStart = 1; te.selectionEnd = extval.length + // Re-set this, in case some other handler touched the + // selection in the meantime. + display.selForContextMenu = cm.doc.sel + } + } + function rehide() { + input.contextMenuPending = false + input.wrapper.style.cssText = oldWrapperCSS + te.style.cssText = oldCSS + if (ie && ie_version < 9) { display.scrollbars.setScrollTop(display.scroller.scrollTop = scrollPos) } + + // Try to detect the user choosing select-all + if (te.selectionStart != null) { + if (!ie || (ie && ie_version < 9)) { prepareSelectAllHack() } + var i = 0, poll = function () { + if (display.selForContextMenu == cm.doc.sel && te.selectionStart == 0 && + te.selectionEnd > 0 && input.prevInput == "\u200b") + { operation(cm, selectAll)(cm) } + else if (i++ < 10) { display.detectingSelectAll = setTimeout(poll, 500) } + else { display.input.reset() } + } + display.detectingSelectAll = setTimeout(poll, 200) + } + } + + if (ie && ie_version >= 9) { prepareSelectAllHack() } + if (captureRightClick) { + e_stop(e) + var mouseup = function () { + off(window, "mouseup", mouseup) + setTimeout(rehide, 20) + } + on(window, "mouseup", mouseup) + } else { + setTimeout(rehide, 50) + } +}; + +TextareaInput.prototype.readOnlyChanged = function (val) { + if (!val) { this.reset() } +}; + +TextareaInput.prototype.setUneditable = function () {}; + +TextareaInput.prototype.needsContentAttribute = false + +function fromTextArea(textarea, options) { + options = options ? copyObj(options) : {} + options.value = textarea.value + if (!options.tabindex && textarea.tabIndex) + { options.tabindex = textarea.tabIndex } + if (!options.placeholder && textarea.placeholder) + { options.placeholder = textarea.placeholder } + // Set autofocus to true if this textarea is focused, or if it has + // autofocus and no other element is focused. + if (options.autofocus == null) { + var hasFocus = activeElt() + options.autofocus = hasFocus == textarea || + textarea.getAttribute("autofocus") != null && hasFocus == document.body + } + + function save() {textarea.value = cm.getValue()} + + var realSubmit + if (textarea.form) { + on(textarea.form, "submit", save) + // Deplorable hack to make the submit method do the right thing. + if (!options.leaveSubmitMethodAlone) { + var form = textarea.form + realSubmit = form.submit + try { + var wrappedSubmit = form.submit = function () { + save() + form.submit = realSubmit + form.submit() + form.submit = wrappedSubmit + } + } catch(e) {} + } + } + + options.finishInit = function (cm) { + cm.save = save + cm.getTextArea = function () { return textarea; } + cm.toTextArea = function () { + cm.toTextArea = isNaN // Prevent this from being ran twice + save() + textarea.parentNode.removeChild(cm.getWrapperElement()) + textarea.style.display = "" + if (textarea.form) { + off(textarea.form, "submit", save) + if (typeof textarea.form.submit == "function") + { textarea.form.submit = realSubmit } + } + } + } + + textarea.style.display = "none" + var cm = CodeMirror(function (node) { return textarea.parentNode.insertBefore(node, textarea.nextSibling); }, + options) + return cm +} + +function addLegacyProps(CodeMirror) { + CodeMirror.off = off + CodeMirror.on = on + CodeMirror.wheelEventPixels = wheelEventPixels + CodeMirror.Doc = Doc + CodeMirror.splitLines = splitLinesAuto + CodeMirror.countColumn = countColumn + CodeMirror.findColumn = findColumn + CodeMirror.isWordChar = isWordCharBasic + CodeMirror.Pass = Pass + CodeMirror.signal = signal + CodeMirror.Line = Line + CodeMirror.changeEnd = changeEnd + CodeMirror.scrollbarModel = scrollbarModel + CodeMirror.Pos = Pos + CodeMirror.cmpPos = cmp + CodeMirror.modes = modes + CodeMirror.mimeModes = mimeModes + CodeMirror.resolveMode = resolveMode + CodeMirror.getMode = getMode + CodeMirror.modeExtensions = modeExtensions + CodeMirror.extendMode = extendMode + CodeMirror.copyState = copyState + CodeMirror.startState = startState + CodeMirror.innerMode = innerMode + CodeMirror.commands = commands + CodeMirror.keyMap = keyMap + CodeMirror.keyName = keyName + CodeMirror.isModifierKey = isModifierKey + CodeMirror.lookupKey = lookupKey + CodeMirror.normalizeKeyMap = normalizeKeyMap + CodeMirror.StringStream = StringStream + CodeMirror.SharedTextMarker = SharedTextMarker + CodeMirror.TextMarker = TextMarker + CodeMirror.LineWidget = LineWidget + CodeMirror.e_preventDefault = e_preventDefault + CodeMirror.e_stopPropagation = e_stopPropagation + CodeMirror.e_stop = e_stop + CodeMirror.addClass = addClass + CodeMirror.contains = contains + CodeMirror.rmClass = rmClass + CodeMirror.keyNames = keyNames +} + +// EDITOR CONSTRUCTOR + +defineOptions(CodeMirror) + +addEditorMethods(CodeMirror) + +// Set up methods on CodeMirror's prototype to redirect to the editor's document. +var dontDelegate = "iter insert remove copy getEditor constructor".split(" ") +for (var prop in Doc.prototype) { if (Doc.prototype.hasOwnProperty(prop) && indexOf(dontDelegate, prop) < 0) + { CodeMirror.prototype[prop] = (function(method) { + return function() {return method.apply(this.doc, arguments)} + })(Doc.prototype[prop]) } } + +eventMixin(Doc) + +// INPUT HANDLING + +CodeMirror.inputStyles = {"textarea": TextareaInput, "contenteditable": ContentEditableInput} + +// MODE DEFINITION AND QUERYING + +// Extra arguments are stored as the mode's dependencies, which is +// used by (legacy) mechanisms like loadmode.js to automatically +// load a mode. (Preferred mechanism is the require/define calls.) +CodeMirror.defineMode = function(name/*, mode, …*/) { + if (!CodeMirror.defaults.mode && name != "null") { CodeMirror.defaults.mode = name } + defineMode.apply(this, arguments) +} + +CodeMirror.defineMIME = defineMIME + +// Minimal default mode. +CodeMirror.defineMode("null", function () { return ({token: function (stream) { return stream.skipToEnd(); }}); }) +CodeMirror.defineMIME("text/plain", "null") + +// EXTENSIONS + +CodeMirror.defineExtension = function (name, func) { + CodeMirror.prototype[name] = func +} +CodeMirror.defineDocExtension = function (name, func) { + Doc.prototype[name] = func +} + +CodeMirror.fromTextArea = fromTextArea + +addLegacyProps(CodeMirror) + +CodeMirror.version = "5.23.0" + +return CodeMirror; + +}))); \ No newline at end of file diff --git a/resources/javascript/effects.core.js b/resources/js/vendor/effects.core.js similarity index 96% rename from resources/javascript/effects.core.js rename to resources/js/vendor/effects.core.js index ed4fd37741..267a7780c3 100644 --- a/resources/javascript/effects.core.js +++ b/resources/js/vendor/effects.core.js @@ -4,7 +4,7 @@ * Copyright (c) 2008 Aaron Eisenberger (aaronchi@gmail.com) * Dual licensed under the MIT (MIT-LICENSE.txt) * and GPL (GPL-LICENSE.txt) licenses. - * + * * http://docs.jquery.com/UI/Effects/ */ ;(function($) { @@ -299,7 +299,7 @@ var colors = { yellow:[255,255,0], transparent: [255,255,255] }; - + /* * jQuery Easing v1.3 - http://gsgd.co.uk/sandbox/jquery/easing/ * @@ -307,33 +307,33 @@ var colors = { * to offer multiple easing options * * TERMS OF USE - jQuery Easing - * - * Open source under the BSD License. - * + * + * Open source under the BSD License. + * * Copyright © 2008 George McGinley Smith * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, + * + * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of + * + * Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, this list - * of conditions and the following disclaimer in the documentation and/or other materials + * Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. - * - * Neither the name of the author nor the names of contributors may be used to endorse + * + * Neither the name of the author nor the names of contributors may be used to endorse * or promote products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED - * OF THE POSSIBILITY OF SUCH DAMAGE. + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. * */ @@ -449,7 +449,7 @@ jQuery.extend( jQuery.easing, return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b; }, easeInOutBack: function (x, t, b, c, d, s) { - if (s == undefined) s = 1.70158; + if (s == undefined) s = 1.70158; if ((t/=d/2) < 1) return c/2*(t*t*(((s*=(1.525))+1)*t - s)) + b; return c/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2) + b; }, @@ -476,34 +476,34 @@ jQuery.extend( jQuery.easing, /* * * TERMS OF USE - EASING EQUATIONS - * - * Open source under the BSD License. - * + * + * Open source under the BSD License. + * * Copyright © 2001 Robert Penner * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, + * + * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of + * + * Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, this list - * of conditions and the following disclaimer in the documentation and/or other materials + * Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. - * - * Neither the name of the author nor the names of contributors may be used to endorse + * + * Neither the name of the author nor the names of contributors may be used to endorse * or promote products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED - * OF THE POSSIBILITY OF SUCH DAMAGE. + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. * */ -})(jQuery); \ No newline at end of file +})(jQuery); diff --git a/resources/javascript/effects.highlight.js b/resources/js/vendor/effects.highlight.js similarity index 100% rename from resources/javascript/effects.highlight.js rename to resources/js/vendor/effects.highlight.js diff --git a/resources/js/vendor/jekyll.search.min.js b/resources/js/vendor/jekyll.search.min.js new file mode 100644 index 0000000000..741a70f1f8 --- /dev/null +++ b/resources/js/vendor/jekyll.search.min.js @@ -0,0 +1 @@ +(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;otlen){return false}if(qlen===tlen){return needle===haystack}outer:for(var i=0,j=0;i=0}}},{}],7:[function(require,module,exports){"use strict";module.exports={compile:compile,setOptions:setOptions};var options={};options.pattern=/\{(.*?)\}/g;options.template="";options.middleware=function(){};function setOptions(_options){options.pattern=_options.pattern||options.pattern;options.template=_options.template||options.template;if(typeof _options.middleware==="function"){options.middleware=_options.middleware}}function compile(data){return options.template.replace(options.pattern,function(match,prop){var value=options.middleware(prop,data[prop],options.template);if(value!==undefined){return value}return data[prop]||match})}},{}],8:[function(require,module,exports){(function(window,document,undefined){"use strict";var options={searchInput:null,resultsContainer:null,json:[],searchResultTemplate:'
      • {title}
      • ',templateMiddleware:function(){},noResultsText:"No results found",limit:99,fuzzy:false,exclude:[]};var requiredOptions=["searchInput","resultsContainer","json"];var templater=require("./Templater");var repository=require("./Repository");var jsonLoader=require("./JSONLoader");var optionsValidator=require("./OptionsValidator")({required:requiredOptions});var utils=require("./utils");window.SimpleJekyllSearch=function SimpleJekyllSearch(_options){var errors=optionsValidator.validate(_options);if(errors.length>0){throwError("You must specify the following required options: "+requiredOptions)}options=utils.merge(options,_options);templater.setOptions({template:options.searchResultTemplate,middleware:options.templateMiddleware});repository.setOptions({fuzzy:options.fuzzy,limit:options.limit});if(utils.isJSON(options.json)){initWithJSON(options.json)}else{initWithURL(options.json)}};window.SimpleJekyllSearch.init=window.SimpleJekyllSearch;if(typeof window.SimpleJekyllSearchInit==="function"){window.SimpleJekyllSearchInit.call(this,window.SimpleJekyllSearch)}function initWithJSON(json){repository.put(json);registerInput()}function initWithURL(url){jsonLoader.load(url,function(err,json){if(err){throwError("failed to get JSON ("+url+")")}initWithJSON(json)})}function emptyResultsContainer(){options.resultsContainer.innerHTML=""}function appendToResultsContainer(text){options.resultsContainer.innerHTML+=text}function registerInput(){options.searchInput.addEventListener("keyup",function(e){emptyResultsContainer();var key=e.which;var query=e.target.value;if(isWhitelistedKey(key)&&isValidQuery(query)){render(repository.search(query))}})}function render(results){if(results.length===0){return appendToResultsContainer(options.noResultsText)}for(var i=0;i0}function isWhitelistedKey(key){return[13,16,20,37,38,39,40,91].indexOf(key)===-1}function throwError(message){throw new Error("SimpleJekyllSearch --- "+message)}})(window,document)},{"./JSONLoader":2,"./OptionsValidator":3,"./Repository":4,"./Templater":7,"./utils":9}],9:[function(require,module,exports){"use strict";module.exports={merge:merge,isJSON:isJSON};function merge(defaultParams,mergeParams){var mergedOptions={};for(var option in defaultParams){mergedOptions[option]=defaultParams[option];if(mergeParams[option]!==undefined){mergedOptions[option]=mergeParams[option]}}return mergedOptions}function isJSON(json){try{if(json instanceof Object&&JSON.parse(JSON.stringify(json))){return true}return false}catch(e){return false}}},{}]},{},[8]); diff --git a/resources/js/vendor/jquery.autocomplete.js b/resources/js/vendor/jquery.autocomplete.js new file mode 100644 index 0000000000..78a1593944 --- /dev/null +++ b/resources/js/vendor/jquery.autocomplete.js @@ -0,0 +1,992 @@ +/** +* Ajax Autocomplete for jQuery, version %version% +* (c) 2015 Tomas Kirda +* +* Ajax Autocomplete for jQuery is freely distributable under the terms of an MIT-style license. +* For details, see the web site: https://github.com/devbridge/jQuery-Autocomplete +*/ + +/*jslint browser: true, white: true, single: true, this: true, multivar: true */ +/*global define, window, document, jQuery, exports, require */ + +// Expose plugin as an AMD module if AMD loader is present: +(function (factory) { + "use strict"; + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['jquery'], factory); + } else if (typeof exports === 'object' && typeof require === 'function') { + // Browserify + factory(require('jquery')); + } else { + // Browser globals + factory(jQuery); + } +}(function ($) { + 'use strict'; + + var + utils = (function () { + return { + escapeRegExChars: function (value) { + return value.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&"); + }, + createNode: function (containerClass) { + var div = document.createElement('div'); + div.className = containerClass; + div.style.position = 'absolute'; + div.style.display = 'none'; + return div; + } + }; + }()), + + keys = { + ESC: 27, + TAB: 9, + RETURN: 13, + LEFT: 37, + UP: 38, + RIGHT: 39, + DOWN: 40 + }; + + function Autocomplete(el, options) { + var noop = $.noop, + that = this, + defaults = { + ajaxSettings: {}, + autoSelectFirst: false, + appendTo: document.body, + serviceUrl: null, + lookup: null, + onSelect: null, + width: 'auto', + minChars: 1, + maxHeight: 300, + deferRequestBy: 0, + params: {}, + formatResult: Autocomplete.formatResult, + delimiter: null, + zIndex: 9999, + type: 'GET', + noCache: false, + onSearchStart: noop, + onSearchComplete: noop, + onSearchError: noop, + preserveInput: false, + containerClass: 'autocomplete-suggestions', + tabDisabled: false, + dataType: 'text', + currentRequest: null, + triggerSelectOnValidInput: true, + preventBadQueries: true, + lookupFilter: function (suggestion, originalQuery, queryLowerCase) { + return suggestion.value.toLowerCase().indexOf(queryLowerCase) !== -1; + }, + paramName: 'query', + transformResult: function (response) { + return typeof response === 'string' ? $.parseJSON(response) : response; + }, + showNoSuggestionNotice: false, + noSuggestionNotice: 'No results', + orientation: 'bottom', + forceFixPosition: false + }; + + // Shared variables: + that.element = el; + that.el = $(el); + that.suggestions = []; + that.badQueries = []; + that.selectedIndex = -1; + that.currentValue = that.element.value; + that.intervalId = 0; + that.cachedResponse = {}; + that.onChangeInterval = null; + that.onChange = null; + that.isLocal = false; + that.suggestionsContainer = null; + that.noSuggestionsContainer = null; + that.options = $.extend({}, defaults, options); + that.classes = { + selected: 'autocomplete-selected', + suggestion: 'autocomplete-suggestion' + }; + that.hint = null; + that.hintValue = ''; + that.selection = null; + + // Initialize and set options: + that.initialize(); + that.setOptions(options); + } + + Autocomplete.utils = utils; + + $.Autocomplete = Autocomplete; + + Autocomplete.formatResult = function (suggestion, currentValue) { + // Do not replace anything if there current value is empty + if (!currentValue) { + return suggestion.value; + } + + var pattern = '(' + utils.escapeRegExChars(currentValue) + ')'; + + return suggestion.value + .replace(new RegExp(pattern, 'gi'), '$1<\/strong>') + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/<(\/?strong)>/g, '<$1>'); + }; + + Autocomplete.prototype = { + + killerFn: null, + + initialize: function () { + var that = this, + suggestionSelector = '.' + that.classes.suggestion, + selected = that.classes.selected, + options = that.options, + container; + + // Remove autocomplete attribute to prevent native suggestions: + that.element.setAttribute('autocomplete', 'off'); + + that.killerFn = function (e) { + if (!$(e.target).closest('.' + that.options.containerClass).length) { + that.killSuggestions(); + that.disableKillerFn(); + } + }; + + // html() deals with many types: htmlString or Element or Array or jQuery + that.noSuggestionsContainer = $('
        ') + .html(this.options.noSuggestionNotice).get(0); + + that.suggestionsContainer = Autocomplete.utils.createNode(options.containerClass); + + container = $(that.suggestionsContainer); + + container.appendTo(options.appendTo); + + // Only set width if it was provided: + if (options.width !== 'auto') { + container.css('width', options.width); + } + + // Listen for mouse over event on suggestions list: + container.on('mouseover.autocomplete', suggestionSelector, function () { + that.activate($(this).data('index')); + }); + + // Deselect active element when mouse leaves suggestions container: + container.on('mouseout.autocomplete', function () { + that.selectedIndex = -1; + container.children('.' + selected).removeClass(selected); + }); + + // Listen for click event on suggestions list: + container.on('click.autocomplete', suggestionSelector, function () { + that.select($(this).data('index')); + return false; + }); + + that.fixPositionCapture = function () { + if (that.visible) { + that.fixPosition(); + } + }; + + $(window).on('resize.autocomplete', that.fixPositionCapture); + + that.el.on('keydown.autocomplete', function (e) { that.onKeyPress(e); }); + that.el.on('keyup.autocomplete', function (e) { that.onKeyUp(e); }); + that.el.on('blur.autocomplete', function () { that.onBlur(); }); + that.el.on('focus.autocomplete', function () { that.onFocus(); }); + that.el.on('change.autocomplete', function (e) { that.onKeyUp(e); }); + that.el.on('input.autocomplete', function (e) { that.onKeyUp(e); }); + }, + + onFocus: function () { + var that = this; + + that.fixPosition(); + + if (that.el.val().length >= that.options.minChars) { + that.onValueChange(); + } + }, + + onBlur: function () { + this.enableKillerFn(); + }, + + abortAjax: function () { + var that = this; + if (that.currentRequest) { + that.currentRequest.abort(); + that.currentRequest = null; + } + }, + + setOptions: function (suppliedOptions) { + var that = this, + options = that.options; + + $.extend(options, suppliedOptions); + + that.isLocal = $.isArray(options.lookup); + + if (that.isLocal) { + options.lookup = that.verifySuggestionsFormat(options.lookup); + } + + options.orientation = that.validateOrientation(options.orientation, 'bottom'); + + // Adjust height, width and z-index: + $(that.suggestionsContainer).css({ + 'max-height': options.maxHeight + 'px', + 'width': options.width + 'px', + 'z-index': options.zIndex + }); + }, + + + clearCache: function () { + this.cachedResponse = {}; + this.badQueries = []; + }, + + clear: function () { + this.clearCache(); + this.currentValue = ''; + this.suggestions = []; + }, + + disable: function () { + var that = this; + that.disabled = true; + clearInterval(that.onChangeInterval); + that.abortAjax(); + }, + + enable: function () { + this.disabled = false; + }, + + fixPosition: function () { + // Use only when container has already its content + + var that = this, + $container = $(that.suggestionsContainer), + containerParent = $container.parent().get(0); + // Fix position automatically when appended to body. + // In other cases force parameter must be given. + if (containerParent !== document.body && !that.options.forceFixPosition) { + return; + } + + // Choose orientation + var orientation = that.options.orientation, + containerHeight = $container.outerHeight(), + height = that.el.outerHeight(), + offset = that.el.offset(), + styles = { 'top': offset.top, 'left': offset.left }; + + if (orientation === 'auto') { + var viewPortHeight = $(window).height(), + scrollTop = $(window).scrollTop(), + topOverflow = -scrollTop + offset.top - containerHeight, + bottomOverflow = scrollTop + viewPortHeight - (offset.top + height + containerHeight); + + orientation = (Math.max(topOverflow, bottomOverflow) === topOverflow) ? 'top' : 'bottom'; + } + + if (orientation === 'top') { + styles.top += -containerHeight; + } else { + styles.top += height; + } + + // If container is not positioned to body, + // correct its position using offset parent offset + if(containerParent !== document.body) { + var opacity = $container.css('opacity'), + parentOffsetDiff; + + if (!that.visible){ + $container.css('opacity', 0).show(); + } + + parentOffsetDiff = $container.offsetParent().offset(); + styles.top -= parentOffsetDiff.top; + styles.left -= parentOffsetDiff.left; + + if (!that.visible){ + $container.css('opacity', opacity).hide(); + } + } + + if (that.options.width === 'auto') { + styles.width = that.el.outerWidth() + 'px'; + } + + $container.css(styles); + }, + + enableKillerFn: function () { + var that = this; + $(document).on('click.autocomplete', that.killerFn); + }, + + disableKillerFn: function () { + var that = this; + $(document).off('click.autocomplete', that.killerFn); + }, + + killSuggestions: function () { + var that = this; + that.stopKillSuggestions(); + that.intervalId = window.setInterval(function () { + if (that.visible) { + // No need to restore value when + // preserveInput === true, + // because we did not change it + if (!that.options.preserveInput) { + that.el.val(that.currentValue); + } + + that.hide(); + } + + that.stopKillSuggestions(); + }, 50); + }, + + stopKillSuggestions: function () { + window.clearInterval(this.intervalId); + }, + + isCursorAtEnd: function () { + var that = this, + valLength = that.el.val().length, + selectionStart = that.element.selectionStart, + range; + + if (typeof selectionStart === 'number') { + return selectionStart === valLength; + } + if (document.selection) { + range = document.selection.createRange(); + range.moveStart('character', -valLength); + return valLength === range.text.length; + } + return true; + }, + + onKeyPress: function (e) { + var that = this; + + // If suggestions are hidden and user presses arrow down, display suggestions: + if (!that.disabled && !that.visible && e.which === keys.DOWN && that.currentValue) { + that.suggest(); + return; + } + + if (that.disabled || !that.visible) { + return; + } + + switch (e.which) { + case keys.ESC: + that.el.val(that.currentValue); + that.hide(); + break; + case keys.RIGHT: + if (that.hint && that.options.onHint && that.isCursorAtEnd()) { + that.selectHint(); + break; + } + return; + case keys.TAB: + if (that.hint && that.options.onHint) { + that.selectHint(); + return; + } + if (that.selectedIndex === -1) { + that.hide(); + return; + } + that.select(that.selectedIndex); + if (that.options.tabDisabled === false) { + return; + } + break; + case keys.RETURN: + if (that.selectedIndex === -1) { + that.hide(); + return; + } + that.select(that.selectedIndex); + break; + case keys.UP: + that.moveUp(); + break; + case keys.DOWN: + that.moveDown(); + break; + default: + return; + } + + // Cancel event if function did not return: + e.stopImmediatePropagation(); + e.preventDefault(); + }, + + onKeyUp: function (e) { + var that = this; + + if (that.disabled) { + return; + } + + switch (e.which) { + case keys.UP: + case keys.DOWN: + return; + } + + clearInterval(that.onChangeInterval); + + if (that.currentValue !== that.el.val()) { + that.findBestHint(); + if (that.options.deferRequestBy > 0) { + // Defer lookup in case when value changes very quickly: + that.onChangeInterval = setInterval(function () { + that.onValueChange(); + }, that.options.deferRequestBy); + } else { + that.onValueChange(); + } + } + }, + + onValueChange: function () { + var that = this, + options = that.options, + value = that.el.val(), + query = that.getQuery(value); + + if (that.selection && that.currentValue !== query) { + that.selection = null; + (options.onInvalidateSelection || $.noop).call(that.element); + } + + clearInterval(that.onChangeInterval); + that.currentValue = value; + that.selectedIndex = -1; + + // Check existing suggestion for the match before proceeding: + if (options.triggerSelectOnValidInput && that.isExactMatch(query)) { + that.select(0); + return; + } + + if (query.length < options.minChars) { + that.hide(); + } else { + that.getSuggestions(query); + } + }, + + isExactMatch: function (query) { + var suggestions = this.suggestions; + + return (suggestions.length === 1 && suggestions[0].value.toLowerCase() === query.toLowerCase()); + }, + + getQuery: function (value) { + var delimiter = this.options.delimiter, + parts; + + if (!delimiter) { + return value; + } + parts = value.split(delimiter); + return $.trim(parts[parts.length - 1]); + }, + + getSuggestionsLocal: function (query) { + var that = this, + options = that.options, + queryLowerCase = query.toLowerCase(), + filter = options.lookupFilter, + limit = parseInt(options.lookupLimit, 10), + data; + + data = { + suggestions: $.grep(options.lookup, function (suggestion) { + return filter(suggestion, query, queryLowerCase); + }) + }; + + if (limit && data.suggestions.length > limit) { + data.suggestions = data.suggestions.slice(0, limit); + } + + return data; + }, + + getSuggestions: function (q) { + var response, + that = this, + options = that.options, + serviceUrl = options.serviceUrl, + params, + cacheKey, + ajaxSettings; + + options.params[options.paramName] = q; + params = options.ignoreParams ? null : options.params; + + if (options.onSearchStart.call(that.element, options.params) === false) { + return; + } + + if ($.isFunction(options.lookup)){ + options.lookup(q, function (data) { + that.suggestions = data.suggestions; + that.suggest(); + options.onSearchComplete.call(that.element, q, data.suggestions); + }); + return; + } + + if (that.isLocal) { + response = that.getSuggestionsLocal(q); + } else { + if ($.isFunction(serviceUrl)) { + serviceUrl = serviceUrl.call(that.element, q); + } + cacheKey = serviceUrl + '?' + $.param(params || {}); + response = that.cachedResponse[cacheKey]; + } + + if (response && $.isArray(response.suggestions)) { + that.suggestions = response.suggestions; + that.suggest(); + options.onSearchComplete.call(that.element, q, response.suggestions); + } else if (!that.isBadQuery(q)) { + that.abortAjax(); + + ajaxSettings = { + url: serviceUrl, + data: params, + type: options.type, + dataType: options.dataType + }; + + $.extend(ajaxSettings, options.ajaxSettings); + + that.currentRequest = $.ajax(ajaxSettings).done(function (data) { + var result; + that.currentRequest = null; + result = options.transformResult(data, q); + that.processResponse(result, q, cacheKey); + options.onSearchComplete.call(that.element, q, result.suggestions); + }).fail(function (jqXHR, textStatus, errorThrown) { + options.onSearchError.call(that.element, q, jqXHR, textStatus, errorThrown); + }); + } else { + options.onSearchComplete.call(that.element, q, []); + } + }, + + isBadQuery: function (q) { + if (!this.options.preventBadQueries){ + return false; + } + + var badQueries = this.badQueries, + i = badQueries.length; + + while (i--) { + if (q.indexOf(badQueries[i]) === 0) { + return true; + } + } + + return false; + }, + + hide: function () { + var that = this, + container = $(that.suggestionsContainer); + + if ($.isFunction(that.options.onHide) && that.visible) { + that.options.onHide.call(that.element, container); + } + + that.visible = false; + that.selectedIndex = -1; + clearInterval(that.onChangeInterval); + $(that.suggestionsContainer).hide(); + that.signalHint(null); + }, + + suggest: function () { + if (!this.suggestions.length) { + if (this.options.showNoSuggestionNotice) { + this.noSuggestions(); + } else { + this.hide(); + } + return; + } + + var that = this, + options = that.options, + groupBy = options.groupBy, + formatResult = options.formatResult, + value = that.getQuery(that.currentValue), + className = that.classes.suggestion, + classSelected = that.classes.selected, + container = $(that.suggestionsContainer), + noSuggestionsContainer = $(that.noSuggestionsContainer), + beforeRender = options.beforeRender, + html = '', + category, + formatGroup = function (suggestion, index) { + var currentCategory = suggestion.data[groupBy]; + + if (category === currentCategory){ + return ''; + } + + category = currentCategory; + + return '
        ' + category + '
        '; + }; + + if (options.triggerSelectOnValidInput && that.isExactMatch(value)) { + that.select(0); + return; + } + + // Build suggestions inner HTML: + $.each(that.suggestions, function (i, suggestion) { + if (groupBy){ + html += formatGroup(suggestion, value, i); + } + + html += '
        ' + formatResult(suggestion, value, i) + '
        '; + }); + + this.adjustContainerWidth(); + + noSuggestionsContainer.detach(); + container.html(html); + + if ($.isFunction(beforeRender)) { + beforeRender.call(that.element, container, that.suggestions); + } + + that.fixPosition(); + container.show(); + + // Select first value by default: + if (options.autoSelectFirst) { + that.selectedIndex = 0; + container.scrollTop(0); + container.children('.' + className).first().addClass(classSelected); + } + + that.visible = true; + that.findBestHint(); + }, + + noSuggestions: function() { + var that = this, + container = $(that.suggestionsContainer), + noSuggestionsContainer = $(that.noSuggestionsContainer); + + this.adjustContainerWidth(); + + // Some explicit steps. Be careful here as it easy to get + // noSuggestionsContainer removed from DOM if not detached properly. + noSuggestionsContainer.detach(); + container.empty(); // clean suggestions if any + container.append(noSuggestionsContainer); + + that.fixPosition(); + + container.show(); + that.visible = true; + }, + + adjustContainerWidth: function() { + var that = this, + options = that.options, + width, + container = $(that.suggestionsContainer); + + // If width is auto, adjust width before displaying suggestions, + // because if instance was created before input had width, it will be zero. + // Also it adjusts if input width has changed. + if (options.width === 'auto') { + width = that.el.outerWidth(); + container.css('width', width > 0 ? width : 300); + } + }, + + findBestHint: function () { + var that = this, + value = that.el.val().toLowerCase(), + bestMatch = null; + + if (!value) { + return; + } + + $.each(that.suggestions, function (i, suggestion) { + var foundMatch = suggestion.value.toLowerCase().indexOf(value) === 0; + if (foundMatch) { + bestMatch = suggestion; + } + return !foundMatch; + }); + + that.signalHint(bestMatch); + }, + + signalHint: function (suggestion) { + var hintValue = '', + that = this; + if (suggestion) { + hintValue = that.currentValue + suggestion.value.substr(that.currentValue.length); + } + if (that.hintValue !== hintValue) { + that.hintValue = hintValue; + that.hint = suggestion; + (this.options.onHint || $.noop)(hintValue); + } + }, + + verifySuggestionsFormat: function (suggestions) { + // If suggestions is string array, convert them to supported format: + if (suggestions.length && typeof suggestions[0] === 'string') { + return $.map(suggestions, function (value) { + return { value: value, data: null }; + }); + } + + return suggestions; + }, + + validateOrientation: function(orientation, fallback) { + orientation = $.trim(orientation || '').toLowerCase(); + + if($.inArray(orientation, ['auto', 'bottom', 'top']) === -1){ + orientation = fallback; + } + + return orientation; + }, + + processResponse: function (result, originalQuery, cacheKey) { + var that = this, + options = that.options; + + result.suggestions = that.verifySuggestionsFormat(result.suggestions); + + // Cache results if cache is not disabled: + if (!options.noCache) { + that.cachedResponse[cacheKey] = result; + if (options.preventBadQueries && !result.suggestions.length) { + that.badQueries.push(originalQuery); + } + } + + // Return if originalQuery is not matching current query: + if (originalQuery !== that.getQuery(that.currentValue)) { + return; + } + + that.suggestions = result.suggestions; + that.suggest(); + }, + + activate: function (index) { + var that = this, + activeItem, + selected = that.classes.selected, + container = $(that.suggestionsContainer), + children = container.find('.' + that.classes.suggestion); + + container.find('.' + selected).removeClass(selected); + + that.selectedIndex = index; + + if (that.selectedIndex !== -1 && children.length > that.selectedIndex) { + activeItem = children.get(that.selectedIndex); + $(activeItem).addClass(selected); + return activeItem; + } + + return null; + }, + + selectHint: function () { + var that = this, + i = $.inArray(that.hint, that.suggestions); + + that.select(i); + }, + + select: function (i) { + var that = this; + that.hide(); + that.onSelect(i); + that.disableKillerFn(); + }, + + moveUp: function () { + var that = this; + + if (that.selectedIndex === -1) { + return; + } + + if (that.selectedIndex === 0) { + $(that.suggestionsContainer).children().first().removeClass(that.classes.selected); + that.selectedIndex = -1; + that.el.val(that.currentValue); + that.findBestHint(); + return; + } + + that.adjustScroll(that.selectedIndex - 1); + }, + + moveDown: function () { + var that = this; + + if (that.selectedIndex === (that.suggestions.length - 1)) { + return; + } + + that.adjustScroll(that.selectedIndex + 1); + }, + + adjustScroll: function (index) { + var that = this, + activeItem = that.activate(index); + + if (!activeItem) { + return; + } + + var offsetTop, + upperBound, + lowerBound, + heightDelta = $(activeItem).outerHeight(); + + offsetTop = activeItem.offsetTop; + upperBound = $(that.suggestionsContainer).scrollTop(); + lowerBound = upperBound + that.options.maxHeight - heightDelta; + + if (offsetTop < upperBound) { + $(that.suggestionsContainer).scrollTop(offsetTop); + } else if (offsetTop > lowerBound) { + $(that.suggestionsContainer).scrollTop(offsetTop - that.options.maxHeight + heightDelta); + } + + if (!that.options.preserveInput) { + that.el.val(that.getValue(that.suggestions[index].value)); + } + that.signalHint(null); + }, + + onSelect: function (index) { + var that = this, + onSelectCallback = that.options.onSelect, + suggestion = that.suggestions[index]; + + that.currentValue = that.getValue(suggestion.value); + + if (that.currentValue !== that.el.val() && !that.options.preserveInput) { + that.el.val(that.currentValue); + } + + that.signalHint(null); + that.suggestions = []; + that.selection = suggestion; + + if ($.isFunction(onSelectCallback)) { + onSelectCallback.call(that.element, suggestion); + } + }, + + getValue: function (value) { + var that = this, + delimiter = that.options.delimiter, + currentValue, + parts; + + if (!delimiter) { + return value; + } + + currentValue = that.currentValue; + parts = currentValue.split(delimiter); + + if (parts.length === 1) { + return value; + } + + return currentValue.substr(0, currentValue.length - parts[parts.length - 1].length) + value; + }, + + dispose: function () { + var that = this; + that.el.off('.autocomplete').removeData('autocomplete'); + that.disableKillerFn(); + $(window).off('resize.autocomplete', that.fixPositionCapture); + $(that.suggestionsContainer).remove(); + } + }; + + // Create chainable jQuery plugin: + $.fn.autocomplete = $.fn.devbridgeAutocomplete = function (options, args) { + var dataKey = 'autocomplete'; + // If function invoked without argument return + // instance of the first matched element: + if (!arguments.length) { + return this.first().data(dataKey); + } + + return this.each(function () { + var inputElement = $(this), + instance = inputElement.data(dataKey); + + if (typeof options === 'string') { + if (instance && typeof instance[options] === 'function') { + instance[options](args); + } + } else { + // If instance already exists, destroy it: + if (instance && instance.dispose) { + instance.dispose(); + } + instance = new Autocomplete(this, options); + inputElement.data(dataKey, instance); + } + }); + }; +})); diff --git a/resources/javascript/jquery.js b/resources/js/vendor/jquery.js similarity index 99% rename from resources/javascript/jquery.js rename to resources/js/vendor/jquery.js index 3684c36b54..bec0f5fbab 100644 --- a/resources/javascript/jquery.js +++ b/resources/js/vendor/jquery.js @@ -1,4 +1,4 @@ /*! jQuery v1.6.4 http://jquery.com/ | http://jquery.org/license */ -(function(a,b){function cu(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cr(a){if(!cg[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){ch||(ch=c.createElement("iframe"),ch.frameBorder=ch.width=ch.height=0),b.appendChild(ch);if(!ci||!ch.createElement)ci=(ch.contentWindow||ch.contentDocument).document,ci.write((c.compatMode==="CSS1Compat"?"":"")+""),ci.close();d=ci.createElement(a),ci.body.appendChild(d),e=f.css(d,"display"),b.removeChild(ch)}cg[a]=e}return cg[a]}function cq(a,b){var c={};f.each(cm.concat.apply([],cm.slice(0,b)),function(){c[this]=a});return c}function cp(){cn=b}function co(){setTimeout(cp,0);return cn=f.now()}function cf(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ce(){try{return new a.XMLHttpRequest}catch(b){}}function b$(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g0){c!=="border"&&f.each(e,function(){c||(d-=parseFloat(f.css(a,"padding"+this))||0),c==="margin"?d+=parseFloat(f.css(a,c+this))||0:d-=parseFloat(f.css(a,"border"+this+"Width"))||0});return d+"px"}d=bv(a,b,b);if(d<0||d==null)d=a.style[b]||0;d=parseFloat(d)||0,c&&f.each(e,function(){d+=parseFloat(f.css(a,"padding"+this))||0,c!=="padding"&&(d+=parseFloat(f.css(a,"border"+this+"Width"))||0),c==="margin"&&(d+=parseFloat(f.css(a,c+this))||0)});return d+"px"}function bl(a,b){b.src?f.ajax({url:b.src,async:!1,dataType:"script"}):f.globalEval((b.text||b.textContent||b.innerHTML||"").replace(bd,"/*$0*/")),b.parentNode&&b.parentNode.removeChild(b)}function bk(a){f.nodeName(a,"input")?bj(a):"getElementsByTagName"in a&&f.grep(a.getElementsByTagName("input"),bj)}function bj(a){if(a.type==="checkbox"||a.type==="radio")a.defaultChecked=a.checked}function bi(a){return"getElementsByTagName"in a?a.getElementsByTagName("*"):"querySelectorAll"in a?a.querySelectorAll("*"):[]}function bh(a,b){var c;if(b.nodeType===1){b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase();if(c==="object")b.outerHTML=a.outerHTML;else if(c!=="input"||a.type!=="checkbox"&&a.type!=="radio"){if(c==="option")b.selected=a.defaultSelected;else if(c==="input"||c==="textarea")b.defaultValue=a.defaultValue}else a.checked&&(b.defaultChecked=b.checked=a.checked),b.value!==a.value&&(b.value=a.value);b.removeAttribute(f.expando)}}function bg(a,b){if(b.nodeType===1&&!!f.hasData(a)){var c=f.expando,d=f.data(a),e=f.data(b,d);if(d=d[c]){var g=d.events;e=e[c]=f.extend({},d);if(g){delete e.handle,e.events={};for(var h in g)for(var i=0,j=g[h].length;i=0===c})}function U(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function M(a,b){return(a&&a!=="*"?a+".":"")+b.replace(y,"`").replace(z,"&")}function L(a){var b,c,d,e,g,h,i,j,k,l,m,n,o,p=[],q=[],r=f._data(this,"events");if(!(a.liveFired===this||!r||!r.live||a.target.disabled||a.button&&a.type==="click")){a.namespace&&(n=new RegExp("(^|\\.)"+a.namespace.split(".").join("\\.(?:.*\\.)?")+"(\\.|$)")),a.liveFired=this;var s=r.live.slice(0);for(i=0;ic)break;a.currentTarget=e.elem,a.data=e.handleObj.data,a.handleObj=e.handleObj,o=e.handleObj.origHandler.apply(e.elem,arguments);if(o===!1||a.isPropagationStopped()){c=e.level,o===!1&&(b=!1);if(a.isImmediatePropagationStopped())break}}return b}}function J(a,c,d){var e=f.extend({},d[0]);e.type=a,e.originalEvent={},e.liveFired=b,f.event.handle.call(c,e),e.isDefaultPrevented()&&d[0].preventDefault()}function D(){return!0}function C(){return!1}function m(a,c,d){var e=c+"defer",g=c+"queue",h=c+"mark",i=f.data(a,e,b,!0);i&&(d==="queue"||!f.data(a,g,b,!0))&&(d==="mark"||!f.data(a,h,b,!0))&&setTimeout(function(){!f.data(a,g,b,!0)&&!f.data(a,h,b,!0)&&(f.removeData(a,e,!0),i.resolve())},0)}function l(a){for(var b in a)if(b!=="toJSON")return!1;return!0}function k(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(j,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNaN(d)?i.test(d)?f.parseJSON(d):d:parseFloat(d)}catch(g){}f.data(a,c,d)}else d=b}return d}var c=a.document,d=a.navigator,e=a.location,f=function(){function K(){if(!e.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(K,1);return}e.ready()}}var e=function(a,b){return new e.fn.init(a,b,h)},f=a.jQuery,g=a.$,h,i=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/\d/,n=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,o=/^[\],:{}\s]*$/,p=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,q=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,r=/(?:^|:|,)(?:\s*\[)+/g,s=/(webkit)[ \/]([\w.]+)/,t=/(opera)(?:.*version)?[ \/]([\w.]+)/,u=/(msie) ([\w.]+)/,v=/(mozilla)(?:.*? rv:([\w.]+))?/,w=/-([a-z]|[0-9])/ig,x=/^-ms-/,y=function(a,b){return(b+"").toUpperCase()},z=d.userAgent,A,B,C,D=Object.prototype.toString,E=Object.prototype.hasOwnProperty,F=Array.prototype.push,G=Array.prototype.slice,H=String.prototype.trim,I=Array.prototype.indexOf,J={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=n.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.6.4",length:0,size:function(){return this.length},toArray:function(){return G.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?F.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),B.done(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(G.apply(this,arguments),"slice",G.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:F,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j0)return;B.resolveWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").unbind("ready")}},bindReady:function(){if(!B){B=e._Deferred();if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",C,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",C),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&K()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a&&typeof a=="object"&&"setInterval"in a},isNaN:function(a){return a==null||!m.test(a)||isNaN(a)},type:function(a){return a==null?String(a):J[D.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;try{if(a.constructor&&!E.call(a,"constructor")&&!E.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||E.call(a,d)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw a},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(o.test(b.replace(p,"@").replace(q,"]").replace(r,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(c){var d,f;try{a.DOMParser?(f=new DOMParser,d=f.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(g){d=b}(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&e.error("Invalid XML: "+c);return d},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(x,"ms-").replace(w,y)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i1?h.call(arguments,0):c,--e||g.resolveWith(g,h.call(b,0))}}var b=arguments,c=0,d=b.length,e=d,g=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred();if(d>1){for(;c
        a",d=a.getElementsByTagName("*"),e=a.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=a.getElementsByTagName("input")[0],k={leadingWhitespace:a.firstChild.nodeType===3,tbody:!a.getElementsByTagName("tbody").length,htmlSerialize:!!a.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55$/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:a.className!=="t",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0},i.checked=!0,k.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,k.optDisabled=!h.disabled;try{delete a.test}catch(v){k.deleteExpando=!1}!a.addEventListener&&a.attachEvent&&a.fireEvent&&(a.attachEvent("onclick",function(){k.noCloneEvent=!1}),a.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),k.radioValue=i.value==="t",i.setAttribute("checked","checked"),a.appendChild(i),l=c.createDocumentFragment(),l.appendChild(a.firstChild),k.checkClone=l.cloneNode(!0).cloneNode(!0).lastChild.checked,a.innerHTML="",a.style.width=a.style.paddingLeft="1px",m=c.getElementsByTagName("body")[0],o=c.createElement(m?"div":"body"),p={visibility:"hidden",width:0,height:0,border:0,margin:0,background:"none"},m&&f.extend(p,{position:"absolute",left:"-1000px",top:"-1000px"});for(t in p)o.style[t]=p[t];o.appendChild(a),n=m||b,n.insertBefore(o,n.firstChild),k.appendChecked=i.checked,k.boxModel=a.offsetWidth===2,"zoom"in a.style&&(a.style.display="inline",a.style.zoom=1,k.inlineBlockNeedsLayout=a.offsetWidth===2,a.style.display="",a.innerHTML="
        ",k.shrinkWrapBlocks=a.offsetWidth!==2),a.innerHTML="
        t
        ",q=a.getElementsByTagName("td"),u=q[0].offsetHeight===0,q[0].style.display="",q[1].style.display="none",k.reliableHiddenOffsets=u&&q[0].offsetHeight===0,a.innerHTML="",c.defaultView&&c.defaultView.getComputedStyle&&(j=c.createElement("div"),j.style.width="0",j.style.marginRight="0",a.appendChild(j),k.reliableMarginRight=(parseInt((c.defaultView.getComputedStyle(j,null)||{marginRight:0}).marginRight,10)||0)===0),o.innerHTML="",n.removeChild(o);if(a.attachEvent)for(t in{submit:1,change:1,focusin:1})s="on"+t,u=s in a,u||(a.setAttribute(s,"return;"),u=typeof a[s]=="function"),k[t+"Bubbles"]=u;o=l=g=h=m=j=a=i=null;return k}(),f.boxModel=f.support.boxModel;var i=/^(?:\{.*\}|\[.*\])$/,j=/([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!l(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g,h,i=f.expando,j=typeof c=="string",k=a.nodeType,l=k?f.cache:a,m=k?a[f.expando]:a[f.expando]&&f.expando;if((!m||e&&m&&l[m]&&!l[m][i])&&j&&d===b)return;m||(k?a[f.expando]=m=++f.uuid:m=f.expando),l[m]||(l[m]={},k||(l[m].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?l[m][i]=f.extend(l[m][i],c):l[m]=f.extend(l[m],c);g=l[m],e&&(g[i]||(g[i]={}),g=g[i]),d!==b&&(g[f.camelCase(c)]=d);if(c==="events"&&!g[c])return g[i]&&g[i].events;j?(h=g[c],h==null&&(h=g[f.camelCase(c)])):h=g;return h}},removeData:function(a,b,c){if(!!f.acceptData(a)){var d,e=f.expando,g=a.nodeType,h=g?f.cache:a,i=g?a[f.expando]:f.expando;if(!h[i])return;if(b){d=c?h[i][e]:h[i];if(d){d[b]||(b=f.camelCase(b)),delete d[b];if(!l(d))return}}if(c){delete h[i][e];if(!l(h[i]))return}var j=h[i][e];f.support.deleteExpando||!h.setInterval?delete h[i]:h[i]=null,j?(h[i]={},g||(h[i].toJSON=f.noop),h[i][e]=j):g&&(f.support.deleteExpando?delete a[f.expando]:a.removeAttribute?a.removeAttribute(f.expando):a[f.expando]=null)}},_data:function(a,b,c){return f.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=f.noData[a.nodeName.toLowerCase()];if(b)return b!==!0&&a.getAttribute("classid")===b}return!0}}),f.fn.extend({data:function(a,c){var d=null;if(typeof a=="undefined"){if(this.length){d=f.data(this[0]);if(this[0].nodeType===1){var e=this[0].attributes,g;for(var h=0,i=e.length;h-1)return!0;return!1},val:function(a){var c,d,e=this[0];if(!arguments.length){if(e){c=f.valHooks[e.nodeName.toLowerCase()]||f.valHooks[e.type];if(c&&"get"in c&&(d=c.get(e,"value"))!==b)return d;d=e.value;return typeof d=="string"?d.replace(p,""):d==null?"":d}return b}var g=f.isFunction(a);return this.each(function(d){var e=f(this),h;if(this.nodeType===1){g?h=a.call(this,d,e.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.nodeName.toLowerCase()]||f.valHooks[this.type];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c=a.selectedIndex,d=[],e=a.options,g=a.type==="select-one";if(c<0)return null;for(var h=g?c:0,i=g?c+1:e.length;h=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attrFix:{tabindex:"tabIndex"},attr:function(a,c,d,e){var g=a.nodeType;if(!a||g===3||g===8||g===2)return b;if(e&&c in f.attrFn)return f(a)[c](d);if(!("getAttribute"in a))return f.prop(a,c,d);var h,i,j=g!==1||!f.isXMLDoc(a);j&&(c=f.attrFix[c]||c,i=f.attrHooks[c],i||(t.test(c)?i=v:u&&(i=u)));if(d!==b){if(d===null){f.removeAttr(a,c);return b}if(i&&"set"in i&&j&&(h=i.set(a,d,c))!==b)return h;a.setAttribute(c,""+d);return d}if(i&&"get"in i&&j&&(h=i.get(a,c))!==null)return h;h=a.getAttribute(c);return h===null?b:h},removeAttr:function(a,b){var c;a.nodeType===1&&(b=f.attrFix[b]||b,f.attr(a,b,""),a.removeAttribute(b),t.test(b)&&(c=f.propFix[b]||b)in a&&(a[c]=!1))},attrHooks:{type:{set:function(a,b){if(q.test(a.nodeName)&&a.parentNode)f.error("type property can't be changed");else if(!f.support.radioValue&&b==="radio"&&f.nodeName(a,"input")){var c=a.value;a.setAttribute("type",b),c&&(a.value=c);return b}}},value:{get:function(a,b){if(u&&f.nodeName(a,"button"))return u.get(a,b);return b in a?a.value:null},set:function(a,b,c){if(u&&f.nodeName(a,"button"))return u.set(a,b,c);a.value=b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e=a.nodeType;if(!a||e===3||e===8||e===2)return b;var g,h,i=e!==1||!f.isXMLDoc(a);i&&(c=f.propFix[c]||c,h=f.propHooks[c]);return d!==b?h&&"set"in h&&(g=h.set(a,d,c))!==b?g:a[c]=d:h&&"get"in h&&(g=h.get(a,c))!==null?g:a[c]},propHooks:{tabIndex:{get:function(a){var c=a.getAttributeNode("tabindex");return c&&c.specified?parseInt(c.value,10):r.test(a.nodeName)||s.test(a.nodeName)&&a.href?0:b}}}}),f.attrHooks.tabIndex=f.propHooks.tabIndex,v={get:function(a,c){var d;return f.prop(a,c)===!0||(d=a.getAttributeNode(c))&&d.nodeValue!==!1?c.toLowerCase():b},set:function(a,b,c){var d;b===!1?f.removeAttr(a,c):(d=f.propFix[c]||c,d in a&&(a[d]=!0),a.setAttribute(c,c.toLowerCase()));return c}},f.support.getSetAttribute||(u=f.valHooks.button={get:function(a,c){var d;d=a.getAttributeNode(c);return d&&d.nodeValue!==""?d.nodeValue:b},set:function(a,b,d){var e=a.getAttributeNode(d);e||(e=c.createAttribute(d),a.setAttributeNode(e));return e.nodeValue=b+""}},f.each(["width","height"],function(a,b){f.attrHooks[b]=f.extend(f.attrHooks[b],{set:function(a,c){if(c===""){a.setAttribute(b,"auto");return c}}})})),f.support.hrefNormalized||f.each(["href","src","width","height"],function(a,c){f.attrHooks[c]=f.extend(f.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),f.support.style||(f.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=""+b}}),f.support.optSelected||(f.propHooks.selected=f.extend(f.propHooks.selected,{get:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex);return null}})),f.support.checkOn||f.each(["radio","checkbox"],function(){f.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),f.each(["radio","checkbox"],function(){f.valHooks[this]=f.extend(f.valHooks[this],{set:function(a,b){if(f.isArray(b))return a.checked=f.inArray(f(a).val(),b)>=0}})});var w=/\.(.*)$/,x=/^(?:textarea|input|select)$/i,y=/\./g,z=/ /g,A=/[^\w\s.|`]/g,B=function(a){return a.replace(A,"\\$&")};f.event={add:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){if(d===!1)d=C;else if(!d)return;var g,h;d.handler&&(g=d,d=g.handler),d.guid||(d.guid=f.guid++);var i=f._data(a);if(!i)return;var j=i.events,k=i.handle;j||(i.events=j={}),k||(i.handle=k=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.handle.apply(k.elem,arguments):b}),k.elem=a,c=c.split(" ");var l,m=0,n;while(l=c[m++]){h=g?f.extend({},g):{handler:d,data:e},l.indexOf(".")>-1?(n=l.split("."),l=n.shift(),h.namespace=n.slice(0).sort().join(".")):(n=[],h.namespace=""),h.type=l,h.guid||(h.guid=d.guid);var o=j[l],p=f.event.special[l]||{};if(!o){o=j[l]=[];if(!p.setup||p.setup.call(a,e,n,k)===!1)a.addEventListener?a.addEventListener(l,k,!1):a.attachEvent&&a.attachEvent("on"+l,k)}p.add&&(p.add.call(a,h),h.handler.guid||(h.handler.guid=d.guid)),o.push(h),f.event.global[l]=!0}a=null}},global:{},remove:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){d===!1&&(d=C);var g,h,i,j,k=0,l,m,n,o,p,q,r,s=f.hasData(a)&&f._data(a),t=s&&s.events;if(!s||!t)return;c&&c.type&&(d=c.handler,c=c.type);if(!c||typeof c=="string"&&c.charAt(0)==="."){c=c||"";for(h in t)f.event.remove(a,h+c);return}c=c.split(" ");while(h=c[k++]){r=h,q=null,l=h.indexOf(".")<0,m=[],l||(m=h.split("."),h=m.shift(),n=new RegExp("(^|\\.)"+f.map(m.slice(0).sort(),B).join("\\.(?:.*\\.)?")+"(\\.|$)")),p=t[h];if(!p)continue;if(!d){for(j=0;j").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){ch||(ch=c.createElement("iframe"),ch.frameBorder=ch.width=ch.height=0),b.appendChild(ch);if(!ci||!ch.createElement)ci=(ch.contentWindow||ch.contentDocument).document,ci.write((c.compatMode==="CSS1Compat"?"":"")+""),ci.close();d=ci.createElement(a),ci.body.appendChild(d),e=f.css(d,"display"),b.removeChild(ch)}cg[a]=e}return cg[a]}function cq(a,b){var c={};f.each(cm.concat.apply([],cm.slice(0,b)),function(){c[this]=a});return c}function cp(){cn=b}function co(){setTimeout(cp,0);return cn=f.now()}function cf(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ce(){try{return new a.XMLHttpRequest}catch(b){}}function b$(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g0){c!=="border"&&f.each(e,function(){c||(d-=parseFloat(f.css(a,"padding"+this))||0),c==="margin"?d+=parseFloat(f.css(a,c+this))||0:d-=parseFloat(f.css(a,"border"+this+"Width"))||0});return d+"px"}d=bv(a,b,b);if(d<0||d==null)d=a.style[b]||0;d=parseFloat(d)||0,c&&f.each(e,function(){d+=parseFloat(f.css(a,"padding"+this))||0,c!=="padding"&&(d+=parseFloat(f.css(a,"border"+this+"Width"))||0),c==="margin"&&(d+=parseFloat(f.css(a,c+this))||0)});return d+"px"}function bl(a,b){b.src?f.ajax({url:b.src,async:!1,dataType:"script"}):f.globalEval((b.text||b.textContent||b.innerHTML||"").replace(bd,"/*$0*/")),b.parentNode&&b.parentNode.removeChild(b)}function bk(a){f.nodeName(a,"input")?bj(a):"getElementsByTagName"in a&&f.grep(a.getElementsByTagName("input"),bj)}function bj(a){if(a.type==="checkbox"||a.type==="radio")a.defaultChecked=a.checked}function bi(a){return"getElementsByTagName"in a?a.getElementsByTagName("*"):"querySelectorAll"in a?a.querySelectorAll("*"):[]}function bh(a,b){var c;if(b.nodeType===1){b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase();if(c==="object")b.outerHTML=a.outerHTML;else if(c!=="input"||a.type!=="checkbox"&&a.type!=="radio"){if(c==="option")b.selected=a.defaultSelected;else if(c==="input"||c==="textarea")b.defaultValue=a.defaultValue}else a.checked&&(b.defaultChecked=b.checked=a.checked),b.value!==a.value&&(b.value=a.value);b.removeAttribute(f.expando)}}function bg(a,b){if(b.nodeType===1&&!!f.hasData(a)){var c=f.expando,d=f.data(a),e=f.data(b,d);if(d=d[c]){var g=d.events;e=e[c]=f.extend({},d);if(g){delete e.handle,e.events={};for(var h in g)for(var i=0,j=g[h].length;i=0===c})}function U(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function M(a,b){return(a&&a!=="*"?a+".":"")+b.replace(y,"`").replace(z,"&")}function L(a){var b,c,d,e,g,h,i,j,k,l,m,n,o,p=[],q=[],r=f._data(this,"events");if(!(a.liveFired===this||!r||!r.live||a.target.disabled||a.button&&a.type==="click")){a.namespace&&(n=new RegExp("(^|\\.)"+a.namespace.split(".").join("\\.(?:.*\\.)?")+"(\\.|$)")),a.liveFired=this;var s=r.live.slice(0);for(i=0;ic)break;a.currentTarget=e.elem,a.data=e.handleObj.data,a.handleObj=e.handleObj,o=e.handleObj.origHandler.apply(e.elem,arguments);if(o===!1||a.isPropagationStopped()){c=e.level,o===!1&&(b=!1);if(a.isImmediatePropagationStopped())break}}return b}}function J(a,c,d){var e=f.extend({},d[0]);e.type=a,e.originalEvent={},e.liveFired=b,f.event.handle.call(c,e),e.isDefaultPrevented()&&d[0].preventDefault()}function D(){return!0}function C(){return!1}function m(a,c,d){var e=c+"defer",g=c+"queue",h=c+"mark",i=f.data(a,e,b,!0);i&&(d==="queue"||!f.data(a,g,b,!0))&&(d==="mark"||!f.data(a,h,b,!0))&&setTimeout(function(){!f.data(a,g,b,!0)&&!f.data(a,h,b,!0)&&(f.removeData(a,e,!0),i.resolve())},0)}function l(a){for(var b in a)if(b!=="toJSON")return!1;return!0}function k(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(j,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNaN(d)?i.test(d)?f.parseJSON(d):d:parseFloat(d)}catch(g){}f.data(a,c,d)}else d=b}return d}var c=a.document,d=a.navigator,e=a.location,f=function(){function K(){if(!e.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(K,1);return}e.ready()}}var e=function(a,b){return new e.fn.init(a,b,h)},f=a.jQuery,g=a.$,h,i=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/\d/,n=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,o=/^[\],:{}\s]*$/,p=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,q=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,r=/(?:^|:|,)(?:\s*\[)+/g,s=/(webkit)[ \/]([\w.]+)/,t=/(opera)(?:.*version)?[ \/]([\w.]+)/,u=/(msie) ([\w.]+)/,v=/(mozilla)(?:.*? rv:([\w.]+))?/,w=/-([a-z]|[0-9])/ig,x=/^-ms-/,y=function(a,b){return(b+"").toUpperCase()},z=d.userAgent,A,B,C,D=Object.prototype.toString,E=Object.prototype.hasOwnProperty,F=Array.prototype.push,G=Array.prototype.slice,H=String.prototype.trim,I=Array.prototype.indexOf,J={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=n.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.6.4",length:0,size:function(){return this.length},toArray:function(){return G.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?F.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),B.done(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(G.apply(this,arguments),"slice",G.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:F,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j0)return;B.resolveWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").unbind("ready")}},bindReady:function(){if(!B){B=e._Deferred();if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",C,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",C),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&K()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a&&typeof a=="object"&&"setInterval"in a},isNaN:function(a){return a==null||!m.test(a)||isNaN(a)},type:function(a){return a==null?String(a):J[D.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;try{if(a.constructor&&!E.call(a,"constructor")&&!E.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||E.call(a,d)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw a},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(o.test(b.replace(p,"@").replace(q,"]").replace(r,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(c){var d,f;try{a.DOMParser?(f=new DOMParser,d=f.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(g){d=b}(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&e.error("Invalid XML: "+c);return d},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(x,"ms-").replace(w,y)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i1?h.call(arguments,0):c,--e||g.resolveWith(g,h.call(b,0))}}var b=arguments,c=0,d=b.length,e=d,g=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred();if(d>1){for(;c
        a",d=a.getElementsByTagName("*"),e=a.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=a.getElementsByTagName("input")[0],k={leadingWhitespace:a.firstChild.nodeType===3,tbody:!a.getElementsByTagName("tbody").length,htmlSerialize:!!a.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55$/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:a.className!=="t",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0},i.checked=!0,k.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,k.optDisabled=!h.disabled;try{delete a.test}catch(v){k.deleteExpando=!1}!a.addEventListener&&a.attachEvent&&a.fireEvent&&(a.attachEvent("onclick",function(){k.noCloneEvent=!1}),a.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),k.radioValue=i.value==="t",i.setAttribute("checked","checked"),a.appendChild(i),l=c.createDocumentFragment(),l.appendChild(a.firstChild),k.checkClone=l.cloneNode(!0).cloneNode(!0).lastChild.checked,a.innerHTML="",a.style.width=a.style.paddingLeft="1px",m=c.getElementsByTagName("body")[0],o=c.createElement(m?"div":"body"),p={visibility:"hidden",width:0,height:0,border:0,margin:0,background:"none"},m&&f.extend(p,{position:"absolute",left:"-1000px",top:"-1000px"});for(t in p)o.style[t]=p[t];o.appendChild(a),n=m||b,n.insertBefore(o,n.firstChild),k.appendChecked=i.checked,k.boxModel=a.offsetWidth===2,"zoom"in a.style&&(a.style.display="inline",a.style.zoom=1,k.inlineBlockNeedsLayout=a.offsetWidth===2,a.style.display="",a.innerHTML="
        ",k.shrinkWrapBlocks=a.offsetWidth!==2),a.innerHTML="
        t
        ",q=a.getElementsByTagName("td"),u=q[0].offsetHeight===0,q[0].style.display="",q[1].style.display="none",k.reliableHiddenOffsets=u&&q[0].offsetHeight===0,a.innerHTML="",c.defaultView&&c.defaultView.getComputedStyle&&(j=c.createElement("div"),j.style.width="0",j.style.marginRight="0",a.appendChild(j),k.reliableMarginRight=(parseInt((c.defaultView.getComputedStyle(j,null)||{marginRight:0}).marginRight,10)||0)===0),o.innerHTML="",n.removeChild(o);if(a.attachEvent)for(t in{submit:1,change:1,focusin:1})s="on"+t,u=s in a,u||(a.setAttribute(s,"return;"),u=typeof a[s]=="function"),k[t+"Bubbles"]=u;o=l=g=h=m=j=a=i=null;return k}(),f.boxModel=f.support.boxModel;var i=/^(?:\{.*\}|\[.*\])$/,j=/([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!l(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g,h,i=f.expando,j=typeof c=="string",k=a.nodeType,l=k?f.cache:a,m=k?a[f.expando]:a[f.expando]&&f.expando;if((!m||e&&m&&l[m]&&!l[m][i])&&j&&d===b)return;m||(k?a[f.expando]=m=++f.uuid:m=f.expando),l[m]||(l[m]={},k||(l[m].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?l[m][i]=f.extend(l[m][i],c):l[m]=f.extend(l[m],c);g=l[m],e&&(g[i]||(g[i]={}),g=g[i]),d!==b&&(g[f.camelCase(c)]=d);if(c==="events"&&!g[c])return g[i]&&g[i].events;j?(h=g[c],h==null&&(h=g[f.camelCase(c)])):h=g;return h}},removeData:function(a,b,c){if(!!f.acceptData(a)){var d,e=f.expando,g=a.nodeType,h=g?f.cache:a,i=g?a[f.expando]:f.expando;if(!h[i])return;if(b){d=c?h[i][e]:h[i];if(d){d[b]||(b=f.camelCase(b)),delete d[b];if(!l(d))return}}if(c){delete h[i][e];if(!l(h[i]))return}var j=h[i][e];f.support.deleteExpando||!h.setInterval?delete h[i]:h[i]=null,j?(h[i]={},g||(h[i].toJSON=f.noop),h[i][e]=j):g&&(f.support.deleteExpando?delete a[f.expando]:a.removeAttribute?a.removeAttribute(f.expando):a[f.expando]=null)}},_data:function(a,b,c){return f.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=f.noData[a.nodeName.toLowerCase()];if(b)return b!==!0&&a.getAttribute("classid")===b}return!0}}),f.fn.extend({data:function(a,c){var d=null;if(typeof a=="undefined"){if(this.length){d=f.data(this[0]);if(this[0].nodeType===1){var e=this[0].attributes,g;for(var h=0,i=e.length;h-1)return!0;return!1},val:function(a){var c,d,e=this[0];if(!arguments.length){if(e){c=f.valHooks[e.nodeName.toLowerCase()]||f.valHooks[e.type];if(c&&"get"in c&&(d=c.get(e,"value"))!==b)return d;d=e.value;return typeof d=="string"?d.replace(p,""):d==null?"":d}return b}var g=f.isFunction(a);return this.each(function(d){var e=f(this),h;if(this.nodeType===1){g?h=a.call(this,d,e.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.nodeName.toLowerCase()]||f.valHooks[this.type];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c=a.selectedIndex,d=[],e=a.options,g=a.type==="select-one";if(c<0)return null;for(var h=g?c:0,i=g?c+1:e.length;h=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attrFix:{tabindex:"tabIndex"},attr:function(a,c,d,e){var g=a.nodeType;if(!a||g===3||g===8||g===2)return b;if(e&&c in f.attrFn)return f(a)[c](d);if(!("getAttribute"in a))return f.prop(a,c,d);var h,i,j=g!==1||!f.isXMLDoc(a);j&&(c=f.attrFix[c]||c,i=f.attrHooks[c],i||(t.test(c)?i=v:u&&(i=u)));if(d!==b){if(d===null){f.removeAttr(a,c);return b}if(i&&"set"in i&&j&&(h=i.set(a,d,c))!==b)return h;a.setAttribute(c,""+d);return d}if(i&&"get"in i&&j&&(h=i.get(a,c))!==null)return h;h=a.getAttribute(c);return h===null?b:h},removeAttr:function(a,b){var c;a.nodeType===1&&(b=f.attrFix[b]||b,f.attr(a,b,""),a.removeAttribute(b),t.test(b)&&(c=f.propFix[b]||b)in a&&(a[c]=!1))},attrHooks:{type:{set:function(a,b){if(q.test(a.nodeName)&&a.parentNode)f.error("type property can't be changed");else if(!f.support.radioValue&&b==="radio"&&f.nodeName(a,"input")){var c=a.value;a.setAttribute("type",b),c&&(a.value=c);return b}}},value:{get:function(a,b){if(u&&f.nodeName(a,"button"))return u.get(a,b);return b in a?a.value:null},set:function(a,b,c){if(u&&f.nodeName(a,"button"))return u.set(a,b,c);a.value=b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e=a.nodeType;if(!a||e===3||e===8||e===2)return b;var g,h,i=e!==1||!f.isXMLDoc(a);i&&(c=f.propFix[c]||c,h=f.propHooks[c]);return d!==b?h&&"set"in h&&(g=h.set(a,d,c))!==b?g:a[c]=d:h&&"get"in h&&(g=h.get(a,c))!==null?g:a[c]},propHooks:{tabIndex:{get:function(a){var c=a.getAttributeNode("tabindex");return c&&c.specified?parseInt(c.value,10):r.test(a.nodeName)||s.test(a.nodeName)&&a.href?0:b}}}}),f.attrHooks.tabIndex=f.propHooks.tabIndex,v={get:function(a,c){var d;return f.prop(a,c)===!0||(d=a.getAttributeNode(c))&&d.nodeValue!==!1?c.toLowerCase():b},set:function(a,b,c){var d;b===!1?f.removeAttr(a,c):(d=f.propFix[c]||c,d in a&&(a[d]=!0),a.setAttribute(c,c.toLowerCase()));return c}},f.support.getSetAttribute||(u=f.valHooks.button={get:function(a,c){var d;d=a.getAttributeNode(c);return d&&d.nodeValue!==""?d.nodeValue:b},set:function(a,b,d){var e=a.getAttributeNode(d);e||(e=c.createAttribute(d),a.setAttributeNode(e));return e.nodeValue=b+""}},f.each(["width","height"],function(a,b){f.attrHooks[b]=f.extend(f.attrHooks[b],{set:function(a,c){if(c===""){a.setAttribute(b,"auto");return c}}})})),f.support.hrefNormalized||f.each(["href","src","width","height"],function(a,c){f.attrHooks[c]=f.extend(f.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),f.support.style||(f.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=""+b}}),f.support.optSelected||(f.propHooks.selected=f.extend(f.propHooks.selected,{get:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex);return null}})),f.support.checkOn||f.each(["radio","checkbox"],function(){f.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),f.each(["radio","checkbox"],function(){f.valHooks[this]=f.extend(f.valHooks[this],{set:function(a,b){if(f.isArray(b))return a.checked=f.inArray(f(a).val(),b)>=0}})});var w=/\.(.*)$/,x=/^(?:textarea|input|select)$/i,y=/\./g,z=/ /g,A=/[^\w\s.|`]/g,B=function(a){return a.replace(A,"\\$&")};f.event={add:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){if(d===!1)d=C;else if(!d)return;var g,h;d.handler&&(g=d,d=g.handler),d.guid||(d.guid=f.guid++);var i=f._data(a);if(!i)return;var j=i.events,k=i.handle;j||(i.events=j={}),k||(i.handle=k=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.handle.apply(k.elem,arguments):b}),k.elem=a,c=c.split(" ");var l,m=0,n;while(l=c[m++]){h=g?f.extend({},g):{handler:d,data:e},l.indexOf(".")>-1?(n=l.split("."),l=n.shift(),h.namespace=n.slice(0).sort().join(".")):(n=[],h.namespace=""),h.type=l,h.guid||(h.guid=d.guid);var o=j[l],p=f.event.special[l]||{};if(!o){o=j[l]=[];if(!p.setup||p.setup.call(a,e,n,k)===!1)a.addEventListener?a.addEventListener(l,k,!1):a.attachEvent&&a.attachEvent("on"+l,k)}p.add&&(p.add.call(a,h),h.handler.guid||(h.handler.guid=d.guid)),o.push(h),f.event.global[l]=!0}a=null}},global:{},remove:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){d===!1&&(d=C);var g,h,i,j,k=0,l,m,n,o,p,q,r,s=f.hasData(a)&&f._data(a),t=s&&s.events;if(!s||!t)return;c&&c.type&&(d=c.handler,c=c.type);if(!c||typeof c=="string"&&c.charAt(0)==="."){c=c||"";for(h in t)f.event.remove(a,h+c);return}c=c.split(" ");while(h=c[k++]){r=h,q=null,l=h.indexOf(".")<0,m=[],l||(m=h.split("."),h=m.shift(),n=new RegExp("(^|\\.)"+f.map(m.slice(0).sort(),B).join("\\.(?:.*\\.)?")+"(\\.|$)")),p=t[h];if(!p)continue;if(!d){for(j=0;j=0&&(h=h.slice(0,-1),j=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if(!!e&&!f.event.customEvent[h]||!!f.event.global[h]){c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.exclusive=j,c.namespace=i.join("."),c.namespace_re=new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)");if(g||!e)c.preventDefault(),c.stopPropagation();if(!e){f.each(f.cache,function(){var a=f.expando,b=this[a];b&&b.events&&b.events[h]&&f.event.trigger(c,d,b.handle.elem)});return}if(e.nodeType===3||e.nodeType===8)return;c.result=b,c.target=e,d=d!=null?f.makeArray(d):[],d.unshift(c);var k=e,l=h.indexOf(":")<0?"on"+h:"";do{var m=f._data(k,"handle");c.currentTarget=k,m&&m.apply(k,d),l&&f.acceptData(k)&&k[l]&&k[l].apply(k,d)===!1&&(c.result=!1,c.preventDefault()),k=k.parentNode||k.ownerDocument||k===c.target.ownerDocument&&a}while(k&&!c.isPropagationStopped());if(!c.isDefaultPrevented()){var n,o=f.event.special[h]||{};if((!o._default||o._default.call(e.ownerDocument,c)===!1)&&(h!=="click"||!f.nodeName(e,"a"))&&f.acceptData(e)){try{l&&e[h]&&(n=e[l],n&&(e[l]=null),f.event.triggered=h,e[h]())}catch(p){}n&&(e[l]=n),f.event.triggered=b}}return c.result}},handle:function(c){c=f.event.fix(c||a.event);var d=((f._data(this,"events")||{})[c.type]||[]).slice(0),e=!c.exclusive&&!c.namespace,g=Array.prototype.slice.call(arguments,0);g[0]=c,c.currentTarget=this;for(var h=0,i=d.length;h-1?f.map(a.options,function(a){return a.selected}).join("-"):"":f.nodeName(a,"select")&&(c=a.selectedIndex);return c},I=function(c){var d=c.target,e,g;if(!!x.test(d.nodeName)&&!d.readOnly){e=f._data(d,"_change_data"),g=H(d),(c.type!=="focusout"||d.type!=="radio")&&f._data(d,"_change_data",g);if(e===b||g===e)return;if(e!=null||g)c.type="change",c.liveFired=b,f.event.trigger(c,arguments[1],d)}};f.event.special.change={filters:{focusout:I,beforedeactivate:I,click:function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(c==="radio"||c==="checkbox"||f.nodeName(b,"select"))&&I.call(this,a)},keydown:function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(a.keyCode===13&&!f.nodeName(b,"textarea")||a.keyCode===32&&(c==="checkbox"||c==="radio")||c==="select-multiple")&&I.call(this,a)},beforeactivate:function(a){var b=a.target;f._data(b,"_change_data",H(b))}},setup:function(a,b){if(this.type==="file")return!1;for(var c in G)f.event.add(this,c+".specialChange",G[c]);return x.test(this.nodeName)},teardown:function(a){f.event.remove(this,".specialChange");return x.test(this.nodeName)}},G=f.event.special.change.filters,G.focus=G.beforeactivate}f.support.focusinBubbles||f.each({focus:"focusin",blur:"focusout"},function(a,b){function e(a){var c=f.event.fix(a);c.type=b,c.originalEvent={},f.event.trigger(c,null,c.target),c.isDefaultPrevented()&&a.preventDefault()}var d=0;f.event.special[b]={setup:function(){d++===0&&c.addEventListener(a,e,!0)},teardown:function(){--d===0&&c.removeEventListener(a,e,!0)}}}),f.each(["bind","one"],function(a,c){f.fn[c]=function(a,d,e){var g;if(typeof a=="object"){for(var h in a)this[c](h,d,a[h],e);return this}if(arguments.length===2||d===!1)e=d,d=b;c==="one"?(g=function(a){f(this).unbind(a,g);return e.apply(this,arguments)},g.guid=e.guid||f.guid++):g=e;if(a==="unload"&&c!=="one")this.one(a,d,e);else for(var i=0,j=this.length;i0?this.bind(b,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0)}),function(){function u(a,b,c,d,e,f){for(var g=0,h=d.length;g0){j=i;break}}i=i[a]}d[g]=j}}}function t(a,b,c,d,e,f){for(var g=0,h=d.length;g+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d=0,e=Object.prototype.toString,g=!1,h=!0,i=/\\/g,j=/\W/;[0,0].sort(function(){h=!1;return 0});var k=function(b,d,f,g){f=f||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return f;var i,j,n,o,q,r,s,t,u=!0,w=k.isXML(d),x=[],y=b;do{a.exec(""),i=a.exec(y);if(i){y=i[3],x.push(i[1]);if(i[2]){o=i[3];break}}}while(i);if(x.length>1&&m.exec(b))if(x.length===2&&l.relative[x[0]])j=v(x[0]+x[1],d);else{j=l.relative[x[0]]?[d]:k(x.shift(),d);while(x.length)b=x.shift(),l.relative[b]&&(b+=x.shift()),j=v(b,j)}else{!g&&x.length>1&&d.nodeType===9&&!w&&l.match.ID.test(x[0])&&!l.match.ID.test(x[x.length-1])&&(q=k.find(x.shift(),d,w),d=q.expr?k.filter(q.expr,q.set)[0]:q.set[0]);if(d){q=g?{expr:x.pop(),set:p(g)}:k.find(x.pop(),x.length===1&&(x[0]==="~"||x[0]==="+")&&d.parentNode?d.parentNode:d,w),j=q.expr?k.filter(q.expr,q.set):q.set,x.length>0?n=p(j):u=!1;while(x.length)r=x.pop(),s=r,l.relative[r]?s=x.pop():r="",s==null&&(s=d),l.relative[r](n,s,w)}else n=x=[]}n||(n=j),n||k.error(r||b);if(e.call(n)==="[object Array]")if(!u)f.push.apply(f,n);else if(d&&d.nodeType===1)for(t=0;n[t]!=null;t++)n[t]&&(n[t]===!0||n[t].nodeType===1&&k.contains(d,n[t]))&&f.push(j[t]);else for(t=0;n[t]!=null;t++)n[t]&&n[t].nodeType===1&&f.push(j[t]);else p(n,f);o&&(k(o,h,f,g),k.uniqueSort(f));return f};k.uniqueSort=function(a){if(r){g=h,a.sort(r);if(g)for(var b=1;b0},k.find=function(a,b,c){var d;if(!a)return[];for(var e=0,f=l.order.length;e":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!j.test(b)){b=b.toLowerCase();for(;e=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(i,"")},TAG:function(a,b){return a[1].replace(i,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||k.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&k.error(a[0]);a[0]=d++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(i,"");!f&&l.attrMap[g]&&(a[1]=l.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(i,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=k(b[3],null,null,c);else{var g=k.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(l.match.POS.test(b[0])||l.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!k(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return bc[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=l.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||k.getText([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=l.attrHandle[c]?l.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=l.setFilters[e];if(f)return f(a,c,b,d)}}},m=l.match.POS,n=function(a,b){return"\\"+(b-0+1)};for(var o in l.match)l.match[o]=new RegExp(l.match[o].source+/(?![^\[]*\])(?![^\(]*\))/.source),l.leftMatch[o]=new RegExp(/(^(?:.|\r|\n)*?)/.source+l.match[o].source.replace(/\\(\d+)/g,n));var p=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(q){p=function(a,b){var c=0,d=b||[];if(e.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var f=a.length;c",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(l.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},l.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(l.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(l.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=k,b=c.createElement("div"),d="__sizzle__";b.innerHTML="

        ";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){k=function(b,e,f,g){e=e||c;if(!g&&!k.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return p(e.getElementsByTagName(b),f);if(h[2]&&l.find.CLASS&&e.getElementsByClassName)return p(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return p([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return p([],f);if(i.id===h[3])return p([i],f)}try{return p(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var m=e,n=e.getAttribute("id"),o=n||d,q=e.parentNode,r=/^\s*[+~]/.test(b);n?o=o.replace(/'/g,"\\$&"):e.setAttribute("id",o),r&&q&&(e=e.parentNode);try{if(!r||q)return p(e.querySelectorAll("[id='"+o+"'] "+b),f)}catch(s){}finally{n||m.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)k[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}k.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!k.isXML(a))try{if(e||!l.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return k(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="
        ";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;l.order.splice(1,0,"CLASS"),l.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?k.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?k.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:k.contains=function(){return!1},k.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var v=function(a,b){var c,d=[],e="",f=b.nodeType?[b]:b;while(c=l.match.PSEUDO.exec(a))e+=c[0],a=a.replace(l.match.PSEUDO,"");a=l.relative[a]?a+"*":a;for(var g=0,h=f.length;g0)for(h=g;h0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h,i,j={},k=1;if(g&&a.length){for(d=0,e=a.length;d-1:f(g).is(h))&&c.push({selector:i,elem:g,level:k});g=g.parentNode,k++}}return c}var l=S.test(a)||typeof a!="string"?f(a,b||this.context):0;for(d=0,e=this.length;d-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a)return this[0]&&this[0].parentNode?this.prevAll().length:-1;if(typeof a=="string")return f.inArray(this[0],f(a));return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(U(c[0])||U(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling(a.parentNode.firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c),g=R.call(arguments);N.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!T[a]?f.unique(e):e,(this.length>1||P.test(d))&&O.test(a)&&(e=e.reverse());return this.pushStack(e,a,g.join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var W=/ jQuery\d+="(?:\d+|null)"/g,X=/^\s+/,Y=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,Z=/<([\w:]+)/,$=/",""],legend:[1,"
        ","
        "],thead:[1,"","
        "],tr:[2,"","
        "],td:[3,"","
        "],col:[2,"","
        "],area:[1,"",""],_default:[0,"",""]};be.optgroup=be.option,be.tbody=be.tfoot=be.colgroup=be.caption=be.thead,be.th=be.td,f.support.htmlSerialize||(be._default=[1,"div
        ","
        "]),f.fn.extend({text:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.text(a.call(this,b,c.text()))});if(typeof a!="object"&&a!==b)return this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a));return f.text(this)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){f(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f(arguments[0]).toArray());return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(W,""):null;if(typeof a=="string"&&!ba.test(a)&&(f.support.leadingWhitespace||!X.test(a))&&!be[(Z.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Y,"<$1>");try{for(var c=0,d=this.length;c1&&l0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d=a.cloneNode(!0),e,g,h;if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bh(a,d),e=bi(a),g=bi(d);for(h=0;e[h];++h)g[h]&&bh(e[h],g[h])}if(b){bg(a,d);if(c){e=bi(a),g=bi(d);for(h=0;e[h];++h)bg(e[h],g[h])}}e=g=null;return d},clean:function(a,b,d,e){var g;b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);var h=[],i;for(var j=0,k;(k=a[j])!=null;j++){typeof k=="number"&&(k+="");if(!k)continue;if(typeof k=="string")if(!_.test(k))k=b.createTextNode(k);else{k=k.replace(Y,"<$1>");var l=(Z.exec(k)||["",""])[1].toLowerCase(),m=be[l]||be._default,n=m[0],o=b.createElement("div");o.innerHTML=m[1]+k+m[2];while(n--)o=o.lastChild;if(!f.support.tbody){var p=$.test(k),q=l==="table"&&!p?o.firstChild&&o.firstChild.childNodes:m[1]===""&&!p?o.childNodes:[];for(i=q.length-1;i>=0;--i)f.nodeName(q[i],"tbody")&&!q[i].childNodes.length&&q[i].parentNode.removeChild(q[i])}!f.support.leadingWhitespace&&X.test(k)&&o.insertBefore(b.createTextNode(X.exec(k)[0]),o.firstChild),k=o.childNodes}var r;if(!f.support.appendChecked)if(k[0]&&typeof (r=k.length)=="number")for(i=0;i=0)return b+"px"}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bn.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=f.isNaN(b)?"":"alpha(opacity="+b*100+")",g=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&f.trim(g.replace(bm,""))===""){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bm.test(g)?g.replace(bm,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){var c;f.swap(a,{display:"inline-block"},function(){b?c=bv(a,"margin-right","marginRight"):c=a.style.marginRight});return c}})}),c.defaultView&&c.defaultView.getComputedStyle&&(bw=function(a,c){var d,e,g;c=c.replace(bo,"-$1").toLowerCase();if(!(e=a.ownerDocument.defaultView))return b;if(g=e.getComputedStyle(a,null))d=g.getPropertyValue(c),d===""&&!f.contains(a.ownerDocument.documentElement,a)&&(d=f.style(a,c));return d}),c.documentElement.currentStyle&&(bx=function(a,b){var c,d=a.currentStyle&&a.currentStyle[b],e=a.runtimeStyle&&a.runtimeStyle[b],f=a.style;!bp.test(d)&&bq.test(d)&&(c=f.left,e&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":d||0,d=f.pixelLeft+"px",f.left=c,e&&(a.runtimeStyle.left=e));return d===""?"auto":d}),bv=bw||bx,f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)});var bz=/%20/g,bA=/\[\]$/,bB=/\r?\n/g,bC=/#.*$/,bD=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bE=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bF=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,bG=/^(?:GET|HEAD)$/,bH=/^\/\//,bI=/\?/,bJ=/)<[^<]*)*<\/script>/gi,bK=/^(?:select|textarea)/i,bL=/\s+/,bM=/([?&])_=[^&]*/,bN=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bO=f.fn.load,bP={},bQ={},bR,bS,bT=["*/"]+["*"];try{bR=e.href}catch(bU){bR=c.createElement("a"),bR.href="",bR=bR.href}bS=bN.exec(bR.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bO)return bO.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("
        ").append(c.replace(bJ,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bK.test(this.nodeName)||bE.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bB,"\r\n")}}):{name:b.name,value:c.replace(bB,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.bind(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?bX(a,f.ajaxSettings):(b=a,a=f.ajaxSettings),bX(a,b);return a},ajaxSettings:{url:bR,isLocal:bF.test(bS[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":bT},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:bV(bP),ajaxTransport:bV(bQ),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a>0?4:0;var o,r,u,w=c,x=l?bZ(d,v,l):b,y,z;if(a>=200&&a<300||a===304){if(d.ifModified){if(y=v.getResponseHeader("Last-Modified"))f.lastModified[k]=y;if(z=v.getResponseHeader("Etag"))f.etag[k]=z}if(a===304)w="notmodified",o=!0;else try{r=b$(d,x),w="success",o=!0}catch(A){w="parsererror",u=A}}else{u=w;if(!w||a)w="error",a<0&&(a=0)}v.status=a,v.statusText=""+(c||w),o?h.resolveWith(e,[r,w,v]):h.rejectWith(e,[v,w,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.resolveWith(e,[v,w]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f._Deferred(),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bD.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.done,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bC,"").replace(bH,bS[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bL),d.crossDomain==null&&(r=bN.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bS[1]&&r[2]==bS[2]&&(r[3]||(r[1]==="http:"?80:443))==(bS[3]||(bS[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),bW(bP,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bG.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bI.test(d.url)?"&":"?")+d.data,delete d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bM,"$1_="+x);d.url=y+(y===d.url?(bI.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", "+bT+"; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=bW(bQ,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){s<2?w(-1,z):f.error(z)}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)bY(g,a[g],c,e);return d.join("&").replace(bz,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var b_=f.now(),ca=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+b_++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=b.contentType==="application/x-www-form-urlencoded"&&typeof b.data=="string";if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(ca.test(b.url)||e&&ca.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(ca,l),b.url===j&&(e&&(k=k.replace(ca,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var cb=a.ActiveXObject?function(){for(var a in cd)cd[a](0,1)}:!1,cc=0,cd;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ce()||cf()}:ce,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,cb&&delete cd[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n),m.text=h.responseText;try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cc,cb&&(cd||(cd={},f(a).unload(cb)),cd[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var cg={},ch,ci,cj=/^(?:toggle|show|hide)$/,ck=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,cl,cm=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cn;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(cq("show",3),a,b,c);for(var g=0,h=this.length;g=e.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),e.animatedProperties[this.prop]=!0;for(g in e.animatedProperties)e.animatedProperties[g]!==!0&&(c=!1);if(c){e.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){d.style["overflow"+b]=e.overflow[a]}),e.hide&&f(d).hide();if(e.hide||e.show)for(var i in e.animatedProperties)f.style(d,i,e.orig[i]);e.complete.call(d)}return!1}e.duration==Infinity?this.now=b:(h=b-this.startTime,this.state=h/e.duration,this.pos=f.easing[e.animatedProperties[this.prop]](this.state,h,0,1,e.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){for(var a=f.timers,b=0;b
        ";f.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"}),b.innerHTML=j,a.insertBefore(b,a.firstChild),d=b.firstChild,e=d.firstChild,h=d.nextSibling.firstChild.firstChild,this.doesNotAddBorder=e.offsetTop!==5,this.doesAddBorderForTableAndCells=h.offsetTop===5,e.style.position="fixed",e.style.top="20px",this.supportsFixedPosition=e.offsetTop===20||e.offsetTop===15,e.style.position=e.style.top="",d.style.overflow="hidden",d.style.position="relative",this.subtractsBorderForOverflowNotVisible=e.offsetTop===-5,this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==i,a.removeChild(b),f.offset.initialize=f.noop},bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;f.offset.initialize(),f.offset.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(f.css(a,"marginTop"))||0,c+=parseFloat(f.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var d=f.css(a,"position");d==="static"&&(a.style.position="relative");var e=f(a),g=e.offset(),h=f.css(a,"top"),i=f.css(a,"left"),j=(d==="absolute"||d==="fixed")&&f.inArray("auto",[h,i])>-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=ct.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!ct.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each(["Left","Top"],function(a,c){var d="scroll"+c;f.fn[d]=function(c){var e,g;if(c===b){e=this[0];if(!e)return null;g=cu(e);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:f.support.boxModel&&g.document.documentElement[d]||g.document.body[d]:e[d]}return this.each(function(){g=cu(this),g?g.scrollTo(a?f(g).scrollLeft():c,a?c:f(g).scrollTop()):this[d]=c})}}),f.each(["Height","Width"],function(a,c){var d=c.toLowerCase();f.fn["inner"+c]=function(){var a=this[0];return a&&a.style?parseFloat(f.css(a,d,"padding")):null},f.fn["outer"+c]=function(a){var b=this[0];return b&&b.style?parseFloat(f.css(b,d,a?"margin":"border")):null},f.fn[d]=function(a){var e=this[0];if(!e)return a==null?null:this;if(f.isFunction(a))return this.each(function(b){var c=f(this);c[d](a.call(this,b,c[d]()))});if(f.isWindow(e)){var g=e.document.documentElement["client"+c],h=e.document.body;return e.document.compatMode==="CSS1Compat"&&g||h&&h["client"+c]||g}if(e.nodeType===9)return Math.max(e.documentElement["client"+c],e.body["scroll"+c],e.documentElement["scroll"+c],e.body["offset"+c],e.documentElement["offset"+c]);if(a===b){var i=f.css(e,d),j=parseFloat(i);return f.isNaN(j)?i:j}return this.css(d,typeof a=="string"?a:a+"px")}}),a.jQuery=a.$=f})(window); \ No newline at end of file +(a,i,e,d)),g&&(f.fragments[a[0]]=h?e:1);return{fragment:e,cacheable:g}},f.fragments={},f.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){f.fn[a]=function(c){var d=[],e=f(c),g=this.length===1&&this[0].parentNode;if(g&&g.nodeType===11&&g.childNodes.length===1&&e.length===1){e[b](this[0]);return this}for(var h=0,i=e.length;h0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d=a.cloneNode(!0),e,g,h;if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bh(a,d),e=bi(a),g=bi(d);for(h=0;e[h];++h)g[h]&&bh(e[h],g[h])}if(b){bg(a,d);if(c){e=bi(a),g=bi(d);for(h=0;e[h];++h)bg(e[h],g[h])}}e=g=null;return d},clean:function(a,b,d,e){var g;b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);var h=[],i;for(var j=0,k;(k=a[j])!=null;j++){typeof k=="number"&&(k+="");if(!k)continue;if(typeof k=="string")if(!_.test(k))k=b.createTextNode(k);else{k=k.replace(Y,"<$1>");var l=(Z.exec(k)||["",""])[1].toLowerCase(),m=be[l]||be._default,n=m[0],o=b.createElement("div");o.innerHTML=m[1]+k+m[2];while(n--)o=o.lastChild;if(!f.support.tbody){var p=$.test(k),q=l==="table"&&!p?o.firstChild&&o.firstChild.childNodes:m[1]===""&&!p?o.childNodes:[];for(i=q.length-1;i>=0;--i)f.nodeName(q[i],"tbody")&&!q[i].childNodes.length&&q[i].parentNode.removeChild(q[i])}!f.support.leadingWhitespace&&X.test(k)&&o.insertBefore(b.createTextNode(X.exec(k)[0]),o.firstChild),k=o.childNodes}var r;if(!f.support.appendChecked)if(k[0]&&typeof (r=k.length)=="number")for(i=0;i=0)return b+"px"}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bn.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=f.isNaN(b)?"":"alpha(opacity="+b*100+")",g=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&f.trim(g.replace(bm,""))===""){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bm.test(g)?g.replace(bm,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){var c;f.swap(a,{display:"inline-block"},function(){b?c=bv(a,"margin-right","marginRight"):c=a.style.marginRight});return c}})}),c.defaultView&&c.defaultView.getComputedStyle&&(bw=function(a,c){var d,e,g;c=c.replace(bo,"-$1").toLowerCase();if(!(e=a.ownerDocument.defaultView))return b;if(g=e.getComputedStyle(a,null))d=g.getPropertyValue(c),d===""&&!f.contains(a.ownerDocument.documentElement,a)&&(d=f.style(a,c));return d}),c.documentElement.currentStyle&&(bx=function(a,b){var c,d=a.currentStyle&&a.currentStyle[b],e=a.runtimeStyle&&a.runtimeStyle[b],f=a.style;!bp.test(d)&&bq.test(d)&&(c=f.left,e&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":d||0,d=f.pixelLeft+"px",f.left=c,e&&(a.runtimeStyle.left=e));return d===""?"auto":d}),bv=bw||bx,f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)});var bz=/%20/g,bA=/\[\]$/,bB=/\r?\n/g,bC=/#.*$/,bD=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bE=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bF=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,bG=/^(?:GET|HEAD)$/,bH=/^\/\//,bI=/\?/,bJ=/)<[^<]*)*<\/script>/gi,bK=/^(?:select|textarea)/i,bL=/\s+/,bM=/([?&])_=[^&]*/,bN=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bO=f.fn.load,bP={},bQ={},bR,bS,bT=["*/"]+["*"];try{bR=e.href}catch(bU){bR=c.createElement("a"),bR.href="",bR=bR.href}bS=bN.exec(bR.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bO)return bO.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("
        ").append(c.replace(bJ,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bK.test(this.nodeName)||bE.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bB,"\r\n")}}):{name:b.name,value:c.replace(bB,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.bind(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?bX(a,f.ajaxSettings):(b=a,a=f.ajaxSettings),bX(a,b);return a},ajaxSettings:{url:bR,isLocal:bF.test(bS[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":bT},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:bV(bP),ajaxTransport:bV(bQ),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a>0?4:0;var o,r,u,w=c,x=l?bZ(d,v,l):b,y,z;if(a>=200&&a<300||a===304){if(d.ifModified){if(y=v.getResponseHeader("Last-Modified"))f.lastModified[k]=y;if(z=v.getResponseHeader("Etag"))f.etag[k]=z}if(a===304)w="notmodified",o=!0;else try{r=b$(d,x),w="success",o=!0}catch(A){w="parsererror",u=A}}else{u=w;if(!w||a)w="error",a<0&&(a=0)}v.status=a,v.statusText=""+(c||w),o?h.resolveWith(e,[r,w,v]):h.rejectWith(e,[v,w,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.resolveWith(e,[v,w]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f._Deferred(),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bD.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.done,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bC,"").replace(bH,bS[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bL),d.crossDomain==null&&(r=bN.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bS[1]&&r[2]==bS[2]&&(r[3]||(r[1]==="http:"?80:443))==(bS[3]||(bS[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),bW(bP,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bG.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bI.test(d.url)?"&":"?")+d.data,delete d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bM,"$1_="+x);d.url=y+(y===d.url?(bI.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", "+bT+"; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=bW(bQ,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){s<2?w(-1,z):f.error(z)}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)bY(g,a[g],c,e);return d.join("&").replace(bz,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var b_=f.now(),ca=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+b_++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=b.contentType==="application/x-www-form-urlencoded"&&typeof b.data=="string";if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(ca.test(b.url)||e&&ca.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(ca,l),b.url===j&&(e&&(k=k.replace(ca,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var cb=a.ActiveXObject?function(){for(var a in cd)cd[a](0,1)}:!1,cc=0,cd;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ce()||cf()}:ce,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,cb&&delete cd[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n),m.text=h.responseText;try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cc,cb&&(cd||(cd={},f(a).unload(cb)),cd[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var cg={},ch,ci,cj=/^(?:toggle|show|hide)$/,ck=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,cl,cm=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cn;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(cq("show",3),a,b,c);for(var g=0,h=this.length;g=e.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),e.animatedProperties[this.prop]=!0;for(g in e.animatedProperties)e.animatedProperties[g]!==!0&&(c=!1);if(c){e.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){d.style["overflow"+b]=e.overflow[a]}),e.hide&&f(d).hide();if(e.hide||e.show)for(var i in e.animatedProperties)f.style(d,i,e.orig[i]);e.complete.call(d)}return!1}e.duration==Infinity?this.now=b:(h=b-this.startTime,this.state=h/e.duration,this.pos=f.easing[e.animatedProperties[this.prop]](this.state,h,0,1,e.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){for(var a=f.timers,b=0;b
        ";f.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"}),b.innerHTML=j,a.insertBefore(b,a.firstChild),d=b.firstChild,e=d.firstChild,h=d.nextSibling.firstChild.firstChild,this.doesNotAddBorder=e.offsetTop!==5,this.doesAddBorderForTableAndCells=h.offsetTop===5,e.style.position="fixed",e.style.top="20px",this.supportsFixedPosition=e.offsetTop===20||e.offsetTop===15,e.style.position=e.style.top="",d.style.overflow="hidden",d.style.position="relative",this.subtractsBorderForOverflowNotVisible=e.offsetTop===-5,this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==i,a.removeChild(b),f.offset.initialize=f.noop},bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;f.offset.initialize(),f.offset.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(f.css(a,"marginTop"))||0,c+=parseFloat(f.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var d=f.css(a,"position");d==="static"&&(a.style.position="relative");var e=f(a),g=e.offset(),h=f.css(a,"top"),i=f.css(a,"left"),j=(d==="absolute"||d==="fixed")&&f.inArray("auto",[h,i])>-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=ct.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!ct.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each(["Left","Top"],function(a,c){var d="scroll"+c;f.fn[d]=function(c){var e,g;if(c===b){e=this[0];if(!e)return null;g=cu(e);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:f.support.boxModel&&g.document.documentElement[d]||g.document.body[d]:e[d]}return this.each(function(){g=cu(this),g?g.scrollTo(a?f(g).scrollLeft():c,a?c:f(g).scrollTop()):this[d]=c})}}),f.each(["Height","Width"],function(a,c){var d=c.toLowerCase();f.fn["inner"+c]=function(){var a=this[0];return a&&a.style?parseFloat(f.css(a,d,"padding")):null},f.fn["outer"+c]=function(a){var b=this[0];return b&&b.style?parseFloat(f.css(b,d,a?"margin":"border")):null},f.fn[d]=function(a){var e=this[0];if(!e)return a==null?null:this;if(f.isFunction(a))return this.each(function(b){var c=f(this);c[d](a.call(this,b,c[d]()))});if(f.isWindow(e)){var g=e.document.documentElement["client"+c],h=e.document.body;return e.document.compatMode==="CSS1Compat"&&g||h&&h["client"+c]||g}if(e.nodeType===9)return Math.max(e.documentElement["client"+c],e.body["scroll"+c],e.documentElement["scroll"+c],e.body["offset"+c],e.documentElement["offset"+c]);if(a===b){var i=f.css(e,d),j=parseFloat(i);return f.isNaN(j)?i:j}return this.css(d,typeof a=="string"?a:a+"px")}}),a.jQuery=a.$=f})(window); diff --git a/resources/js/vendor/jquery.sticky.js b/resources/js/vendor/jquery.sticky.js new file mode 100644 index 0000000000..f50486f357 --- /dev/null +++ b/resources/js/vendor/jquery.sticky.js @@ -0,0 +1,287 @@ +// Sticky Plugin v1.0.4 for jQuery +// ============= +// Author: Anthony Garand +// Improvements by German M. Bravo (Kronuz) and Ruud Kamphuis (ruudk) +// Improvements by Leonardo C. Daronco (daronco) +// Created: 02/14/2011 +// Date: 07/20/2015 +// Website: http://stickyjs.com/ +// Description: Makes an element on the page stick on the screen as you scroll +// It will only set the 'top' and 'position' of your element, you +// might need to adjust the width in some cases. + +(function (factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['jquery'], factory); + } else if (typeof module === 'object' && module.exports) { + // Node/CommonJS + module.exports = factory(require('jquery')); + } else { + // Browser globals + factory(jQuery); + } +}(function ($) { + var slice = Array.prototype.slice; // save ref to original slice() + var splice = Array.prototype.splice; // save ref to original slice() + + var defaults = { + topSpacing: 0, + bottomSpacing: 0, + className: 'is-sticky', + wrapperClassName: 'sticky-wrapper', + center: false, + getWidthFrom: '', + widthFromWrapper: true, // works only when .getWidthFrom is empty + responsiveWidth: false, + zIndex: 'auto' + }, + $window = $(window), + $document = $(document), + sticked = [], + windowHeight = $window.height(), + scroller = function() { + var scrollTop = $window.scrollTop(), + documentHeight = $document.height(), + dwh = documentHeight - windowHeight, + extra = (scrollTop > dwh) ? dwh - scrollTop : 0; + + for (var i = 0, l = sticked.length; i < l; i++) { + var s = sticked[i], + elementTop = s.stickyWrapper.offset().top, + etse = elementTop - s.topSpacing - extra; + + //update height in case of dynamic content + s.stickyWrapper.css('height', s.stickyElement.outerHeight()); + + if (scrollTop <= etse) { + if (s.currentTop !== null) { + s.stickyElement + .css({ + 'width': '', + 'position': '', + 'top': '', + 'z-index': '' + }); + s.stickyElement.parent().removeClass(s.className); + s.stickyElement.trigger('sticky-end', [s]); + s.currentTop = null; + } + } + else { + var newTop = documentHeight - s.stickyElement.outerHeight() + - s.topSpacing - s.bottomSpacing - scrollTop - extra; + if (newTop < 0) { + newTop = newTop + s.topSpacing; + } else { + newTop = s.topSpacing; + } + if (s.currentTop !== newTop) { + var newWidth; + if (s.getWidthFrom) { + newWidth = $(s.getWidthFrom).width() || null; + } else if (s.widthFromWrapper) { + newWidth = s.stickyWrapper.width(); + } + if (newWidth == null) { + newWidth = s.stickyElement.width(); + } + s.stickyElement + .css('width', newWidth) + .css('position', 'fixed') + .css('top', newTop) + .css('z-index', s.zIndex); + + s.stickyElement.parent().addClass(s.className); + + if (s.currentTop === null) { + s.stickyElement.trigger('sticky-start', [s]); + } else { + // sticky is started but it have to be repositioned + s.stickyElement.trigger('sticky-update', [s]); + } + + if (s.currentTop === s.topSpacing && s.currentTop > newTop || s.currentTop === null && newTop < s.topSpacing) { + // just reached bottom || just started to stick but bottom is already reached + s.stickyElement.trigger('sticky-bottom-reached', [s]); + } else if(s.currentTop !== null && newTop === s.topSpacing && s.currentTop < newTop) { + // sticky is started && sticked at topSpacing && overflowing from top just finished + s.stickyElement.trigger('sticky-bottom-unreached', [s]); + } + + s.currentTop = newTop; + } + + // Check if sticky has reached end of container and stop sticking + var stickyWrapperContainer = s.stickyWrapper.parent(); + var unstick = (s.stickyElement.offset().top + s.stickyElement.outerHeight() >= stickyWrapperContainer.offset().top + stickyWrapperContainer.outerHeight()) && (s.stickyElement.offset().top <= s.topSpacing); + + if( unstick ) { + s.stickyElement + .css('position', 'absolute') + .css('top', '') + .css('bottom', 0) + .css('z-index', ''); + } else { + s.stickyElement + .css('position', 'fixed') + .css('top', newTop) + .css('bottom', '') + .css('z-index', s.zIndex); + } + } + } + }, + resizer = function() { + windowHeight = $window.height(); + + for (var i = 0, l = sticked.length; i < l; i++) { + var s = sticked[i]; + var newWidth = null; + if (s.getWidthFrom) { + if (s.responsiveWidth) { + newWidth = $(s.getWidthFrom).width(); + } + } else if(s.widthFromWrapper) { + newWidth = s.stickyWrapper.width(); + } + if (newWidth != null) { + s.stickyElement.css('width', newWidth); + } + } + }, + methods = { + init: function(options) { + return this.each(function() { + var o = $.extend({}, defaults, options); + var stickyElement = $(this); + + var stickyId = stickyElement.attr('id'); + var wrapperId = stickyId ? stickyId + '-' + defaults.wrapperClassName : defaults.wrapperClassName; + var wrapper = $('
        ') + .attr('id', wrapperId) + .addClass(o.wrapperClassName); + + stickyElement.wrapAll(function() { + if ($(this).parent("#" + wrapperId).length == 0) { + return wrapper; + } +}); + + var stickyWrapper = stickyElement.parent(); + + if (o.center) { + stickyWrapper.css({width:stickyElement.outerWidth(),marginLeft:"auto",marginRight:"auto"}); + } + + if (stickyElement.css("float") === "right") { + stickyElement.css({"float":"none"}).parent().css({"float":"right"}); + } + + o.stickyElement = stickyElement; + o.stickyWrapper = stickyWrapper; + o.currentTop = null; + + sticked.push(o); + + methods.setWrapperHeight(this); + methods.setupChangeListeners(this); + }); + }, + + setWrapperHeight: function(stickyElement) { + var element = $(stickyElement); + var stickyWrapper = element.parent(); + if (stickyWrapper) { + stickyWrapper.css('height', element.outerHeight()); + } + }, + + setupChangeListeners: function(stickyElement) { + if (window.MutationObserver) { + var mutationObserver = new window.MutationObserver(function(mutations) { + if (mutations[0].addedNodes.length || mutations[0].removedNodes.length) { + methods.setWrapperHeight(stickyElement); + } + }); + mutationObserver.observe(stickyElement, {subtree: true, childList: true}); + } else { + if (window.addEventListener) { + stickyElement.addEventListener('DOMNodeInserted', function() { + methods.setWrapperHeight(stickyElement); + }, false); + stickyElement.addEventListener('DOMNodeRemoved', function() { + methods.setWrapperHeight(stickyElement); + }, false); + } else if (window.attachEvent) { + stickyElement.attachEvent('onDOMNodeInserted', function() { + methods.setWrapperHeight(stickyElement); + }); + stickyElement.attachEvent('onDOMNodeRemoved', function() { + methods.setWrapperHeight(stickyElement); + }); + } + } + }, + update: scroller, + unstick: function(options) { + return this.each(function() { + var that = this; + var unstickyElement = $(that); + + var removeIdx = -1; + var i = sticked.length; + while (i-- > 0) { + if (sticked[i].stickyElement.get(0) === that) { + splice.call(sticked,i,1); + removeIdx = i; + } + } + if(removeIdx !== -1) { + unstickyElement.unwrap(); + unstickyElement + .css({ + 'width': '', + 'position': '', + 'top': '', + 'float': '', + 'z-index': '' + }) + ; + } + }); + } + }; + + // should be more efficient than using $window.scroll(scroller) and $window.resize(resizer): + if (window.addEventListener) { + window.addEventListener('scroll', scroller, false); + window.addEventListener('resize', resizer, false); + } else if (window.attachEvent) { + window.attachEvent('onscroll', scroller); + window.attachEvent('onresize', resizer); + } + + $.fn.sticky = function(method) { + if (methods[method]) { + return methods[method].apply(this, slice.call(arguments, 1)); + } else if (typeof method === 'object' || !method ) { + return methods.init.apply( this, arguments ); + } else { + $.error('Method ' + method + ' does not exist on jQuery.sticky'); + } + }; + + $.fn.unstick = function(method) { + if (methods[method]) { + return methods[method].apply(this, slice.call(arguments, 1)); + } else if (typeof method === 'object' || !method ) { + return methods.unstick.apply( this, arguments ); + } else { + $.error('Method ' + method + ' does not exist on jQuery.sticky'); + } + }; + $(function() { + setTimeout(scroller, 0); + }); +})); diff --git a/resources/js/vendor/moment.min.js b/resources/js/vendor/moment.min.js new file mode 100644 index 0000000000..25fa625cca --- /dev/null +++ b/resources/js/vendor/moment.min.js @@ -0,0 +1,7 @@ +//! moment.js +//! version : 2.18.1 +//! authors : Tim Wood, Iskren Chernev, Moment.js contributors +//! license : MIT +//! momentjs.com +!function(a,b){"object"==typeof exports&&"undefined"!=typeof module?module.exports=b():"function"==typeof define&&define.amd?define(b):a.moment=b()}(this,function(){"use strict";function a(){return sd.apply(null,arguments)}function b(a){sd=a}function c(a){return a instanceof Array||"[object Array]"===Object.prototype.toString.call(a)}function d(a){return null!=a&&"[object Object]"===Object.prototype.toString.call(a)}function e(a){var b;for(b in a)return!1;return!0}function f(a){return void 0===a}function g(a){return"number"==typeof a||"[object Number]"===Object.prototype.toString.call(a)}function h(a){return a instanceof Date||"[object Date]"===Object.prototype.toString.call(a)}function i(a,b){var c,d=[];for(c=0;c0)for(c=0;c0?"future":"past"];return z(c)?c(b):c.replace(/%s/i,b)}function J(a,b){var c=a.toLowerCase();Hd[c]=Hd[c+"s"]=Hd[b]=a}function K(a){return"string"==typeof a?Hd[a]||Hd[a.toLowerCase()]:void 0}function L(a){var b,c,d={};for(c in a)j(a,c)&&(b=K(c),b&&(d[b]=a[c]));return d}function M(a,b){Id[a]=b}function N(a){var b=[];for(var c in a)b.push({unit:c,priority:Id[c]});return b.sort(function(a,b){return a.priority-b.priority}),b}function O(b,c){return function(d){return null!=d?(Q(this,b,d),a.updateOffset(this,c),this):P(this,b)}}function P(a,b){return a.isValid()?a._d["get"+(a._isUTC?"UTC":"")+b]():NaN}function Q(a,b,c){a.isValid()&&a._d["set"+(a._isUTC?"UTC":"")+b](c)}function R(a){return a=K(a),z(this[a])?this[a]():this}function S(a,b){if("object"==typeof a){a=L(a);for(var c=N(a),d=0;d=0;return(f?c?"+":"":"-")+Math.pow(10,Math.max(0,e)).toString().substr(1)+d}function U(a,b,c,d){var e=d;"string"==typeof d&&(e=function(){return this[d]()}),a&&(Md[a]=e),b&&(Md[b[0]]=function(){return T(e.apply(this,arguments),b[1],b[2])}),c&&(Md[c]=function(){return this.localeData().ordinal(e.apply(this,arguments),a)})}function V(a){return a.match(/\[[\s\S]/)?a.replace(/^\[|\]$/g,""):a.replace(/\\/g,"")}function W(a){var b,c,d=a.match(Jd);for(b=0,c=d.length;b=0&&Kd.test(a);)a=a.replace(Kd,c),Kd.lastIndex=0,d-=1;return a}function Z(a,b,c){ce[a]=z(b)?b:function(a,d){return a&&c?c:b}}function $(a,b){return j(ce,a)?ce[a](b._strict,b._locale):new RegExp(_(a))}function _(a){return aa(a.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(a,b,c,d,e){return b||c||d||e}))}function aa(a){return a.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function ba(a,b){var c,d=b;for("string"==typeof a&&(a=[a]),g(b)&&(d=function(a,c){c[b]=u(a)}),c=0;c=0&&isFinite(h.getFullYear())&&h.setFullYear(a),h}function ta(a){var b=new Date(Date.UTC.apply(null,arguments));return a<100&&a>=0&&isFinite(b.getUTCFullYear())&&b.setUTCFullYear(a),b}function ua(a,b,c){var d=7+b-c,e=(7+ta(a,0,d).getUTCDay()-b)%7;return-e+d-1}function va(a,b,c,d,e){var f,g,h=(7+c-d)%7,i=ua(a,d,e),j=1+7*(b-1)+h+i;return j<=0?(f=a-1,g=pa(f)+j):j>pa(a)?(f=a+1,g=j-pa(a)):(f=a,g=j),{year:f,dayOfYear:g}}function wa(a,b,c){var d,e,f=ua(a.year(),b,c),g=Math.floor((a.dayOfYear()-f-1)/7)+1;return g<1?(e=a.year()-1,d=g+xa(e,b,c)):g>xa(a.year(),b,c)?(d=g-xa(a.year(),b,c),e=a.year()+1):(e=a.year(),d=g),{week:d,year:e}}function xa(a,b,c){var d=ua(a,b,c),e=ua(a+1,b,c);return(pa(a)-d+e)/7}function ya(a){return wa(a,this._week.dow,this._week.doy).week}function za(){return this._week.dow}function Aa(){return this._week.doy}function Ba(a){var b=this.localeData().week(this);return null==a?b:this.add(7*(a-b),"d")}function Ca(a){var b=wa(this,1,4).week;return null==a?b:this.add(7*(a-b),"d")}function Da(a,b){return"string"!=typeof a?a:isNaN(a)?(a=b.weekdaysParse(a),"number"==typeof a?a:null):parseInt(a,10)}function Ea(a,b){return"string"==typeof a?b.weekdaysParse(a)%7||7:isNaN(a)?null:a}function Fa(a,b){return a?c(this._weekdays)?this._weekdays[a.day()]:this._weekdays[this._weekdays.isFormat.test(b)?"format":"standalone"][a.day()]:c(this._weekdays)?this._weekdays:this._weekdays.standalone}function Ga(a){return a?this._weekdaysShort[a.day()]:this._weekdaysShort}function Ha(a){return a?this._weekdaysMin[a.day()]:this._weekdaysMin}function Ia(a,b,c){var d,e,f,g=a.toLocaleLowerCase();if(!this._weekdaysParse)for(this._weekdaysParse=[],this._shortWeekdaysParse=[],this._minWeekdaysParse=[],d=0;d<7;++d)f=l([2e3,1]).day(d),this._minWeekdaysParse[d]=this.weekdaysMin(f,"").toLocaleLowerCase(),this._shortWeekdaysParse[d]=this.weekdaysShort(f,"").toLocaleLowerCase(),this._weekdaysParse[d]=this.weekdays(f,"").toLocaleLowerCase();return c?"dddd"===b?(e=ne.call(this._weekdaysParse,g),e!==-1?e:null):"ddd"===b?(e=ne.call(this._shortWeekdaysParse,g),e!==-1?e:null):(e=ne.call(this._minWeekdaysParse,g),e!==-1?e:null):"dddd"===b?(e=ne.call(this._weekdaysParse,g),e!==-1?e:(e=ne.call(this._shortWeekdaysParse,g),e!==-1?e:(e=ne.call(this._minWeekdaysParse,g),e!==-1?e:null))):"ddd"===b?(e=ne.call(this._shortWeekdaysParse,g),e!==-1?e:(e=ne.call(this._weekdaysParse,g),e!==-1?e:(e=ne.call(this._minWeekdaysParse,g),e!==-1?e:null))):(e=ne.call(this._minWeekdaysParse,g),e!==-1?e:(e=ne.call(this._weekdaysParse,g),e!==-1?e:(e=ne.call(this._shortWeekdaysParse,g),e!==-1?e:null)))}function Ja(a,b,c){var d,e,f;if(this._weekdaysParseExact)return Ia.call(this,a,b,c);for(this._weekdaysParse||(this._weekdaysParse=[],this._minWeekdaysParse=[],this._shortWeekdaysParse=[],this._fullWeekdaysParse=[]),d=0;d<7;d++){if(e=l([2e3,1]).day(d),c&&!this._fullWeekdaysParse[d]&&(this._fullWeekdaysParse[d]=new RegExp("^"+this.weekdays(e,"").replace(".",".?")+"$","i"),this._shortWeekdaysParse[d]=new RegExp("^"+this.weekdaysShort(e,"").replace(".",".?")+"$","i"),this._minWeekdaysParse[d]=new RegExp("^"+this.weekdaysMin(e,"").replace(".",".?")+"$","i")),this._weekdaysParse[d]||(f="^"+this.weekdays(e,"")+"|^"+this.weekdaysShort(e,"")+"|^"+this.weekdaysMin(e,""),this._weekdaysParse[d]=new RegExp(f.replace(".",""),"i")),c&&"dddd"===b&&this._fullWeekdaysParse[d].test(a))return d;if(c&&"ddd"===b&&this._shortWeekdaysParse[d].test(a))return d;if(c&&"dd"===b&&this._minWeekdaysParse[d].test(a))return d;if(!c&&this._weekdaysParse[d].test(a))return d}}function Ka(a){if(!this.isValid())return null!=a?this:NaN;var b=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=a?(a=Da(a,this.localeData()),this.add(a-b,"d")):b}function La(a){if(!this.isValid())return null!=a?this:NaN;var b=(this.day()+7-this.localeData()._week.dow)%7;return null==a?b:this.add(a-b,"d")}function Ma(a){if(!this.isValid())return null!=a?this:NaN;if(null!=a){var b=Ea(a,this.localeData());return this.day(this.day()%7?b:b-7)}return this.day()||7}function Na(a){return this._weekdaysParseExact?(j(this,"_weekdaysRegex")||Qa.call(this),a?this._weekdaysStrictRegex:this._weekdaysRegex):(j(this,"_weekdaysRegex")||(this._weekdaysRegex=ye),this._weekdaysStrictRegex&&a?this._weekdaysStrictRegex:this._weekdaysRegex)}function Oa(a){return this._weekdaysParseExact?(j(this,"_weekdaysRegex")||Qa.call(this),a?this._weekdaysShortStrictRegex:this._weekdaysShortRegex):(j(this,"_weekdaysShortRegex")||(this._weekdaysShortRegex=ze),this._weekdaysShortStrictRegex&&a?this._weekdaysShortStrictRegex:this._weekdaysShortRegex)}function Pa(a){return this._weekdaysParseExact?(j(this,"_weekdaysRegex")||Qa.call(this),a?this._weekdaysMinStrictRegex:this._weekdaysMinRegex):(j(this,"_weekdaysMinRegex")||(this._weekdaysMinRegex=Ae),this._weekdaysMinStrictRegex&&a?this._weekdaysMinStrictRegex:this._weekdaysMinRegex)}function Qa(){function a(a,b){return b.length-a.length}var b,c,d,e,f,g=[],h=[],i=[],j=[];for(b=0;b<7;b++)c=l([2e3,1]).day(b),d=this.weekdaysMin(c,""),e=this.weekdaysShort(c,""),f=this.weekdays(c,""),g.push(d),h.push(e),i.push(f),j.push(d),j.push(e),j.push(f);for(g.sort(a),h.sort(a),i.sort(a),j.sort(a),b=0;b<7;b++)h[b]=aa(h[b]),i[b]=aa(i[b]),j[b]=aa(j[b]);this._weekdaysRegex=new RegExp("^("+j.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+i.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+h.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+g.join("|")+")","i")}function Ra(){return this.hours()%12||12}function Sa(){return this.hours()||24}function Ta(a,b){U(a,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),b)})}function Ua(a,b){return b._meridiemParse}function Va(a){return"p"===(a+"").toLowerCase().charAt(0)}function Wa(a,b,c){return a>11?c?"pm":"PM":c?"am":"AM"}function Xa(a){return a?a.toLowerCase().replace("_","-"):a}function Ya(a){for(var b,c,d,e,f=0;f0;){if(d=Za(e.slice(0,b).join("-")))return d;if(c&&c.length>=b&&v(e,c,!0)>=b-1)break;b--}f++}return null}function Za(a){var b=null;if(!Fe[a]&&"undefined"!=typeof module&&module&&module.exports)try{b=Be._abbr,require("./locale/"+a),$a(b)}catch(a){}return Fe[a]}function $a(a,b){var c;return a&&(c=f(b)?bb(a):_a(a,b),c&&(Be=c)),Be._abbr}function _a(a,b){if(null!==b){var c=Ee;if(b.abbr=a,null!=Fe[a])y("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info."),c=Fe[a]._config;else if(null!=b.parentLocale){if(null==Fe[b.parentLocale])return Ge[b.parentLocale]||(Ge[b.parentLocale]=[]),Ge[b.parentLocale].push({name:a,config:b}),null;c=Fe[b.parentLocale]._config}return Fe[a]=new C(B(c,b)),Ge[a]&&Ge[a].forEach(function(a){_a(a.name,a.config)}),$a(a),Fe[a]}return delete Fe[a],null}function ab(a,b){if(null!=b){var c,d=Ee;null!=Fe[a]&&(d=Fe[a]._config),b=B(d,b),c=new C(b),c.parentLocale=Fe[a],Fe[a]=c,$a(a)}else null!=Fe[a]&&(null!=Fe[a].parentLocale?Fe[a]=Fe[a].parentLocale:null!=Fe[a]&&delete Fe[a]);return Fe[a]}function bb(a){var b;if(a&&a._locale&&a._locale._abbr&&(a=a._locale._abbr),!a)return Be;if(!c(a)){if(b=Za(a))return b;a=[a]}return Ya(a)}function cb(){return Ad(Fe)}function db(a){var b,c=a._a;return c&&n(a).overflow===-2&&(b=c[fe]<0||c[fe]>11?fe:c[ge]<1||c[ge]>ea(c[ee],c[fe])?ge:c[he]<0||c[he]>24||24===c[he]&&(0!==c[ie]||0!==c[je]||0!==c[ke])?he:c[ie]<0||c[ie]>59?ie:c[je]<0||c[je]>59?je:c[ke]<0||c[ke]>999?ke:-1,n(a)._overflowDayOfYear&&(bge)&&(b=ge),n(a)._overflowWeeks&&b===-1&&(b=le),n(a)._overflowWeekday&&b===-1&&(b=me),n(a).overflow=b),a}function eb(a){var b,c,d,e,f,g,h=a._i,i=He.exec(h)||Ie.exec(h);if(i){for(n(a).iso=!0,b=0,c=Ke.length;b10?"YYYY ":"YY "),f="HH:mm"+(c[4]?":ss":""),c[1]){var l=new Date(c[2]),m=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"][l.getDay()];if(c[1].substr(0,3)!==m)return n(a).weekdayMismatch=!0,void(a._isValid=!1)}switch(c[5].length){case 2:0===i?h=" +0000":(i=k.indexOf(c[5][1].toUpperCase())-12,h=(i<0?" -":" +")+(""+i).replace(/^-?/,"0").match(/..$/)[0]+"00");break;case 4:h=j[c[5]];break;default:h=j[" GMT"]}c[5]=h,a._i=c.splice(1).join(""),g=" ZZ",a._f=d+e+f+g,lb(a),n(a).rfc2822=!0}else a._isValid=!1}function gb(b){var c=Me.exec(b._i);return null!==c?void(b._d=new Date(+c[1])):(eb(b),void(b._isValid===!1&&(delete b._isValid,fb(b),b._isValid===!1&&(delete b._isValid,a.createFromInputFallback(b)))))}function hb(a,b,c){return null!=a?a:null!=b?b:c}function ib(b){var c=new Date(a.now());return b._useUTC?[c.getUTCFullYear(),c.getUTCMonth(),c.getUTCDate()]:[c.getFullYear(),c.getMonth(),c.getDate()]}function jb(a){var b,c,d,e,f=[];if(!a._d){for(d=ib(a),a._w&&null==a._a[ge]&&null==a._a[fe]&&kb(a),null!=a._dayOfYear&&(e=hb(a._a[ee],d[ee]),(a._dayOfYear>pa(e)||0===a._dayOfYear)&&(n(a)._overflowDayOfYear=!0),c=ta(e,0,a._dayOfYear),a._a[fe]=c.getUTCMonth(),a._a[ge]=c.getUTCDate()),b=0;b<3&&null==a._a[b];++b)a._a[b]=f[b]=d[b];for(;b<7;b++)a._a[b]=f[b]=null==a._a[b]?2===b?1:0:a._a[b];24===a._a[he]&&0===a._a[ie]&&0===a._a[je]&&0===a._a[ke]&&(a._nextDay=!0,a._a[he]=0),a._d=(a._useUTC?ta:sa).apply(null,f),null!=a._tzm&&a._d.setUTCMinutes(a._d.getUTCMinutes()-a._tzm),a._nextDay&&(a._a[he]=24)}}function kb(a){var b,c,d,e,f,g,h,i;if(b=a._w,null!=b.GG||null!=b.W||null!=b.E)f=1,g=4,c=hb(b.GG,a._a[ee],wa(tb(),1,4).year),d=hb(b.W,1),e=hb(b.E,1),(e<1||e>7)&&(i=!0);else{f=a._locale._week.dow,g=a._locale._week.doy;var j=wa(tb(),f,g);c=hb(b.gg,a._a[ee],j.year),d=hb(b.w,j.week),null!=b.d?(e=b.d,(e<0||e>6)&&(i=!0)):null!=b.e?(e=b.e+f,(b.e<0||b.e>6)&&(i=!0)):e=f}d<1||d>xa(c,f,g)?n(a)._overflowWeeks=!0:null!=i?n(a)._overflowWeekday=!0:(h=va(c,d,e,f,g),a._a[ee]=h.year,a._dayOfYear=h.dayOfYear)}function lb(b){if(b._f===a.ISO_8601)return void eb(b);if(b._f===a.RFC_2822)return void fb(b);b._a=[],n(b).empty=!0;var c,d,e,f,g,h=""+b._i,i=h.length,j=0;for(e=Y(b._f,b._locale).match(Jd)||[],c=0;c0&&n(b).unusedInput.push(g),h=h.slice(h.indexOf(d)+d.length),j+=d.length),Md[f]?(d?n(b).empty=!1:n(b).unusedTokens.push(f),da(f,d,b)):b._strict&&!d&&n(b).unusedTokens.push(f);n(b).charsLeftOver=i-j,h.length>0&&n(b).unusedInput.push(h),b._a[he]<=12&&n(b).bigHour===!0&&b._a[he]>0&&(n(b).bigHour=void 0),n(b).parsedDateParts=b._a.slice(0),n(b).meridiem=b._meridiem,b._a[he]=mb(b._locale,b._a[he],b._meridiem),jb(b),db(b)}function mb(a,b,c){var d;return null==c?b:null!=a.meridiemHour?a.meridiemHour(b,c):null!=a.isPM?(d=a.isPM(c),d&&b<12&&(b+=12),d||12!==b||(b=0),b):b}function nb(a){var b,c,d,e,f;if(0===a._f.length)return n(a).invalidFormat=!0,void(a._d=new Date(NaN));for(e=0;ethis.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()}function Ob(){if(!f(this._isDSTShifted))return this._isDSTShifted;var a={};if(q(a,this),a=qb(a),a._a){var b=a._isUTC?l(a._a):tb(a._a);this._isDSTShifted=this.isValid()&&v(a._a,b.toArray())>0}else this._isDSTShifted=!1;return this._isDSTShifted}function Pb(){return!!this.isValid()&&!this._isUTC}function Qb(){return!!this.isValid()&&this._isUTC}function Rb(){return!!this.isValid()&&(this._isUTC&&0===this._offset)}function Sb(a,b){var c,d,e,f=a,h=null;return Bb(a)?f={ms:a._milliseconds,d:a._days,M:a._months}:g(a)?(f={},b?f[b]=a:f.milliseconds=a):(h=Te.exec(a))?(c="-"===h[1]?-1:1,f={y:0,d:u(h[ge])*c,h:u(h[he])*c,m:u(h[ie])*c,s:u(h[je])*c,ms:u(Cb(1e3*h[ke]))*c}):(h=Ue.exec(a))?(c="-"===h[1]?-1:1,f={y:Tb(h[2],c),M:Tb(h[3],c),w:Tb(h[4],c),d:Tb(h[5],c),h:Tb(h[6],c),m:Tb(h[7],c),s:Tb(h[8],c)}):null==f?f={}:"object"==typeof f&&("from"in f||"to"in f)&&(e=Vb(tb(f.from),tb(f.to)),f={},f.ms=e.milliseconds,f.M=e.months),d=new Ab(f),Bb(a)&&j(a,"_locale")&&(d._locale=a._locale),d}function Tb(a,b){var c=a&&parseFloat(a.replace(",","."));return(isNaN(c)?0:c)*b}function Ub(a,b){var c={milliseconds:0,months:0};return c.months=b.month()-a.month()+12*(b.year()-a.year()),a.clone().add(c.months,"M").isAfter(b)&&--c.months,c.milliseconds=+b-+a.clone().add(c.months,"M"),c}function Vb(a,b){var c;return a.isValid()&&b.isValid()?(b=Fb(b,a),a.isBefore(b)?c=Ub(a,b):(c=Ub(b,a),c.milliseconds=-c.milliseconds,c.months=-c.months),c):{milliseconds:0,months:0}}function Wb(a,b){return function(c,d){var e,f;return null===d||isNaN(+d)||(y(b,"moment()."+b+"(period, number) is deprecated. Please use moment()."+b+"(number, period). See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info."),f=c,c=d,d=f),c="string"==typeof c?+c:c,e=Sb(c,d),Xb(this,e,a),this}}function Xb(b,c,d,e){var f=c._milliseconds,g=Cb(c._days),h=Cb(c._months);b.isValid()&&(e=null==e||e,f&&b._d.setTime(b._d.valueOf()+f*d),g&&Q(b,"Date",P(b,"Date")+g*d),h&&ja(b,P(b,"Month")+h*d),e&&a.updateOffset(b,g||h))}function Yb(a,b){var c=a.diff(b,"days",!0);return c<-6?"sameElse":c<-1?"lastWeek":c<0?"lastDay":c<1?"sameDay":c<2?"nextDay":c<7?"nextWeek":"sameElse"}function Zb(b,c){var d=b||tb(),e=Fb(d,this).startOf("day"),f=a.calendarFormat(this,e)||"sameElse",g=c&&(z(c[f])?c[f].call(this,d):c[f]);return this.format(g||this.localeData().calendar(f,this,tb(d)))}function $b(){return new r(this)}function _b(a,b){var c=s(a)?a:tb(a);return!(!this.isValid()||!c.isValid())&&(b=K(f(b)?"millisecond":b),"millisecond"===b?this.valueOf()>c.valueOf():c.valueOf()9999?X(a,"YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]"):z(Date.prototype.toISOString)?this.toDate().toISOString():X(a,"YYYY-MM-DD[T]HH:mm:ss.SSS[Z]")}function jc(){if(!this.isValid())return"moment.invalid(/* "+this._i+" */)";var a="moment",b="";this.isLocal()||(a=0===this.utcOffset()?"moment.utc":"moment.parseZone",b="Z");var c="["+a+'("]',d=0<=this.year()&&this.year()<=9999?"YYYY":"YYYYYY",e="-MM-DD[T]HH:mm:ss.SSS",f=b+'[")]';return this.format(c+d+e+f)}function kc(b){b||(b=this.isUtc()?a.defaultFormatUtc:a.defaultFormat);var c=X(this,b);return this.localeData().postformat(c)}function lc(a,b){return this.isValid()&&(s(a)&&a.isValid()||tb(a).isValid())?Sb({to:this,from:a}).locale(this.locale()).humanize(!b):this.localeData().invalidDate()}function mc(a){return this.from(tb(),a)}function nc(a,b){return this.isValid()&&(s(a)&&a.isValid()||tb(a).isValid())?Sb({from:this,to:a}).locale(this.locale()).humanize(!b):this.localeData().invalidDate()}function oc(a){return this.to(tb(),a)}function pc(a){var b;return void 0===a?this._locale._abbr:(b=bb(a),null!=b&&(this._locale=b),this)}function qc(){return this._locale}function rc(a){switch(a=K(a)){case"year":this.month(0);case"quarter":case"month":this.date(1);case"week":case"isoWeek":case"day":case"date":this.hours(0);case"hour":this.minutes(0);case"minute":this.seconds(0);case"second":this.milliseconds(0)}return"week"===a&&this.weekday(0),"isoWeek"===a&&this.isoWeekday(1),"quarter"===a&&this.month(3*Math.floor(this.month()/3)),this}function sc(a){return a=K(a),void 0===a||"millisecond"===a?this:("date"===a&&(a="day"),this.startOf(a).add(1,"isoWeek"===a?"week":a).subtract(1,"ms"))}function tc(){return this._d.valueOf()-6e4*(this._offset||0)}function uc(){return Math.floor(this.valueOf()/1e3)}function vc(){return new Date(this.valueOf())}function wc(){var a=this;return[a.year(),a.month(),a.date(),a.hour(),a.minute(),a.second(),a.millisecond()]}function xc(){var a=this;return{years:a.year(),months:a.month(),date:a.date(),hours:a.hours(),minutes:a.minutes(),seconds:a.seconds(),milliseconds:a.milliseconds()}}function yc(){return this.isValid()?this.toISOString():null}function zc(){return o(this)}function Ac(){ +return k({},n(this))}function Bc(){return n(this).overflow}function Cc(){return{input:this._i,format:this._f,locale:this._locale,isUTC:this._isUTC,strict:this._strict}}function Dc(a,b){U(0,[a,a.length],0,b)}function Ec(a){return Ic.call(this,a,this.week(),this.weekday(),this.localeData()._week.dow,this.localeData()._week.doy)}function Fc(a){return Ic.call(this,a,this.isoWeek(),this.isoWeekday(),1,4)}function Gc(){return xa(this.year(),1,4)}function Hc(){var a=this.localeData()._week;return xa(this.year(),a.dow,a.doy)}function Ic(a,b,c,d,e){var f;return null==a?wa(this,d,e).year:(f=xa(a,d,e),b>f&&(b=f),Jc.call(this,a,b,c,d,e))}function Jc(a,b,c,d,e){var f=va(a,b,c,d,e),g=ta(f.year,0,f.dayOfYear);return this.year(g.getUTCFullYear()),this.month(g.getUTCMonth()),this.date(g.getUTCDate()),this}function Kc(a){return null==a?Math.ceil((this.month()+1)/3):this.month(3*(a-1)+this.month()%3)}function Lc(a){var b=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1;return null==a?b:this.add(a-b,"d")}function Mc(a,b){b[ke]=u(1e3*("0."+a))}function Nc(){return this._isUTC?"UTC":""}function Oc(){return this._isUTC?"Coordinated Universal Time":""}function Pc(a){return tb(1e3*a)}function Qc(){return tb.apply(null,arguments).parseZone()}function Rc(a){return a}function Sc(a,b,c,d){var e=bb(),f=l().set(d,b);return e[c](f,a)}function Tc(a,b,c){if(g(a)&&(b=a,a=void 0),a=a||"",null!=b)return Sc(a,b,c,"month");var d,e=[];for(d=0;d<12;d++)e[d]=Sc(a,d,c,"month");return e}function Uc(a,b,c,d){"boolean"==typeof a?(g(b)&&(c=b,b=void 0),b=b||""):(b=a,c=b,a=!1,g(b)&&(c=b,b=void 0),b=b||"");var e=bb(),f=a?e._week.dow:0;if(null!=c)return Sc(b,(c+f)%7,d,"day");var h,i=[];for(h=0;h<7;h++)i[h]=Sc(b,(h+f)%7,d,"day");return i}function Vc(a,b){return Tc(a,b,"months")}function Wc(a,b){return Tc(a,b,"monthsShort")}function Xc(a,b,c){return Uc(a,b,c,"weekdays")}function Yc(a,b,c){return Uc(a,b,c,"weekdaysShort")}function Zc(a,b,c){return Uc(a,b,c,"weekdaysMin")}function $c(){var a=this._data;return this._milliseconds=df(this._milliseconds),this._days=df(this._days),this._months=df(this._months),a.milliseconds=df(a.milliseconds),a.seconds=df(a.seconds),a.minutes=df(a.minutes),a.hours=df(a.hours),a.months=df(a.months),a.years=df(a.years),this}function _c(a,b,c,d){var e=Sb(b,c);return a._milliseconds+=d*e._milliseconds,a._days+=d*e._days,a._months+=d*e._months,a._bubble()}function ad(a,b){return _c(this,a,b,1)}function bd(a,b){return _c(this,a,b,-1)}function cd(a){return a<0?Math.floor(a):Math.ceil(a)}function dd(){var a,b,c,d,e,f=this._milliseconds,g=this._days,h=this._months,i=this._data;return f>=0&&g>=0&&h>=0||f<=0&&g<=0&&h<=0||(f+=864e5*cd(fd(h)+g),g=0,h=0),i.milliseconds=f%1e3,a=t(f/1e3),i.seconds=a%60,b=t(a/60),i.minutes=b%60,c=t(b/60),i.hours=c%24,g+=t(c/24),e=t(ed(g)),h+=e,g-=cd(fd(e)),d=t(h/12),h%=12,i.days=g,i.months=h,i.years=d,this}function ed(a){return 4800*a/146097}function fd(a){return 146097*a/4800}function gd(a){if(!this.isValid())return NaN;var b,c,d=this._milliseconds;if(a=K(a),"month"===a||"year"===a)return b=this._days+d/864e5,c=this._months+ed(b),"month"===a?c:c/12;switch(b=this._days+Math.round(fd(this._months)),a){case"week":return b/7+d/6048e5;case"day":return b+d/864e5;case"hour":return 24*b+d/36e5;case"minute":return 1440*b+d/6e4;case"second":return 86400*b+d/1e3;case"millisecond":return Math.floor(864e5*b)+d;default:throw new Error("Unknown unit "+a)}}function hd(){return this.isValid()?this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*u(this._months/12):NaN}function id(a){return function(){return this.as(a)}}function jd(a){return a=K(a),this.isValid()?this[a+"s"]():NaN}function kd(a){return function(){return this.isValid()?this._data[a]:NaN}}function ld(){return t(this.days()/7)}function md(a,b,c,d,e){return e.relativeTime(b||1,!!c,a,d)}function nd(a,b,c){var d=Sb(a).abs(),e=uf(d.as("s")),f=uf(d.as("m")),g=uf(d.as("h")),h=uf(d.as("d")),i=uf(d.as("M")),j=uf(d.as("y")),k=e<=vf.ss&&["s",e]||e0,k[4]=c,md.apply(null,k)}function od(a){return void 0===a?uf:"function"==typeof a&&(uf=a,!0)}function pd(a,b){return void 0!==vf[a]&&(void 0===b?vf[a]:(vf[a]=b,"s"===a&&(vf.ss=b-1),!0))}function qd(a){if(!this.isValid())return this.localeData().invalidDate();var b=this.localeData(),c=nd(this,!a,b);return a&&(c=b.pastFuture(+this,c)),b.postformat(c)}function rd(){if(!this.isValid())return this.localeData().invalidDate();var a,b,c,d=wf(this._milliseconds)/1e3,e=wf(this._days),f=wf(this._months);a=t(d/60),b=t(a/60),d%=60,a%=60,c=t(f/12),f%=12;var g=c,h=f,i=e,j=b,k=a,l=d,m=this.asSeconds();return m?(m<0?"-":"")+"P"+(g?g+"Y":"")+(h?h+"M":"")+(i?i+"D":"")+(j||k||l?"T":"")+(j?j+"H":"")+(k?k+"M":"")+(l?l+"S":""):"P0D"}var sd,td;td=Array.prototype.some?Array.prototype.some:function(a){for(var b=Object(this),c=b.length>>>0,d=0;d68?1900:2e3)};var te=O("FullYear",!0);U("w",["ww",2],"wo","week"),U("W",["WW",2],"Wo","isoWeek"),J("week","w"),J("isoWeek","W"),M("week",5),M("isoWeek",5),Z("w",Sd),Z("ww",Sd,Od),Z("W",Sd),Z("WW",Sd,Od),ca(["w","ww","W","WW"],function(a,b,c,d){b[d.substr(0,1)]=u(a)});var ue={dow:0,doy:6};U("d",0,"do","day"),U("dd",0,0,function(a){return this.localeData().weekdaysMin(this,a)}),U("ddd",0,0,function(a){return this.localeData().weekdaysShort(this,a)}),U("dddd",0,0,function(a){return this.localeData().weekdays(this,a)}),U("e",0,0,"weekday"),U("E",0,0,"isoWeekday"),J("day","d"),J("weekday","e"),J("isoWeekday","E"),M("day",11),M("weekday",11),M("isoWeekday",11),Z("d",Sd),Z("e",Sd),Z("E",Sd),Z("dd",function(a,b){return b.weekdaysMinRegex(a)}),Z("ddd",function(a,b){return b.weekdaysShortRegex(a)}),Z("dddd",function(a,b){return b.weekdaysRegex(a)}),ca(["dd","ddd","dddd"],function(a,b,c,d){var e=c._locale.weekdaysParse(a,d,c._strict);null!=e?b.d=e:n(c).invalidWeekday=a}),ca(["d","e","E"],function(a,b,c,d){b[d]=u(a)});var ve="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),we="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),xe="Su_Mo_Tu_We_Th_Fr_Sa".split("_"),ye=be,ze=be,Ae=be;U("H",["HH",2],0,"hour"),U("h",["hh",2],0,Ra),U("k",["kk",2],0,Sa),U("hmm",0,0,function(){return""+Ra.apply(this)+T(this.minutes(),2)}),U("hmmss",0,0,function(){return""+Ra.apply(this)+T(this.minutes(),2)+T(this.seconds(),2)}),U("Hmm",0,0,function(){return""+this.hours()+T(this.minutes(),2)}),U("Hmmss",0,0,function(){return""+this.hours()+T(this.minutes(),2)+T(this.seconds(),2)}),Ta("a",!0),Ta("A",!1),J("hour","h"),M("hour",13),Z("a",Ua),Z("A",Ua),Z("H",Sd),Z("h",Sd),Z("k",Sd),Z("HH",Sd,Od),Z("hh",Sd,Od),Z("kk",Sd,Od),Z("hmm",Td),Z("hmmss",Ud),Z("Hmm",Td),Z("Hmmss",Ud),ba(["H","HH"],he),ba(["k","kk"],function(a,b,c){var d=u(a);b[he]=24===d?0:d}),ba(["a","A"],function(a,b,c){c._isPm=c._locale.isPM(a),c._meridiem=a}),ba(["h","hh"],function(a,b,c){b[he]=u(a),n(c).bigHour=!0}),ba("hmm",function(a,b,c){var d=a.length-2;b[he]=u(a.substr(0,d)),b[ie]=u(a.substr(d)),n(c).bigHour=!0}),ba("hmmss",function(a,b,c){var d=a.length-4,e=a.length-2;b[he]=u(a.substr(0,d)),b[ie]=u(a.substr(d,2)),b[je]=u(a.substr(e)),n(c).bigHour=!0}),ba("Hmm",function(a,b,c){var d=a.length-2;b[he]=u(a.substr(0,d)),b[ie]=u(a.substr(d))}),ba("Hmmss",function(a,b,c){var d=a.length-4,e=a.length-2;b[he]=u(a.substr(0,d)),b[ie]=u(a.substr(d,2)),b[je]=u(a.substr(e))});var Be,Ce=/[ap]\.?m?\.?/i,De=O("Hours",!0),Ee={calendar:Bd,longDateFormat:Cd,invalidDate:Dd,ordinal:Ed,dayOfMonthOrdinalParse:Fd,relativeTime:Gd,months:pe,monthsShort:qe,week:ue,weekdays:ve,weekdaysMin:xe,weekdaysShort:we,meridiemParse:Ce},Fe={},Ge={},He=/^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,Ie=/^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,Je=/Z|[+-]\d\d(?::?\d\d)?/,Ke=[["YYYYYY-MM-DD",/[+-]\d{6}-\d\d-\d\d/],["YYYY-MM-DD",/\d{4}-\d\d-\d\d/],["GGGG-[W]WW-E",/\d{4}-W\d\d-\d/],["GGGG-[W]WW",/\d{4}-W\d\d/,!1],["YYYY-DDD",/\d{4}-\d{3}/],["YYYY-MM",/\d{4}-\d\d/,!1],["YYYYYYMMDD",/[+-]\d{10}/],["YYYYMMDD",/\d{8}/],["GGGG[W]WWE",/\d{4}W\d{3}/],["GGGG[W]WW",/\d{4}W\d{2}/,!1],["YYYYDDD",/\d{7}/]],Le=[["HH:mm:ss.SSSS",/\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss,SSSS",/\d\d:\d\d:\d\d,\d+/],["HH:mm:ss",/\d\d:\d\d:\d\d/],["HH:mm",/\d\d:\d\d/],["HHmmss.SSSS",/\d\d\d\d\d\d\.\d+/],["HHmmss,SSSS",/\d\d\d\d\d\d,\d+/],["HHmmss",/\d\d\d\d\d\d/],["HHmm",/\d\d\d\d/],["HH",/\d\d/]],Me=/^\/?Date\((\-?\d+)/i,Ne=/^((?:Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\s)?(\d?\d\s(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(?:\d\d)?\d\d\s)(\d\d:\d\d)(\:\d\d)?(\s(?:UT|GMT|[ECMP][SD]T|[A-IK-Za-ik-z]|[+-]\d{4}))$/;a.createFromInputFallback=x("value provided is not in a recognized RFC2822 or ISO format. moment construction falls back to js Date(), which is not reliable across all browsers and versions. Non RFC2822/ISO date formats are discouraged and will be removed in an upcoming major release. Please refer to http://momentjs.com/guides/#/warnings/js-date/ for more info.",function(a){a._d=new Date(a._i+(a._useUTC?" UTC":""))}),a.ISO_8601=function(){},a.RFC_2822=function(){};var Oe=x("moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/",function(){var a=tb.apply(null,arguments);return this.isValid()&&a.isValid()?athis?this:a:p()}),Qe=function(){return Date.now?Date.now():+new Date},Re=["year","quarter","month","week","day","hour","minute","second","millisecond"];Db("Z",":"),Db("ZZ",""),Z("Z",_d),Z("ZZ",_d),ba(["Z","ZZ"],function(a,b,c){c._useUTC=!0,c._tzm=Eb(_d,a)});var Se=/([\+\-]|\d\d)/gi;a.updateOffset=function(){};var Te=/^(\-)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)(\.\d*)?)?$/,Ue=/^(-)?P(?:(-?[0-9,.]*)Y)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)W)?(?:(-?[0-9,.]*)D)?(?:T(?:(-?[0-9,.]*)H)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)S)?)?$/;Sb.fn=Ab.prototype,Sb.invalid=zb;var Ve=Wb(1,"add"),We=Wb(-1,"subtract");a.defaultFormat="YYYY-MM-DDTHH:mm:ssZ",a.defaultFormatUtc="YYYY-MM-DDTHH:mm:ss[Z]";var Xe=x("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(a){return void 0===a?this.localeData():this.locale(a)});U(0,["gg",2],0,function(){return this.weekYear()%100}),U(0,["GG",2],0,function(){return this.isoWeekYear()%100}),Dc("gggg","weekYear"),Dc("ggggg","weekYear"),Dc("GGGG","isoWeekYear"),Dc("GGGGG","isoWeekYear"),J("weekYear","gg"),J("isoWeekYear","GG"),M("weekYear",1),M("isoWeekYear",1),Z("G",Zd),Z("g",Zd),Z("GG",Sd,Od),Z("gg",Sd,Od),Z("GGGG",Wd,Qd),Z("gggg",Wd,Qd),Z("GGGGG",Xd,Rd),Z("ggggg",Xd,Rd),ca(["gggg","ggggg","GGGG","GGGGG"],function(a,b,c,d){b[d.substr(0,2)]=u(a)}),ca(["gg","GG"],function(b,c,d,e){c[e]=a.parseTwoDigitYear(b)}),U("Q",0,"Qo","quarter"),J("quarter","Q"),M("quarter",7),Z("Q",Nd),ba("Q",function(a,b){b[fe]=3*(u(a)-1)}),U("D",["DD",2],"Do","date"),J("date","D"),M("date",9),Z("D",Sd),Z("DD",Sd,Od),Z("Do",function(a,b){return a?b._dayOfMonthOrdinalParse||b._ordinalParse:b._dayOfMonthOrdinalParseLenient}),ba(["D","DD"],ge),ba("Do",function(a,b){b[ge]=u(a.match(Sd)[0],10)});var Ye=O("Date",!0);U("DDD",["DDDD",3],"DDDo","dayOfYear"),J("dayOfYear","DDD"),M("dayOfYear",4),Z("DDD",Vd),Z("DDDD",Pd),ba(["DDD","DDDD"],function(a,b,c){c._dayOfYear=u(a)}),U("m",["mm",2],0,"minute"),J("minute","m"),M("minute",14),Z("m",Sd),Z("mm",Sd,Od),ba(["m","mm"],ie);var Ze=O("Minutes",!1);U("s",["ss",2],0,"second"),J("second","s"),M("second",15),Z("s",Sd),Z("ss",Sd,Od),ba(["s","ss"],je);var $e=O("Seconds",!1);U("S",0,0,function(){return~~(this.millisecond()/100)}),U(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),U(0,["SSS",3],0,"millisecond"),U(0,["SSSS",4],0,function(){return 10*this.millisecond()}),U(0,["SSSSS",5],0,function(){return 100*this.millisecond()}),U(0,["SSSSSS",6],0,function(){return 1e3*this.millisecond()}),U(0,["SSSSSSS",7],0,function(){return 1e4*this.millisecond()}),U(0,["SSSSSSSS",8],0,function(){return 1e5*this.millisecond()}),U(0,["SSSSSSSSS",9],0,function(){return 1e6*this.millisecond()}),J("millisecond","ms"),M("millisecond",16),Z("S",Vd,Nd),Z("SS",Vd,Od),Z("SSS",Vd,Pd);var _e;for(_e="SSSS";_e.length<=9;_e+="S")Z(_e,Yd);for(_e="S";_e.length<=9;_e+="S")ba(_e,Mc);var af=O("Milliseconds",!1);U("z",0,0,"zoneAbbr"),U("zz",0,0,"zoneName");var bf=r.prototype;bf.add=Ve,bf.calendar=Zb,bf.clone=$b,bf.diff=fc,bf.endOf=sc,bf.format=kc,bf.from=lc,bf.fromNow=mc,bf.to=nc,bf.toNow=oc,bf.get=R,bf.invalidAt=Bc,bf.isAfter=_b,bf.isBefore=ac,bf.isBetween=bc,bf.isSame=cc,bf.isSameOrAfter=dc,bf.isSameOrBefore=ec,bf.isValid=zc,bf.lang=Xe,bf.locale=pc,bf.localeData=qc,bf.max=Pe,bf.min=Oe,bf.parsingFlags=Ac,bf.set=S,bf.startOf=rc,bf.subtract=We,bf.toArray=wc,bf.toObject=xc,bf.toDate=vc,bf.toISOString=ic,bf.inspect=jc,bf.toJSON=yc,bf.toString=hc,bf.unix=uc,bf.valueOf=tc,bf.creationData=Cc,bf.year=te,bf.isLeapYear=ra,bf.weekYear=Ec,bf.isoWeekYear=Fc,bf.quarter=bf.quarters=Kc,bf.month=ka,bf.daysInMonth=la,bf.week=bf.weeks=Ba,bf.isoWeek=bf.isoWeeks=Ca,bf.weeksInYear=Hc,bf.isoWeeksInYear=Gc,bf.date=Ye,bf.day=bf.days=Ka,bf.weekday=La,bf.isoWeekday=Ma,bf.dayOfYear=Lc,bf.hour=bf.hours=De,bf.minute=bf.minutes=Ze,bf.second=bf.seconds=$e,bf.millisecond=bf.milliseconds=af,bf.utcOffset=Hb,bf.utc=Jb,bf.local=Kb,bf.parseZone=Lb,bf.hasAlignedHourOffset=Mb,bf.isDST=Nb,bf.isLocal=Pb,bf.isUtcOffset=Qb,bf.isUtc=Rb,bf.isUTC=Rb,bf.zoneAbbr=Nc,bf.zoneName=Oc,bf.dates=x("dates accessor is deprecated. Use date instead.",Ye),bf.months=x("months accessor is deprecated. Use month instead",ka),bf.years=x("years accessor is deprecated. Use year instead",te),bf.zone=x("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",Ib),bf.isDSTShifted=x("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",Ob);var cf=C.prototype;cf.calendar=D,cf.longDateFormat=E,cf.invalidDate=F,cf.ordinal=G,cf.preparse=Rc,cf.postformat=Rc,cf.relativeTime=H,cf.pastFuture=I,cf.set=A,cf.months=fa,cf.monthsShort=ga,cf.monthsParse=ia,cf.monthsRegex=na,cf.monthsShortRegex=ma,cf.week=ya,cf.firstDayOfYear=Aa,cf.firstDayOfWeek=za,cf.weekdays=Fa,cf.weekdaysMin=Ha,cf.weekdaysShort=Ga,cf.weekdaysParse=Ja,cf.weekdaysRegex=Na,cf.weekdaysShortRegex=Oa,cf.weekdaysMinRegex=Pa,cf.isPM=Va,cf.meridiem=Wa,$a("en",{dayOfMonthOrdinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(a){var b=a%10,c=1===u(a%100/10)?"th":1===b?"st":2===b?"nd":3===b?"rd":"th";return a+c}}),a.lang=x("moment.lang is deprecated. Use moment.locale instead.",$a),a.langData=x("moment.langData is deprecated. Use moment.localeData instead.",bb);var df=Math.abs,ef=id("ms"),ff=id("s"),gf=id("m"),hf=id("h"),jf=id("d"),kf=id("w"),lf=id("M"),mf=id("y"),nf=kd("milliseconds"),of=kd("seconds"),pf=kd("minutes"),qf=kd("hours"),rf=kd("days"),sf=kd("months"),tf=kd("years"),uf=Math.round,vf={ss:44,s:45,m:45,h:22,d:26,M:11},wf=Math.abs,xf=Ab.prototype;return xf.isValid=yb,xf.abs=$c,xf.add=ad,xf.subtract=bd,xf.as=gd,xf.asMilliseconds=ef,xf.asSeconds=ff,xf.asMinutes=gf,xf.asHours=hf,xf.asDays=jf,xf.asWeeks=kf,xf.asMonths=lf,xf.asYears=mf,xf.valueOf=hd,xf._bubble=dd,xf.get=jd,xf.milliseconds=nf,xf.seconds=of,xf.minutes=pf,xf.hours=qf,xf.days=rf,xf.weeks=ld,xf.months=sf,xf.years=tf,xf.humanize=qd,xf.toISOString=rd,xf.toString=rd,xf.toJSON=rd,xf.locale=pc,xf.localeData=qc,xf.toIsoString=x("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",rd),xf.lang=Xe,U("X",0,0,"unix"),U("x",0,0,"valueOf"),Z("x",Zd),Z("X",ae),ba("X",function(a,b,c){c._d=new Date(1e3*parseFloat(a,10))}),ba("x",function(a,b,c){c._d=new Date(u(a))}),a.version="2.18.1",b(tb),a.fn=bf,a.min=vb,a.max=wb,a.now=Qe,a.utc=l,a.unix=Pc,a.months=Vc,a.isDate=h,a.locale=$a,a.invalid=p,a.duration=Sb,a.isMoment=s,a.weekdays=Xc,a.parseZone=Qc,a.localeData=bb,a.isDuration=Bb,a.monthsShort=Wc,a.weekdaysMin=Zc,a.defineLocale=_a,a.updateLocale=ab,a.locales=cb,a.weekdaysShort=Yc,a.normalizeUnits=K,a.relativeTimeRounding=od,a.relativeTimeThreshold=pd,a.calendarFormat=Yb,a.prototype=bf,a}); diff --git a/resources/javascript/moveScroller.js b/resources/js/vendor/moveScroller.js similarity index 100% rename from resources/javascript/moveScroller.js rename to resources/js/vendor/moveScroller.js diff --git a/resources/javascript/prettify/lang-apollo.js b/resources/js/vendor/prettify/lang-apollo.js similarity index 100% rename from resources/javascript/prettify/lang-apollo.js rename to resources/js/vendor/prettify/lang-apollo.js diff --git a/resources/javascript/prettify/lang-clj.js b/resources/js/vendor/prettify/lang-clj.js similarity index 100% rename from resources/javascript/prettify/lang-clj.js rename to resources/js/vendor/prettify/lang-clj.js diff --git a/resources/javascript/prettify/lang-css.js b/resources/js/vendor/prettify/lang-css.js similarity index 100% rename from resources/javascript/prettify/lang-css.js rename to resources/js/vendor/prettify/lang-css.js diff --git a/resources/javascript/prettify/lang-go.js b/resources/js/vendor/prettify/lang-go.js similarity index 100% rename from resources/javascript/prettify/lang-go.js rename to resources/js/vendor/prettify/lang-go.js diff --git a/resources/javascript/prettify/lang-hs.js b/resources/js/vendor/prettify/lang-hs.js similarity index 100% rename from resources/javascript/prettify/lang-hs.js rename to resources/js/vendor/prettify/lang-hs.js diff --git a/resources/javascript/prettify/lang-lisp.js b/resources/js/vendor/prettify/lang-lisp.js similarity index 100% rename from resources/javascript/prettify/lang-lisp.js rename to resources/js/vendor/prettify/lang-lisp.js diff --git a/resources/javascript/prettify/lang-lua.js b/resources/js/vendor/prettify/lang-lua.js similarity index 100% rename from resources/javascript/prettify/lang-lua.js rename to resources/js/vendor/prettify/lang-lua.js diff --git a/resources/javascript/prettify/lang-ml.js b/resources/js/vendor/prettify/lang-ml.js similarity index 100% rename from resources/javascript/prettify/lang-ml.js rename to resources/js/vendor/prettify/lang-ml.js diff --git a/resources/javascript/prettify/lang-n.js b/resources/js/vendor/prettify/lang-n.js similarity index 100% rename from resources/javascript/prettify/lang-n.js rename to resources/js/vendor/prettify/lang-n.js diff --git a/resources/javascript/prettify/lang-proto.js b/resources/js/vendor/prettify/lang-proto.js similarity index 100% rename from resources/javascript/prettify/lang-proto.js rename to resources/js/vendor/prettify/lang-proto.js diff --git a/resources/javascript/prettify/lang-scala.js b/resources/js/vendor/prettify/lang-scala.js similarity index 100% rename from resources/javascript/prettify/lang-scala.js rename to resources/js/vendor/prettify/lang-scala.js diff --git a/resources/javascript/prettify/lang-sql.js b/resources/js/vendor/prettify/lang-sql.js similarity index 100% rename from resources/javascript/prettify/lang-sql.js rename to resources/js/vendor/prettify/lang-sql.js diff --git a/resources/javascript/prettify/lang-tex.js b/resources/js/vendor/prettify/lang-tex.js similarity index 100% rename from resources/javascript/prettify/lang-tex.js rename to resources/js/vendor/prettify/lang-tex.js diff --git a/resources/javascript/prettify/lang-vb.js b/resources/js/vendor/prettify/lang-vb.js similarity index 100% rename from resources/javascript/prettify/lang-vb.js rename to resources/js/vendor/prettify/lang-vb.js diff --git a/resources/javascript/prettify/lang-vhdl.js b/resources/js/vendor/prettify/lang-vhdl.js similarity index 100% rename from resources/javascript/prettify/lang-vhdl.js rename to resources/js/vendor/prettify/lang-vhdl.js diff --git a/resources/javascript/prettify/lang-wiki.js b/resources/js/vendor/prettify/lang-wiki.js similarity index 100% rename from resources/javascript/prettify/lang-wiki.js rename to resources/js/vendor/prettify/lang-wiki.js diff --git a/resources/javascript/prettify/lang-xq.js b/resources/js/vendor/prettify/lang-xq.js similarity index 100% rename from resources/javascript/prettify/lang-xq.js rename to resources/js/vendor/prettify/lang-xq.js diff --git a/resources/javascript/prettify/lang-yaml.js b/resources/js/vendor/prettify/lang-yaml.js similarity index 100% rename from resources/javascript/prettify/lang-yaml.js rename to resources/js/vendor/prettify/lang-yaml.js diff --git a/resources/javascript/prettify/prettify.js b/resources/js/vendor/prettify/prettify.js similarity index 100% rename from resources/javascript/prettify/prettify.js rename to resources/js/vendor/prettify/prettify.js diff --git a/resources/javascript/toc.js b/resources/js/vendor/toc.js similarity index 78% rename from resources/javascript/toc.js rename to resources/js/vendor/toc.js index 019df06c45..55dbaac46d 100644 --- a/resources/javascript/toc.js +++ b/resources/js/vendor/toc.js @@ -19,10 +19,11 @@ */ (function($) { - /* - * The TOC plugin dynamically builds a table of contents from the headings in - * a document and prepends legal-style section numbers to each of the headings. - */ + + /* + * The TOC plugin dynamically builds a table of contents from the headings in + * a document and prepends legal-style section numbers to each of the headings. + */ $.fn.toc = function(options) { var opts = $.extend({}, $.fn.toc.defaults, options); var toc = this.append('
          ').children('ul'); @@ -34,7 +35,7 @@ } return this.each(function() { - $(opts.context + ' :header').not(opts.exclude).each(function() { + $(opts.context + ' :header').not(opts.exclude).each(function() { var $this = $(this); for (var i = 6; i >= 1; i--) { if ($this.is('h' + i)) { @@ -46,6 +47,9 @@ } $this.text(addNumeration(headers, 'h' + i, $this.text())); } + if (opts.autoId && !$this.attr('id')) { + $this.attr('id', generateId($this.text())); + } appendToTOC(toc, indexes['h' + i], $this.attr('id'), $this.text()); } } @@ -59,28 +63,28 @@ */ function checkContainer(header, toc) { if (header === 0 && toc.find(':last').length !== 0 && !toc.find(':last').is('ul')) { - toc.find('li:last').append('
            '); - } + toc.find('li:last').append('
              '); + } }; /* * Updates headers numeration. */ - function updateNumeration(headers, header) { - $.each(headers, function(i, val) { - if (i === header) { - ++headers[i]; - } else if (i > header) { - headers[i] = 0; - } - }); - }; + function updateNumeration(headers, header) { + $.each(headers, function(i, val) { + if (i === header) { + ++headers[i]; + } else if (i > header) { + headers[i] = 0; + } + }); + }; /* * Generate an anchor id from a string by replacing unwanted characters. */ function generateId(text) { - return text.replace(/[ <#\/\\?&]/g, '_'); + return text.replace(/[ <#\/\\?&.,():;]/g, '_'); }; /* @@ -106,7 +110,7 @@ for (var i = 1; i < index; i++) { if (parent.find('> li:last > ul').length === 0) { - parent.append('
              • '); + parent.append('
                • '); } parent = parent.find('> li:last > ul:first'); } @@ -121,7 +125,7 @@ $.fn.toc.defaults = { exclude: 'h1, h5, h6', context: '', - autoId: false, - numerate: true + autoId: true, + numerate: false }; })(jQuery); diff --git a/resources/js/vendor/unslider.js b/resources/js/vendor/unslider.js new file mode 100644 index 0000000000..c41609c9bf --- /dev/null +++ b/resources/js/vendor/unslider.js @@ -0,0 +1,654 @@ +/** + * Unslider + * version 2.0 + * by @idiot and friends + */ + +(function(factory) { + if (typeof module === 'object' && typeof module.exports === 'object') { + factory(require('jquery')); + } else if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define([], factory(window.jQuery)); + } else { + factory(window.jQuery); + } +}(function($) { + // Don't throw any errors when jQuery + if(!$) { + return console.warn('Unslider needs jQuery'); + } + + $.Unslider = function(context, options) { + var self = this; + + // Create an Unslider reference we can use everywhere + self._ = 'unslider'; + + // Store our default options in here + // Everything will be overwritten by the jQuery plugin though + self.defaults = { + // Should the slider move on its own or only when + // you interact with the nav/arrows? + // Only accepts boolean true/false. + autoplay: false, + + // 3 second delay between slides moving, pass + // as a number in milliseconds. + delay: 3000, + + // Animation speed in millseconds + speed: 750, + + // An easing string to use. If you're using Velocity, use a + // Velocity string otherwise you can use jQuery/jQ UI options. + easing: 'swing', // [.42, 0, .58, 1], + + // Does it support keyboard arrows? + // Can pass either true or false - + // or an object with the keycodes, like so: + // { + // prev: 37, + // next: 39 + // } + // You can call any internal method name + // before the keycode and it'll be called. + keys: { + prev: 37, + next: 39 + }, + + // Do you want to generate clickable navigation + // to skip to each slide? Accepts boolean true/false or + // a callback function per item to generate. + nav: true, + + // Should there be left/right arrows to go back/forth? + // -> This isn't keyboard support. + // Either set true/false, or an object with the HTML + // elements for each arrow like below: + arrows: { + prev: '', + next: '' + }, + + // How should Unslider animate? + // It can do one of the following types: + // "fade": each slide fades in to each other + // "horizontal": each slide moves from left to right + // "vertical": each slide moves from top to bottom + animation: 'horizontal', + + // If you don't want to use a list to display your slides, + // you can change it here. Not recommended and you'll need + // to adjust the CSS accordingly. + selectors: { + container: 'ul:first', + slides: 'li' + }, + + // Do you want to animate the heights of each slide as + // it moves + animateHeight: false, + + // Active class for the nav + activeClass: self._ + '-active', + + // Have swipe support? + // You can set this here with a boolean and always use + // initSwipe/destroySwipe later on. + swipe: true, + // Swipe threshold - + // lower float for enabling short swipe + swipeThreshold: 0.2 + }; + + // Set defaults + self.$context = context; + self.options = {}; + + // Leave our elements blank for now + // Since they get changed by the options, we'll need to + // set them in the init method. + self.$parent = null; + self.$container = null; + self.$slides = null; + self.$nav = null; + self.$arrows = []; + + // Set our indexes and totals + self.total = 0; + self.current = 0; + + // Generate a specific random ID so we don't dupe events + self.prefix = self._ + '-'; + self.eventSuffix = '.' + self.prefix + ~~(Math.random() * 2e3); + + // In case we're going to use the autoplay + self.interval = null; + + // Get everything set up innit + self.init = function(options) { + // Set up our options inside here so we can re-init at + // any time + self.options = $.extend({}, self.defaults, options); + + // Our elements + self.$container = self.$context.find(self.options.selectors.container).addClass(self.prefix + 'wrap'); + self.$slides = self.$container.children(self.options.selectors.slides); + + // We'll manually init the container + self.setup(); + + // We want to keep this script as small as possible + // so we'll optimise some checks + $.each(['nav', 'arrows', 'keys', 'infinite'], function(index, module) { + self.options[module] && self['init' + $._ucfirst(module)](); + }); + + // Add swipe support + if(jQuery.event.special.swipe && self.options.swipe) { + self.initSwipe(); + } + + // If autoplay is set to true, call self.start() + // to start calling our timeouts + self.options.autoplay && self.start(); + + // We should be able to recalculate slides at will + self.calculateSlides(); + + // Listen to a ready event + self.$context.trigger(self._ + '.ready'); + + // Everyday I'm chainin' + return self.animate(self.options.index || self.current, 'init'); + }; + + self.setup = function() { + // Add a CSS hook to the main element + self.$context.addClass(self.prefix + self.options.animation).wrap('
                  '); + self.$parent = self.$context.parent('.' + self._); + + // We need to manually check if the container is absolutely + // or relatively positioned + var position = self.$context.css('position'); + + // If we don't already have a position set, we'll + // automatically set it ourselves + if(position === 'static') { + self.$context.css('position', 'relative'); + } + + self.$context.css('overflow', 'hidden'); + }; + + // Set up the slide widths to animate with + // so the box doesn't float over + self.calculateSlides = function() { + // update slides before recalculating the total + self.$slides = self.$container.children(self.options.selectors.slides); + + self.total = self.$slides.length; + + // Set the total width + if(self.options.animation !== 'fade') { + var prop = 'width'; + + if(self.options.animation === 'vertical') { + prop = 'height'; + } + + self.$container.css(prop, (self.total * 100) + '%').addClass(self.prefix + 'carousel'); + self.$slides.css(prop, (100 / self.total) + '%'); + } + }; + + + // Start our autoplay + self.start = function() { + self.interval = setTimeout(function() { + // Move on to the next slide + self.next(); + + // If we've got autoplay set up + // we don't need to keep starting + // the slider from within our timeout + // as .animate() calls it for us + }, self.options.delay); + + return self; + }; + + // And pause our timeouts + // and force stop the slider if needed + self.stop = function() { + clearTimeout(self.interval); + + return self; + }; + + + // Set up our navigation + self.initNav = function() { + var $nav = $(''); + + // Build our click navigation item-by-item + self.$slides.each(function(key) { + // If we've already set a label, let's use that + // instead of generating one + var label = this.getAttribute('data-nav') || key + 1; + + // Listen to any callback functions + if($.isFunction(self.options.nav)) { + label = self.options.nav.call(self.$slides.eq(key), key, label); + } + + // And add it to our navigation item + $nav.children('ol').append('
                • ' + label + '
                • '); + }); + + // Keep a copy of the nav everywhere so we can use it + self.$nav = $nav.insertAfter(self.$context); + + // Now our nav is built, let's add it to the slider and bind + // for any click events on the generated links + self.$nav.find('li').on('click' + self.eventSuffix, function() { + // Cache our link and set it to be active + var $me = $(this).addClass(self.options.activeClass); + + // Set the right active class, remove any other ones + $me.siblings().removeClass(self.options.activeClass); + + // Move the slide + self.animate($me.attr('data-slide')); + }); + }; + + + // Set up our left-right arrow navigation + // (Not keyboard arrows, prev/next buttons) + self.initArrows = function() { + if(self.options.arrows === true) { + self.options.arrows = self.defaults.arrows; + } + + // Loop our options object and bind our events + $.each(self.options.arrows, function(key, val) { + // Add our arrow HTML and bind it + self.$arrows.push( + $(val).insertAfter(self.$context).on('click' + self.eventSuffix, self[key]) + ); + }); + }; + + + // Set up our keyboad navigation + // Allow binding to multiple keycodes + self.initKeys = function() { + if(self.options.keys === true) { + self.options.keys = self.defaults.keys; + } + + $(document).on('keyup' + self.eventSuffix, function(e) { + $.each(self.options.keys, function(key, val) { + if(e.which === val) { + $.isFunction(self[key]) && self[key].call(self); + } + }); + }); + }; + + // Requires jQuery.event.swipe + // -> stephband.info/jquery.event.swipe + self.initSwipe = function() { + var width = self.$slides.width(); + + // We don't want to have a tactile swipe in the slider + // in the fade animation, as it can cause some problems + // with layout, so we'll just disable it. + if(self.options.animation !== 'fade') { + + self.$container.on({ + + movestart: function(e) { + // If the movestart heads off in a upwards or downwards + // direction, prevent it so that the browser scrolls normally. + if((e.distX > e.distY && e.distX < -e.distY) || (e.distX < e.distY && e.distX > -e.distY)) { + return !!e.preventDefault(); + } + + self.$container.css('position', 'relative'); + }, + + move: function(e) { + self.$container.css('left', -(100 * self.current) + (100 * e.distX / width) + '%'); + }, + + moveend: function(e) { + // Check if swiped distance is greater than threshold. + // If yes slide to next/prev slide. If not animate to + // starting point. + + if((Math.abs(e.distX) / width) > self.options.swipeThreshold) { + + self[e.distX < 0 ? 'next' : 'prev'](); + } + else { + + self.$container.animate({left: -(100 * self.current) + '%' }, self.options.speed / 2 ); + } + } + }); + } + }; + + // Infinite scrolling is a massive pain in the arse + // so we need to create a whole bloody function to set + // it up. Argh. + self.initInfinite = function() { + var pos = ['first', 'last']; + + $.each(pos, function(index, item) { + self.$slides.push.apply( + self.$slides, + + // Exclude all cloned slides and call .first() or .last() + // depending on what `item` is. + self.$slides.filter(':not(".' + self._ + '-clone")')[item]() + + // Make a copy of it and identify it as a clone + .clone().addClass(self._ + '-clone') + + // Either insert before or after depending on whether we're + // the first or last clone + ['insert' + (index === 0 ? 'After' : 'Before')]( + // Return the other element in the position array + // if item = first, return "last" + self.$slides[pos[~~!index]]() + ) + ); + }); + }; + + // Remove any trace of arrows + // Loop our array of arrows and use jQuery to remove + // It'll unbind any event handlers for us + self.destroyArrows = function() { + $.each(self.$arrows, function(i, $arrow) { + $arrow.remove(); + }); + }; + + // Remove any swipe events and reset the position + self.destroySwipe = function() { + // We bind to 4 events, so we'll unbind those + self.$container.off('movestart move moveend'); + }; + + // Unset the keyboard navigation + // Remove the handler + self.destroyKeys = function() { + // Remove the event handler + $(document).off('keyup' + self.eventSuffix); + }; + + self.setIndex = function(to) { + if(to < 0) { + to = self.total - 1; + } + + self.current = Math.min(Math.max(0, to), self.total - 1); + + if(self.options.nav) { + self.$nav.find('[data-slide="' + self.current + '"]')._active(self.options.activeClass); + } + + self.$slides.eq(self.current)._active(self.options.activeClass); + + return self; + }; + + // Despite the name, this doesn't do any animation - since there's + // now three different types of animation, we let this method delegate + // to the right type, keeping the name for backwards compat. + self.animate = function(to, dir) { + // Animation shortcuts + // Instead of passing a number index, we can now + // use .data('unslider').animate('last'); + // or .unslider('animate:last') + // to go to the very last slide + if(to === 'first') to = 0; + if(to === 'last') to = self.total; + + // Don't animate if it's not a valid index + if(isNaN(to)) { + return self; + } + + if(self.options.autoplay) { + self.stop().start(); + } + + self.setIndex(to); + + // Add a callback method to do stuff with + self.$context.trigger(self._ + '.change', [to, self.$slides.eq(to)]); + + // Delegate the right method - everything's named consistently + // so we can assume it'll be called "animate" + + var fn = 'animate' + $._ucfirst(self.options.animation); + + // Make sure it's a valid animation method, otherwise we'll get + // a load of bug reports that'll be really hard to report + if($.isFunction(self[fn])) { + self[fn](self.current, dir); + } + + return self; + }; + + + // Shortcuts for animating if we don't know what the current + // index is (i.e back/forward) + // For moving forward we need to make sure we don't overshoot. + self.next = function() { + var target = self.current + 1; + + // If we're at the end, we need to move back to the start + if(target >= self.total) { + target = 0; + } + + return self.animate(target, 'next'); + }; + + // Previous is a bit simpler, we can just decrease the index + // by one and check if it's over 0. + self.prev = function() { + return self.animate(self.current - 1, 'prev'); + }; + + + // Our default animation method, the old-school left-to-right + // horizontal animation + self.animateHorizontal = function(to) { + var prop = 'left'; + + // Add RTL support, slide the slider + // the other way if the site is right-to-left + if(self.$context.attr('dir') === 'rtl') { + prop = 'right'; + } + + if(self.options.infinite) { + // So then we need to hide the first slide + self.$container.css('margin-' + prop, '-100%'); + } + + return self.slide(prop, to); + }; + + // The same animation methods, but vertical support + // RTL doesn't affect the vertical direction so we + // can just call as is + self.animateVertical = function(to) { + self.options.animateHeight = true; + + // Normal infinite CSS fix doesn't work for + // vertical animation so we need to manually set it + // with pixels. Ah well. + if(self.options.infinite) { + self.$container.css('margin-top', -self.$slides.outerHeight()); + } + + return self.slide('top', to); + }; + + // Actually move the slide now + // We have to pass a property to animate as there's + // a few different directions it can now move, but it's + // otherwise unchanged from before. + self.slide = function(prop, to) { + // If we want to change the height of the slider + // to match the current slide, you can set + // {animateHeight: true} + self.animateHeight(to); + + // For infinite sliding we add a dummy slide at the end and start + // of each slider to give the appearance of being infinite + if(self.options.infinite) { + var dummy; + + // Going backwards to last slide + if(to === self.total - 1) { + // We're setting a dummy position and an actual one + // the dummy is what the index looks like + // (and what we'll silently update to afterwards), + // and the actual is what makes it not go backwards + dummy = self.total - 3; + to = -1; + } + + // Going forwards to first slide + if(to === self.total - 2) { + dummy = 0; + to = self.total - 2; + } + + // If it's a number we can safely set it + if(typeof dummy === 'number') { + self.setIndex(dummy); + + // Listen for when the slide's finished transitioning so + // we can silently move it into the right place and clear + // this whole mess up. + self.$context.on(self._ + '.moved', function() { + if(self.current === dummy) { + self.$container.css(prop, -(100 * dummy) + '%').off(self._ + '.moved'); + } + }); + } + } + + // We need to create an object to store our property in + // since we don't know what it'll be. + var obj = {}; + + // Manually create it here + obj[prop] = -(100 * to) + '%'; + + // And animate using our newly-created object + return self._move(self.$container, obj); + }; + + + // Fade between slides rather than, uh, sliding it + self.animateFade = function(to) { + // If we want to change the height of the slider + // to match the current slide, you can set + // {animateHeight: true} + self.animateHeight(to); + + var $active = self.$slides.eq(to).addClass(self.options.activeClass); + + // Toggle our classes + self._move($active.siblings().removeClass(self.options.activeClass), {opacity: 0}); + self._move($active, {opacity: 1}, false); + }; + + // Animate height of slider + self.animateHeight = function(to) { + // If we want to change the height of the slider + // to match the current slide, you can set + // {animateHeight: true} + if (self.options.animateHeight) { + self._move(self.$context, {height: self.$slides.eq(to).outerHeight()}, false); + } + }; + + self._move = function($el, obj, callback, speed) { + if(callback !== false) { + callback = function() { + self.$context.trigger(self._ + '.moved'); + }; + } + + return $el._move(obj, speed || self.options.speed, self.options.easing, callback); + }; + + // Allow daisy-chaining of methods + return self.init(options); + }; + + // Internal (but global) jQuery methods + // They're both just helpful types of shorthand for + // anything that might take too long to write out or + // something that might be used more than once. + $.fn._active = function(className) { + return this.addClass(className).siblings().removeClass(className); + }; + + // The equivalent to PHP's ucfirst(). Take the first + // character of a string and make it uppercase. + // Simples. + $._ucfirst = function(str) { + // Take our variable, run a regex on the first letter + return (str + '').toLowerCase().replace(/^./, function(match) { + // And uppercase it. Simples. + return match.toUpperCase(); + }); + }; + + $.fn._move = function() { + this.stop(true, true); + return $.fn[$.fn.velocity ? 'velocity' : 'animate'].apply(this, arguments); + }; + + // And set up our jQuery plugin + $.fn.unslider = function(opts) { + return this.each(function(index,elem) { + var $this = $(elem); + var unslider = $(elem).data('unslider'); + if(unslider instanceof $.Unslider) { + return; + } + // Allow usage of .unslider('function_name') + // as well as using .data('unslider') to access the + // main Unslider object + if(typeof opts === 'string' && $this.data('unslider')) { + opts = opts.split(':'); + + var call = $this.data('unslider')[opts[0]]; + + // Do we have arguments to pass to the string-function? + if($.isFunction(call)) { + return call.apply($this, opts[1] ? opts[1].split(',') : null); + } + } + + return $this.data('unslider', new $.Unslider($this, opts)); + }); + }; + +})); diff --git a/resources/stylesheets/base.css b/resources/stylesheets/base.css deleted file mode 100644 index 38d37e1ee3..0000000000 --- a/resources/stylesheets/base.css +++ /dev/null @@ -1,220 +0,0 @@ -html, body { - height: 100%; -} - -.bottom { - min-height: 100%; - height: 100%; - padding-bottom: 20px; -} - -/* every page should need this */ -input, textarea, select, .uneditable-input { - width: 165px; -} - -* { - margin: 0; -} - -.push { - height: 199px; /* .push must be the same height as .footer */ - clear: both; -} - -.footer { - padding-top: 15px; - clear: both; - background: #8d8d8d; - border-top: solid 1px #676767; - width: 100%; - color: rgba(255, 255, 255, 0.7); - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.5); -} - -.wrapper { - min-height: 100%; - height: auto !important; - height: 100%; - margin: 0 auto -199px; /* the bottom margin is the negative value of the footer's height */ -} - -.footer ul { - float: left; - margin: 0; - padding: 10px 2% 20px 0; - -webkit-box-sizing: content-box; - -moz-box-sizing: content-box; - box-sizing: content-box; - width: 18%; - list-style: none; -} - -.footer ul:last-child { - padding-right: 0; -} - -.footer ul li a { - text-decoration: none; - color: rgba(255, 255, 255, 0.7); - font-size: 12px; -} - -.footer ul li a:hover { - text-decoration: underline; -} - -.footer ul li h5 { - color: rgba(255, 255, 255, 0.9); - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.5); - margin-bottom: 10px; - padding-bottom: 10px; - line-height: 20px; - border-bottom:1px solid #676767; - -webkit-box-shadow:0 1px 0 rgba(255, 255, 255, 0.5); - -moz-box-shadow:0 1px 0 rgba(255, 255, 255, 0.5); - box-shadow:0 1px 0 rgba(255, 255, 255, 0.5); } - -.footer ul li h5 a { - font-size: 14px; - opacity: 1; -} - -.footer .copyright { - font-size: 12px; - border-top:1px solid #808080; - clear: both; - padding: 10px 0 20px; -} - -blockquote p { - font-size: 13px; - font-weight: normal; - font-style: italic; -} - -.langbar li { - display: inline; -} - -.langbar-cheatsheet li { - display: block; -} - -.langbar li a { - color: rgba(0, 0, 0, 0.5); - font-weight: bold; - text-decoration: none; - font-size: 14px; - margin-right: 5px; -} - -.langbar li a:hover { - text-decoration: underline; -} - -/*Coursera stuff*/ - -.label { - white-space: nowrap; -} - -h2 code { font-size: 18px; } -h3 code { font-size: 16px; } - -.axis path, -.axis line { - fill: none; - stroke: #000; - shape-rendering: crispEdges; -} - -.bar { - fill: steelblue; -} - -.x.axis path { - display: none; -} - -div#pop-up { - display: none; - position:absolute; - font-size: 11px; - text-align: center; - color: white; - background: rgba(0,0,0,0.5); - padding: 3px 8px; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; - -} - -div#pop-up-arrow { - position: absolute; - width: 0; - height: 0; - border-color: transparent; - border-style: solid; - bottom: 0; - left: 50%; - margin-left: -5px; - margin-bottom: -5px; - border-top-color: rgba(0,0,0,0.5); - border-width: 5px 5px 0; -} - -.jvectormap-label { - position: absolute; - display: none; - border: solid 1px #CDCDCD; - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - border-radius: 3px; - background: #292929; - color: white; - font-family: sans-serif, Verdana; - font-size: smaller; - padding: 3px; -} - -.jvectormap-zoomin, .jvectormap-zoomout { - position: absolute; - left: 10px; - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - border-radius: 3px; - background: #292929; - padding: 3px; - color: white; - width: 10px; - height: 10px; - cursor: pointer; - line-height: 10px; - text-align: center; -} - -.jvectormap-zoomin { - top: 10px; -} - -.jvectormap-zoomout { - top: 30px; -} -.h2 { - font-size: 24px; - line-height: 36px; - color: #404040; - font-weight: bold; - display: block; - -webkit-margin-before: 0.83em; - -webkit-margin-after: 0.83em; - -webkit-margin-start: 0px; - -webkit-margin-end: 0px; -} - -.topbar input:focus, .topbar input.focused { - background-color: #FEFEFE; - text-shadow: none; -} \ No newline at end of file diff --git a/resources/stylesheets/custom.css b/resources/stylesheets/custom.css deleted file mode 100644 index 9d85f21c27..0000000000 --- a/resources/stylesheets/custom.css +++ /dev/null @@ -1,4 +0,0 @@ -code,pre{} -code{} -pre{} - diff --git a/resources/stylesheets/docs.css b/resources/stylesheets/docs.css deleted file mode 100644 index fb727a900a..0000000000 --- a/resources/stylesheets/docs.css +++ /dev/null @@ -1,317 +0,0 @@ -/* Add additional stylesheets below --------------------------------------------------- */ -/* - Bootstrap's documentation styles - Special styles for presenting Bootstrap's documentation and examples -*/ - -/* Body and structure --------------------------------------------------- */ -body { - background-color: #fff; - position: relative; -} -section { - padding-top: 60px; -} -section > .row { - margin-bottom: 10px; -} - - -/* Jumbotrons --------------------------------------------------- */ -.jumbotron { - min-width: 940px; - padding-top: 40px; -} -.jumbotron .inner { - background: transparent url(../img/grid-18px.png) top center; - padding: 45px 0; - -webkit-box-shadow: inset 0 10px 30px rgba(0,0,0,.3); - -moz-box-shadow: inset 0 10px 30px rgba(0,0,0,.3); -/* box-shadow: inset 0 10px 30px rgba(0,0,0,.3); -*/} -.jumbotron h1, -.jumbotron p { - margin-bottom: 9px; - color: #fff; - text-align: center; - text-shadow: 0 1px 1px rgba(0,0,0,.3); -} -.jumbotron h1 { - font-size: 54px; - line-height: 1; - text-shadow: 0 1px 2px rgba(0,0,0,.5); -} -.jumbotron p { - font-weight: 300; -} -.jumbotron .lead { - font-size: 20px; - line-height: 27px; -} -.jumbotron p a { - color: #fff; - font-weight: bold; -} - -/* Specific jumbotrons -------------------------- */ -/* main docs page */ -.masthead { - background-color: #049cd9; - background-repeat: no-repeat; - background-image: -webkit-gradient(linear, left top, left bottom, from(#004D9F), to(#049cd9)); - background-image: -webkit-linear-gradient(#004D9F, #049cd9); - background-image: -moz-linear-gradient(#004D9F, #049cd9); - background-image: -o-linear-gradient(top, #004D9F, #049cd9); - background-image: -khtml-gradient(linear, left top, left bottom, from(#004D9F), to(#049cd9)); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#004D9F', endColorstr='#049cd9', GradientType=0); /* IE8 and down */ -} -/* supporting docs pages */ -.subhead { - background-color: #767d80; - background-repeat: no-repeat; - background-image: -webkit-gradient(linear, left top, left bottom, from(#565d60), to(#767d80)); - background-image: -webkit-linear-gradient(#565d60, #767d80); - background-image: -moz-linear-gradient(#565d60, #767d80); - background-image: -o-linear-gradient(top, #565d60, #767d80); - background-image: -khtml-gradient(linear, left top, left bottom, from(#565d60), to(#767d80)); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#565d60', endColorstr='#767d80', GradientType=0); /* IE8 and down */ -} -.subhead .inner { - padding: 36px 0 27px; -} -.subhead h1, -.subhead p { - text-align: left; -} -.subhead h1 { - font-size: 40px; -} -.subhead p a { - font-weight: normal; -} - - -/* Footer --------------------------------------------------- */ -.footer { - background-color: #eee; - min-width: 940px; - padding: 30px 0; - text-shadow: 0 1px 0 #fff; - border-top: 1px solid #e5e5e5; - -webkit-box-shadow: inset 0 5px 15px rgba(0,0,0,.025); - -moz-box-shadow: inset 0 5px 15px rgba(0,0,0,.025); -/* box-shadow: inset 0 5px 15px rgba(0,0,0,.025); -*/} -.footer p { - color: #555; -} - - -/* Quickstart section for getting le code --------------------------------------------------- */ -.quickstart { - background-color: #f5f5f5; - background-repeat: repeat-x; - background-image: -khtml-gradient(linear, left top, left bottom, from(#f9f9f9), to(#f5f5f5)); - background-image: -moz-linear-gradient(#f9f9f9, #f5f5f5); - background-image: -ms-linear-gradient(#f9f9f9, #f5f5f5); - background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #f9f9f9), color-stop(100%, #f5f5f5)); - background-image: -webkit-linear-gradient(#f9f9f9, #f5f5f5); - background-image: -o-linear-gradient(#f9f9f9, #f5f5f5); - -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='#f9f9f9', endColorstr='#f5f5f5', GradientType=0)"; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#f9f9f9', endColorstr='#f5f5f5', GradientType=0); - background-image: linear-gradient(#f9f9f9, #f5f5f5); - border-top: 1px solid #fff; - border-bottom: 1px solid #eee; -} -.quickstart .container { - margin-bottom: 0; -} -.quickstart .row { - margin: 0 -20px; - -webkit-box-shadow: 1px 0 0 #f9f9f9; - -moz-box-shadow: 1px 0 0 #f9f9f9; - box-shadow: 1px 0 0 #f9f9f9; -} -.quickstart [class*="span"] { - width: 285px; - height: 117px; - margin-left: 0; - padding: 17px 20px 26px; - border-left: 1px solid #eee; - -webkit-box-shadow: inset 1px 0 0 #f9f9f9; - -moz-box-shadow: inset 1px 0 0 #f9f9f9; - box-shadow: inset 1px 0 0 #f9f9f9; -} -.quickstart [class*="span"]:last-child { - border-right: 1px solid #eee; - width: 286px; -} -.quickstart h6, -.quickstart p { - line-height: 18px; - text-align: center; - margin-bottom: 9px; - color: #333; -} -.quickstart .current-version, -.quickstart .current-version a { - color: #999; -} -.quickstart h6 { - color: #999; -} -.quickstart textarea { - display: block; - width: 275px; - height: auto; - margin: 0 0 9px; - line-height: 21px; - white-space: nowrap; - overflow: hidden; -} - - -/* Special grid styles --------------------------------------------------- */ -.show-grid { - margin-top: 10px; - margin-bottom: 10px; -} -.show-grid [class*="span"] { - background: #eee; - text-align: center; - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - border-radius: 3px; - min-height: 30px; - line-height: 30px; -} -.show-grid:hover [class*="span"] { - background: #ddd; -} -.show-grid .show-grid { - margin-top: 0; - margin-bottom: 0; -} -.show-grid .show-grid [class*="span"] { - background-color: #ccc; -} - - -/* Render mini layout previews --------------------------------------------------- */ -.mini-layout { - border: 1px solid #ddd; - -webkit-border-radius: 6px; - -moz-border-radius: 6px; - border-radius: 6px; - -webkit-box-shadow: 0 1px 2px rgba(0,0,0,.075); - -moz-box-shadow: 0 1px 2px rgba(0,0,0,.075); - box-shadow: 0 1px 2px rgba(0,0,0,.075); -} -.mini-layout { - height: 240px; - margin-bottom: 20px; - padding: 9px; -} -.mini-layout div { - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - border-radius: 3px; -} -.mini-layout .mini-layout-body { - background-color: #dceaf4; - margin: 0 auto; - width: 240px; - height: 240px; -} -.mini-layout.fluid .mini-layout-sidebar, -.mini-layout.fluid .mini-layout-header, -.mini-layout.fluid .mini-layout-body { - float: left; -} -.mini-layout.fluid .mini-layout-sidebar { - background-color: #bbd8e9; - width: 90px; - height: 240px; -} -.mini-layout.fluid .mini-layout-body { - width: 300px; - margin-left: 10px; -} - - -/* Topbar special styles --------------------------------------------------- */ -.topbar-wrapper { - position: relative; - height: 40px; - margin: 5px 0 15px; -} -.topbar-wrapper .topbar { - position: absolute; - margin: 0 -20px; -} -.topbar-wrapper .topbar .topbar-inner { - padding-left: 20px; - padding-right: 20px; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; -} - -/* Topbar in js docs -------------------------- */ -#bootstrap-js .topbar-wrapper { - z-index: 1; -} -#bootstrap-js .topbar-wrapper .topbar { - position: absolute; - margin: 0 -20px; -} -#bootstrap-js .topbar-wrapper .topbar .topbar-inner { - padding-left: 20px; - padding-right: 20px; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; -} -#bootstrap-js .topbar-wrapper .container { - width: auto; -} - - -/* Popover docs --------------------------------------------------- */ -.popover-well { - min-height: 160px; -} -.popover-well .popover { - display: block; -} -.popover-well .popover-wrapper { - width: 50%; - height: 160px; - float: left; - margin-left: 55px; - position: relative; -} -.popover-well .popover-menu-wrapper { - height: 80px; -} -img.large-bird { - margin: 5px 0 0 310px; - opacity: .1; -} - -/* Pretty Print --------------------------------------------------- */ -pre.prettyprint { - overflow: hidden; -} \ No newline at end of file diff --git a/resources/stylesheets/frontpage.css b/resources/stylesheets/frontpage.css deleted file mode 100644 index 063166950f..0000000000 --- a/resources/stylesheets/frontpage.css +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Accordionza jQuery Plugin - * Copyright 2010, Geert De Deckere - */ - -#accordion2 { - background-color:rgba(0,0,0,0.5); - color:#fff; - height:160px; - list-style:none; - overflow:scroll; - padding:0; - width:940px; -} -ul, ol { - margin: 0 0 0 0; -} -#accordion2 li { - height:160px; /* Needed in case javascript is disabled */ - position:relative; - width: 700px; -} -#accordion2 .slide_thumb { - position:absolute; -} -#accordion2 p { - left:470px; - margin:0; - position:absolute; - width: 700px; -} -#accordion2 .slide_title { - font-size: 28px; - color: #fff; - text-shadow: 0 1px 1px rgba(0,0,0,.3); - top:30px; -} -#accordion2 .slide_content { - font-size: 20px; - color:rgba(255,255,255,0.5); - top:60px; -} -#accordion2 .slide_button { - font-size:10px; - letter-spacing:0.2em; - text-transform:uppercase; - top:110px; -} -#accordion2 .slide_button a { - background:rgb(34,92,122); /* Fallback */ - background:rgba(255,255,255,0.1); - border:1px solid rgba(255,255,255,0.1); - color:#fff; - padding:2px 4px; - text-decoration:none; - text-shadow:none; -} -#accordion2 .slide_button a:hover { - background:rgb(56,114,135); /* Fallback */ - background:rgba(255,255,255,0.2); -} \ No newline at end of file diff --git a/resources/stylesheets/pager.css b/resources/stylesheets/pager.css deleted file mode 100644 index cbcc02b20e..0000000000 --- a/resources/stylesheets/pager.css +++ /dev/null @@ -1,34 +0,0 @@ -/* Inspired by Bootstrap */ -.pager { - padding-left: 0; - margin: 20px 0; - list-style: none; -} -.pager li { - display: inline; -} -.pager li > a, -.pager li > span { - display: inline-block; - padding: 10px; - margin-bottom: 10px; - /*border-radius: 10px;*/ -} -.pager li > a:hover, -.pager li > a:focus { - text-decoration: none; -} - -.pager .previous > a, -.pager .previous > span { - padding-left: 0; -} - -.pager .disabled > a, -.pager .disabled > a:hover, -.pager .disabled > a:focus, -.pager .disabled > span { - color: #777; - cursor: not-allowed; - background-color: #fff; -} \ No newline at end of file diff --git a/resources/stylesheets/prettify.css b/resources/stylesheets/prettify.css deleted file mode 100644 index 113b049e69..0000000000 --- a/resources/stylesheets/prettify.css +++ /dev/null @@ -1,43 +0,0 @@ -.com { color: #93a1a1; } -.lit { color: #195f91; } -.pun, .opn, .clo { color: #93a1a1; } -.fun { color: #dc322f; } -.str, .atv { color: #268bd2; } -.kwd, .tag { color: #195f91; } -.typ, .atn, .dec, .var { color: #CB4B16; } -.pln { color: 888;/*color: #93a1a1;*/ } -pre.prettyprint { - background: #fefbf3; - padding: 9px; - border: 1px solid rgba(0,0,0,.2); - -webkit-box-shadow: 0 1px 2px rgba(0,0,0,.1); - -moz-box-shadow: 0 1px 2px rgba(0,0,0,.1); - box-shadow: 0 1px 2px rgba(0,0,0,.1); -} - -/* Specify class=linenums on a pre to get line numbering */ -ol.linenums { margin: 0 0 0 40px; } /* IE indents via margin-left */ -ol.linenums li { color: rgba(0,0,0,.15); line-height: 20px; } -/* Alternate shading for lines */ -li.L1, li.L3, li.L5, li.L7, li.L9 { } - -/* -$base03: #002b36; -$base02: #073642; -$base01: #586e75; -$base00: #657b83; -$base0: #839496; -$base1: #93a1a1; -$base2: #eee8d5; -$base3: #fdf6e3; -$yellow: #b58900; -$orange: #cb4b16; -$red: #dc322f; -$magenta: #d33682; -$violet: #6c71c4; -$blue: #268bd2; -$cyan: #2aa198; -$green: #859900; -*/ - -/*.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{*//*padding:2px;border:1px solid #888*//*}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee}*/ \ No newline at end of file diff --git a/resources/stylesheets/screen.css b/resources/stylesheets/screen.css deleted file mode 100644 index b2d3b9b708..0000000000 --- a/resources/stylesheets/screen.css +++ /dev/null @@ -1,143 +0,0 @@ -.highlight { - background: #ffffff; -} -.highlight .c { - color: #999988; - font-style: italic; -} -.highlight .err { - color: #a61717; - background-color: #e3d2d2; -} -.highlight .k, .highlight .o { - font-weight: bold; -} -.highlight .cm { - color: #999988; - font-style: italic; -} -.highlight .cp { - color: #999999; - font-weight: bold; -} -.highlight .c1 { - color: #999988; - font-style: italic; -} -.highlight .cs { - color: #999999; - font-weight: bold; - font-style: italic; -} -.highlight .gd { - color: #000000; - background-color: #ffdddd; -} -.highlight .gd .x { - color: #000000; - background-color: #ffaaaa; -} -.highlight .ge { - font-style: italic; -} -.highlight .gr { - color: #aa0000; -} -.highlight .gh { - color: #999999; -} -.highlight .gi { - color: #000000; - background-color: #ddffdd; -} -.highlight .gi .x { - color: #000000; - background-color: #aaffaa; -} -.highlight .go { - color: #888888; -} -.highlight .gp { - color: #555555; -} -.highlight .gs { - font-weight: bold; -} -.highlight .gu { - color: #aaaaaa; -} -.highlight .gt { - color: #aa0000; -} -.highlight .kc, .highlight .kd, .highlight .kp, .highlight .kr { - font-weight: bold; -} -.highlight .kt { - color: #445588; - font-weight: bold; -} -.highlight .m { - color: #009999; -} -.highlight .s { - color: #d14; -} -.highlight .na { - color: #008080; -} -.highlight .nb { - color: #0086B3; -} -.highlight .nc { - color: #445588; - font-weight: bold; -} -.highlight .no { - color: #008080; -} -.highlight .ni { - color: #800080; -} -.highlight .ne, .highlight .nf { - color: #990000; - font-weight: bold; -} -.highlight .nn { - color: #555555; -} -.highlight .nt { - color: #000080; -} -.highlight .nv { - color: #008080; -} -.highlight .ow { - font-weight: bold; -} -.highlight .w { - color: #bbbbbb; -} -.highlight .mf, .highlight .mh, .highlight .mi, .highlight .mo { - color: #009999; -} -.highlight .sb, .highlight .sc, .highlight .sd, .highlight .s2, .highlight .se, .highlight .sh, .highlight .si, .highlight .sx { - color: #d14; -} -.highlight .sr { - color: #009926; -} -.highlight .s1 { - color: #d14; -} -.highlight .ss { - color: #990073; -} -.highlight .bp { - color: #999999; -} -.highlight .vc, .highlight .vg, .highlight .vi { - color: #008080; -} -.highlight .il { - color: #009999; -} diff --git a/resources/stylesheets/style.css b/resources/stylesheets/style.css deleted file mode 100644 index 7c907acb59..0000000000 --- a/resources/stylesheets/style.css +++ /dev/null @@ -1,218 +0,0 @@ -@import url('reset.css'); -body{ - font-family:"Trebuchet MS", sans-serif; - font-size:14px; - background:#333; - color:#fff; -} -.header{ - text-align:center; - width:100%; - height:35px; - clear:both; - background:#000 url(../images/stripe.gif) repeat top left; - margin-bottom:20px; - border-bottom:7px solid #222; - font-size:11px; - line-height:35px; - font-style:italic; - text-shadow:1px 1px 1px #000; -} -.header a{ - color:#aaa; - text-shadow:1px 1px 1px #000; - padding-right:20px; -} -.header a:hover{ - color:#fff; -} -.container { - position:relative; /* needed for footer positioning*/ - margin:0 auto; /* center, not in IE5 */ - width:100%; - height:auto !important; /* real browsers */ - height:100%; /* IE6: treaded as min-height*/ - min-height:100%; /* real browsers */ -} -.left{ - float:left; - margin-left:10px; -} -.back{ - position:absolute; - right:10px; - top:0px; -} -.content > h1{ - font-size:40px; - font-weight:normal; - text-shadow:0px 0px 1px #fff; - font-family: 'Raleway', arial, serif; - border-bottom:1px dotted #444; - padding:10px 20px; -} -.content > h3{ - font-size:24px; - color:#aaa; - font-weight:normal; - padding:10px 20px; - text-shadow:1px 1px 1px #000; - font-family: 'Rock Salt', arial, serif; -} -h3 a{ - font-size:14px; - padding-left:20px; -} -a{ - color:#777; - text-decoration:none; -} -a:hover{ - color:#fff; -} -.content { - margin:0 auto; - padding:0px 0px 58px 0px; /* Footer Padding */ -} -.footer { - position:absolute; - width:100%; - height:50px; - line-height:50px; - bottom:0; /* stick to bottom */ - background:#f0f0f0; - border-top:7px solid #222; - text-align:center; - text-shadow:1px 1px 1px #000; - color:#fff; - background:#000 url(../images/stripe.gif) repeat top left; -} -.footer a{ - color:#aaa; - padding:0px 10px; - text-shadow:1px 1px 1px #000; -} -.footer a:hover{ - color:#fff; - text-shadow:0px 0px 1px #fff; -} -/* Menu style */ -.ei_menu{ - background:#111; - width:100%; - overflow:hidden; -} -.ei_menu ul{ - height:350px; - margin-left:50px; - position:relative; - display:block; - width:1300px; -} -.ei_menu ul li{ - float:left; - width:75px; - height:350px; - position:relative; - overflow:hidden; - border-right:2px solid #111; -} -.ei_preview{ - width:75px; - height:350px; - cursor:pointer; - position:absolute; - top:0px; - left:0px; - background:transparent url(../images/bw.jpg) no-repeat top left; -} -.ei_image{ - position:absolute; - left:75px; - top:0px; - width:75px; - height:350px; - opacity:0.2; - background:transparent url(../images/color.jpg) no-repeat top left; -} -.pos1 span{ - background-position:0px 0px; -} -.pos2 span{ - background-position:-75px 0px; -} -.pos3 span{ - background-position:-152px 0px; -} -.pos4 span{ - background-position:-227px 0px; -} -.pos5 span{ - background-position:-302px 0px; -} -.pos6 span{ - background-position:-377px 0px; -} -.ei_descr{ - position:absolute; - width:278px; - height:310px; - border-right:7px solid #f0f0f0; - padding:20px; - left:75px; - top:0px; - background:#fff; -} -.ei_descr h2{ - font-family: 'Rock Salt', arial, serif; - font-size:26px; - color:#333; - padding:10px; - text-shadow:0px 0px 1px #fff; - background:#fff url(../images/stripe_light.gif) repeat top left; -} -.ei_descr h3{ - font-family: 'Raleway', arial, serif; - color:#fff; - text-shadow:0px 0px 1px #000; - font-style:normal; - padding:10px; - background:#333; -} -.ei_descr p{ - color:#000; - padding:10px 5px 0px 5px; - line-height:18px; - font-size:11px; - font-family: Arial; - text-transform:uppercase; -} - -/* For the index_3 demo */ -ul.trigger_list{ - position:absolute; - right:20px; - top:145px; -} -ul.trigger_list li{ - float:left; - line-height:53px; - color:#ddd; - font-style:italic; -} -ul.trigger_list li a{ - font-family: 'Rock Salt', arial, serif; - display:block; - background:#000; - color:#ddd; - line-height:35px; - padding:5px 10px; - margin:3px; - border-radius:5px 5px 5px 5px; - text-shadow:1px 1px 1px #000; -} -ul.trigger_list li a:hover{ - background:#222; - color:#fff; - -} \ No newline at end of file diff --git a/resources/stylesheets/syntax.css b/resources/stylesheets/syntax.css deleted file mode 100644 index 1e651cf79d..0000000000 --- a/resources/stylesheets/syntax.css +++ /dev/null @@ -1,60 +0,0 @@ -.highlight { background: #ffffff; } -.highlight .c { color: #999988; font-style: italic } /* Comment */ -.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ -.highlight .k { font-weight: bold } /* Keyword */ -.highlight .o { font-weight: bold } /* Operator */ -.highlight .cm { color: #999988; font-style: italic } /* Comment.Multiline */ -.highlight .cp { color: #999999; font-weight: bold } /* Comment.Preproc */ -.highlight .c1 { color: #999988; font-style: italic } /* Comment.Single */ -.highlight .cs { color: #999999; font-weight: bold; font-style: italic } /* Comment.Special */ -.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ -.highlight .gd .x { color: #000000; background-color: #ffaaaa } /* Generic.Deleted.Specific */ -.highlight .ge { font-style: italic } /* Generic.Emph */ -.highlight .gr { color: #aa0000 } /* Generic.Error */ -.highlight .gh { color: #999999 } /* Generic.Heading */ -.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ -.highlight .gi .x { color: #000000; background-color: #aaffaa } /* Generic.Inserted.Specific */ -.highlight .go { color: #888888 } /* Generic.Output */ -.highlight .gp { color: #555555 } /* Generic.Prompt */ -.highlight .gs { font-weight: bold } /* Generic.Strong */ -.highlight .gu { color: #aaaaaa } /* Generic.Subheading */ -.highlight .gt { color: #aa0000 } /* Generic.Traceback */ -.highlight .kc { font-weight: bold } /* Keyword.Constant */ -.highlight .kd { font-weight: bold } /* Keyword.Declaration */ -.highlight .kp { font-weight: bold } /* Keyword.Pseudo */ -.highlight .kr { font-weight: bold } /* Keyword.Reserved */ -.highlight .kt { color: #445588; font-weight: bold } /* Keyword.Type */ -.highlight .m { color: #009999 } /* Literal.Number */ -.highlight .s { color: #d14 } /* Literal.String */ -.highlight .na { color: #008080 } /* Name.Attribute */ -.highlight .nb { color: #0086B3 } /* Name.Builtin */ -.highlight .nc { color: #445588; font-weight: bold } /* Name.Class */ -.highlight .no { color: #008080 } /* Name.Constant */ -.highlight .ni { color: #800080 } /* Name.Entity */ -.highlight .ne { color: #990000; font-weight: bold } /* Name.Exception */ -.highlight .nf { color: #990000; font-weight: bold } /* Name.Function */ -.highlight .nn { color: #555555 } /* Name.Namespace */ -.highlight .nt { color: #000080 } /* Name.Tag */ -.highlight .nv { color: #008080 } /* Name.Variable */ -.highlight .ow { font-weight: bold } /* Operator.Word */ -.highlight .w { color: #bbbbbb } /* Text.Whitespace */ -.highlight .mf { color: #009999 } /* Literal.Number.Float */ -.highlight .mh { color: #009999 } /* Literal.Number.Hex */ -.highlight .mi { color: #009999 } /* Literal.Number.Integer */ -.highlight .mo { color: #009999 } /* Literal.Number.Oct */ -.highlight .sb { color: #d14 } /* Literal.String.Backtick */ -.highlight .sc { color: #d14 } /* Literal.String.Char */ -.highlight .sd { color: #d14 } /* Literal.String.Doc */ -.highlight .s2 { color: #d14 } /* Literal.String.Double */ -.highlight .se { color: #d14 } /* Literal.String.Escape */ -.highlight .sh { color: #d14 } /* Literal.String.Heredoc */ -.highlight .si { color: #d14 } /* Literal.String.Interpol */ -.highlight .sx { color: #d14 } /* Literal.String.Other */ -.highlight .sr { color: #009926 } /* Literal.String.Regex */ -.highlight .s1 { color: #d14 } /* Literal.String.Single */ -.highlight .ss { color: #990073 } /* Literal.String.Symbol */ -.highlight .bp { color: #999999 } /* Name.Builtin.Pseudo */ -.highlight .vc { color: #008080 } /* Name.Variable.Class */ -.highlight .vg { color: #008080 } /* Name.Variable.Global */ -.highlight .vi { color: #008080 } /* Name.Variable.Instance */ -.highlight .il { color: #009999 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/resources/stylesheets/toc.css b/resources/stylesheets/toc.css deleted file mode 100644 index e60f1f8851..0000000000 --- a/resources/stylesheets/toc.css +++ /dev/null @@ -1,4 +0,0 @@ -li.current-page { - list-style: circle; - font-weight: bold; -} \ No newline at end of file diff --git a/ru/overviews/parallel-collections/architecture.md b/ru/overviews/parallel-collections/architecture.md deleted file mode 100644 index 266c4b9e83..0000000000 --- a/ru/overviews/parallel-collections/architecture.md +++ /dev/null @@ -1,68 +0,0 @@ ---- -layout: overview-large -title: Архитектура библиотеки параллельных коллекций - -discourse: false - -partof: parallel-collections -language: ru -num: 5 ---- - -Как и обычная библиотека коллекций Scala, библиотека параллельных коллекций содержит большое количество операций, для которых, в свою очередь, существует множество различных реализаций. И так же, как последовательная, параллельная библиотека избегает повторений кода путем реализации большинства операций посредством собственных "шаблонов", которые достаточно объявить один раз, а потом наследовать в различных реализациях параллельных коллекций. - -Преимущества этого подхода сильно облегчают **поддержку** и **расширяемость**. Поддержка станет простой и надежной, когда одна реализация операции над параллельной коллекцией унаследуется всеми параллельными коллекциями; исправления ошибок в этом случае сами распространятся вниз по иерархии классов, а не потребуют дублировать реализации. По тем же причинам всю библиотеку проще расширять-- новые классы коллекций наследуют большинство имеющихся операций. - - -## Ключевые абстракции - -Упомянутые выше "шаблонные" трейты реализуют большинство параллельных операций в терминах двух ключевых абстракций -- разделителей (`Splitter`) и компоновщиков (`Combiner`). - -### Разделители - -Задача разделителя `Splitter`, как и предполагает имя, заключается в том, чтобы разбить параллельную коллекцию на непустые разделы. А основная идея-- в том, чтобы разбивать коллекцию на более мелкие части, пока их размер не станет подходящим для последовательной обработки. - - trait Splitter[T] extends Iterator[T] { - def split: Seq[Splitter[T]] - } - -Что интересно, разделители `Splitter` реализованы через итераторы-- `Iterator`, а это подразумевает, что помимо разделения, они позволяют перебирать элементы параллельной коллекции (то есть, наследуют стандартные методы трейта `Iterator`, такие, как `next` и `hasNext`.) Уникальность этого "разделяющего итератора" в том, что его метод `split` разбивает текущий объект `this` (мы помним, что `Splitter`, это подтип `Iterator`а) на другие разделители `Splitter`, каждый из которых перебирает свой, **отделенный** набор элементов когда-то целой параллельной коллекции. И так же, как любой нормальный `Iterator`, `Splitter` становится недействительным после того, как вызван его метод `split`. - -Как правило, коллекции разделяются `Splitter`ами на части примерно одинакового размера. В случаях, когда требуются разделы произвольного размера, особенно в параллельных последовательностях, используется `PreciseSplitter`, который является наследником `Splitter` и дополнительно реализует точный метод разделения, `psplit`. - -### Компоновщики - -Компоновщик `Combiner` можно представить себе как обобщенный `Builder` из библиотеки последовательных коллекций Scala. У каждой параллельной коллекции есть свой отдельный `Combiner`, так же, как у каждой последовательной есть свой `Builder`. - -Если в случае с последовательными коллекциями элементы можно добавлять в `Builder`, а потом получить коллекцию, вызвав метод `result`, то при работе с параллельными требуется вызвать у компоновщика метод `combine`, который берет аргументом другой компоновщик и делает новый `Combiner`. Результатом будет компоновщик, содержащий объединенный набор. После вызова метода `combine` оба компоновщика становятся недействительными. - - trait Combiner[Elem, To] extends Builder[Elem, To] { - def combine(other: Combiner[Elem, To]): Combiner[Elem, To] - } - -Два параметра-типа в примере выше, `Elem` и `To`, просто обозначают тип элемента и тип результирующей коллекции соответственно. - -_Примечание:_ Если есть два `Combiner`а, `c1` и `c2` где `c1 eq c2` равняется `true` (то есть, они являются одним и тем же `Combiner`ом), вызов `c1.combine(c2)` всегда ничего не делает, а просто возвращает исходный `Combiner`, то есть `c1`. - - -## Иерархия - -Параллельные коллекции Scala во многом созданы под влиянием дизайна библиотеки (последовательных) коллекций Scala. На рисунке ниже показано, что их дизайн фактически отражает соответствующие трейты фреймворка обычных коллекций. - -[]({{ site.baseurl }}/resources/images/parallel-collections-hierarchy.png) - -
                  Иерархия библиотеки Scala: коллекции и параллельные коллекции
                  -
                  - -Цель, конечно же, в том, чтобы интегрировать параллельные коллекции с последовательными настолько тесно, насколько это возможно, так, чтобы можно было без дополнительных усилий заменять последовательные коллекции параллельными (и наоборот). - -Чтобы можно было получить ссылку на коллекцию, которая может быть либо последовательной, либо параллельной (так, чтобы было возможно "переключаться" между параллельной и последовательной коллекции вызовами `par` и `seq` соответственно), у обоих типов коллекций должен быть общий предок. Этим источником "обобщенных" трейтов, как показано выше, являются `GenTraversable`, `GenIterable`, `GenSeq`, `GenMap` и `GenSet`, которые не гарантируют того, что элементы будут обрабатываться по-порядку или по-одному. Отсюда наследуются соответствующие последовательные и параллельные трейты; например, `ParSeq` и `Seq` являются подтипами общей последовательности `GenSeq`, а не унаследованы друг от друга. - -Более подробное обсуждение иерархии, разделяемой последовательными и параллельными коллекциями, можно найти в техническом отчете. \[[1][1]\] - - -## Ссылки - -1. [On a Generic Parallel Collection Framework, Aleksandar Prokopec, Phil Bawgell, Tiark Rompf, Martin Odersky, June 2011][1] - -[1]: http://infoscience.epfl.ch/record/165523/files/techrep.pdf "flawed-benchmark" diff --git a/ru/overviews/parallel-collections/concrete-parallel-collections.md b/ru/overviews/parallel-collections/concrete-parallel-collections.md deleted file mode 100644 index bfae84b45a..0000000000 --- a/ru/overviews/parallel-collections/concrete-parallel-collections.md +++ /dev/null @@ -1,165 +0,0 @@ ---- -layout: overview-large -title: Конкретные классы параллельных коллекций - -discourse: false - -partof: parallel-collections -language: ru -num: 2 ---- - -## Параллельный Массив - -Последовательность [ParArray](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/parallel/mutable/ParArray.html) хранит линейный массив смежно хранимых элементов. Это означает, что получение доступа и обновление элементов эффективно, так как происходит путем изменения массива, лежащего в основе. По этой причине наиболее эффективна последовательная обработка элементов одного за другим. Параллельные массивы похожи на обычные в том отношении, что их размер постоянен. - - scala> val pa = scala.collection.parallel.mutable.ParArray.tabulate(1000)(x => 2 * x + 1) - pa: scala.collection.parallel.mutable.ParArray[Int] = ParArray(1, 3, 5, 7, 9, 11, 13,... - - scala> pa reduce (_ + _) - res0: Int = 1000000 - - scala> pa map (x => (x - 1) / 2) - res1: scala.collection.parallel.mutable.ParArray[Int] = ParArray(0, 1, 2, 3, 4, 5, 6, 7,... - -Реализация разбивки параллельного массива [разделителями]({{ site.baseurl }}/ru/overviews/parallel-collections/architecture.html#core_abstractions) сводится к созданию двух новых разделителей с последующим обновлением их итерационных индексов. [Компоновщики]({{ site.baseurl }}/ru/overviews/parallel-collections/architecture.html#core_abstractions) играют более заметную роль. Так как мы не знаем заранее количество элементов (и следовательно, размер массива) при выполнении большинства методов трансформации (например, `flatMap`, `filter`, `takeWhile`, и т.д.), каждый компоновщик, в сущности, является массивом-буфером, у которого операция `+=` требует для выполнения амортизированное постоянное время. Разные процессоры добавляют элементы к отдельным компоновщикам параллельного массива, которые потом по цепочке объединяют свои внутренние массивы. Лежащий в основе массив размещается и параллельно заполняется только после того, как становится известным общее число элементов. По этой причине методы трансформации требуют больше ресурсов, чем методы получения доступа. Также стоит заметить, что финальное размещение массива выполняется JVM последовательно, поэтому этот момент может стать узким местом, если даже сама операция отображения весьма нересурсоемкая. - -Вызов метода `seq` приводит к преобразованию параллельного массива в коллекцию `ArraySeq`, которая является его последовательным аналогом. Такое преобразование эффективно, и в основе `ArraySeq` остается тот же массив, что и был у исходного параллельного. - -## Параллельный вектор - -[ParVector](http://www.scala-lang.org/api/{{ site.scala-version}}/scala/collection/parallel/immutable/ParVector.html) является неизменяемой последовательностью, временная сложность доступа и обновления которой является логарифмической с низкой константой-множителем. - - scala> val pv = scala.collection.parallel.immutable.ParVector.tabulate(1000)(x => x) - pv: scala.collection.parallel.immutable.ParVector[Int] = ParVector(0, 1, 2, 3, 4, 5, 6, 7, 8, 9,... - - scala> pv filter (_ % 2 == 0) - res0: scala.collection.parallel.immutable.ParVector[Int] = ParVector(0, 2, 4, 6, 8, 10, 12, 14, 16, 18,... - -Неизменяемые векторы представлены 32-ичными деревьями (32-way trees), поэтому [разделители]({{ site.baseurl }}/ru/overviews/parallel-collections/architecture.html#core_abstractions) разбивают их, назначая по поддереву каждому новому разделителю. -[Компоновщики]({{ site.baseurl }}/ru/overviews/parallel-collections/architecture.html#core_abstractions) в настоящий момент хранят вектор из элементов и компонуют путем отложенного копирования. По этой причине методы трансформации менее масштабируемы по сравнению с теми же методами параллельного массива. Как только в будущем релизе Scala станет доступной операция конкатенации векторов, компоновщики станут образовываться путем конкатенации, и от этого методы трансформации станут гораздо более эффективными. - -Параллельный вектор является параллельным аналогом последовательной коллекции [Vector](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Vector.html), и преобразования одного в другое занимают постоянное время. - -## Параллельный диапазон - -[ParRange](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/parallel/immutable/ParRange.html) представляет собой упорядоченную последовательность элементов, отстоящих друг от друга на одинаковые промежутки. Параллельный диапазон создается подобно последовательному [Range](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Range.html): - - scala> 1 to 3 par - res0: scala.collection.parallel.immutable.ParRange = ParRange(1, 2, 3) - - scala> 15 to 5 by -2 par - res1: scala.collection.parallel.immutable.ParRange = ParRange(15, 13, 11, 9, 7, 5) - -Подобно тому, как последовательные диапазоны не имеют строителей, параллельные диапазоны не имеют [компоновщиков]({{ site.baseurl }}/ru/overviews/parallel-collections/architecture.html#core_abstractions). При создании отображения (mapping) элементов параллельного диапазона получается параллельный вектор. Последовательные и параллельные диапазоны могут эффективно преобразовываться друг в друга вызовами методов `seq` и `par`. - -## Параллельные хэш-таблицы - -В основе параллельной хэш-таблицы лежит массив, причем место элемента таблицы в этом массиве определяется хэш-кодом элемента. На хэш-таблицах основаны параллельные изменяемые хэш-множества ([mutable.ParHashSet](http://www.scala-lang.org/api/{{ site.scala-version}}/scala/collection/parallel/mutable/ParHashSet.html)) и параллельные изменяемые ассоциативные хэш-массивы (хэш-отображения) ([mutable.ParHashMap](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/parallel/mutable/ParHashMap.html)). - - scala> val phs = scala.collection.parallel.mutable.ParHashSet(1 until 2000: _*) - phs: scala.collection.parallel.mutable.ParHashSet[Int] = ParHashSet(18, 327, 736, 1045, 773, 1082,... - - scala> phs map (x => x * x) - res0: scala.collection.parallel.mutable.ParHashSet[Int] = ParHashSet(2181529, 2446096, 99225, 2585664,... - -Компоновщики параллельных хэш-таблиц распределяют элементы по блокам в соответствии с префиксом их хэш-кода. Компонуют же они простой конкатенацией таких блоков. Когда хэш-таблица окажется окончательно сформированной (то есть, когда будет вызван метод `result` компоновщика), размещается лежащий в основе массив и элементы из различных блоков параллельно копируются в различные смежнолежащие сегменты этого массива. - -Последовательные хэш-отображения и хэш-множества могут преобразовываться в свои параллельные аналоги с помощью метода `par`. Внутри параллельной хэш-таблицы требуется поддерживать карту размеров, которая отслеживает количество элементов в различных ее частях. Это значит, что при первом преобразовании последовательной хэш-таблицы в параллельную, вся она просматривается с целью создания карты размеров - по этой причине первый вызов метода `par` требует линейного по отношению к числу элементов времени выполнения. При дальнейших изменениях хэш-таблицы ее карта размеров поддерживается в актуальном состоянии, поэтому последующие преобразования вызовами `par` и `seq` имеют постоянную сложность. Впрочем, поддержку карты размеров можно и отключить, используя метод `useSizeMap` хэш-таблицы. Важный момент: изменения, сделанные в последовательной хэш-таблице, видны в параллельной, и наоборот. - -## Параллельные префиксные хэш-деревья (Hash Tries) - -Параллельные префиксные хэш-деревья являются параллельным аналогом неизменяемых префиксных хэш-деревьев, которые используются для эффективного представления неизменяемых множеств и ассоциативных массивов. Последние представлены классами [immutable.ParHashSet](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/parallel/immutable/ParHashSet.html) и [immutable.ParHashMap](http://www.scala-lang.org/api/{{ site.scala-version}}/scala/collection/parallel/immutable/ParHashMap.html). - - scala> val phs = scala.collection.parallel.immutable.ParHashSet(1 until 1000: _*) - phs: scala.collection.parallel.immutable.ParHashSet[Int] = ParSet(645, 892, 69, 809, 629, 365, 138, 760, 101, 479,... - - scala> phs map { x => x * x } sum - res0: Int = 332833500 - -[Компоновщики]({{ site.baseurl }}/overviews/parallel-collections/architecture.html#core_abstractions) параллельных хэш-деревьев действуют аналогично компоновщикам хэш-таблиц, а именно предварительно распределяют элементы по блокам, а после этого параллельно составляют результирующее хэш-дерево, назначая обработку различных блоков разным процессорам, каждый из которых независимо собирает свое поддерево. - -Параллельные хэш-деревья могут за постоянное время преобразовываться вызовами методов `seq` и `par` в последовательные хэш-деревья и обратно. - -## Параллельные многопоточные префиксные деревья (Concurrent Tries) - -Параллельным аналогом коллекции [concurrent.TrieMap](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/concurrent/TrieMap.html), представляющей собой многопоточный и потокозащищеный ассоциативный массив, является коллекция [mutable.ParTrieMap](http://www.scala-lang.org/api/{{ site.scala-version}}/scala/collection/parallel/mutable/ParTrieMap.html). В то время, как большинство многопоточных структур данных не гарантируют правильного перебора элементов в случае, если эта структура данных была изменена во время ее прохождения, многопоточные деревья `Ctries` гарантируют, что обновленные данные станут видны только при следующем прохождении. Это означает, что можно изменять многопоточное дерево прямо во время прохождения, как в следующем примере, в котором выводятся квадратные корни от 1 до 99: - - scala> val numbers = scala.collection.parallel.mutable.ParTrieMap((1 until 100) zip (1 until 100): _*) map { case (k, v) => (k.toDouble, v.toDouble) } - numbers: scala.collection.parallel.mutable.ParTrieMap[Double,Double] = ParTrieMap(0.0 -> 0.0, 42.0 -> 42.0, 70.0 -> 70.0, 2.0 -> 2.0,... - - scala> while (numbers.nonEmpty) { - | numbers foreach { case (num, sqrt) => - | val nsqrt = 0.5 * (sqrt + num / sqrt) - | numbers(num) = nsqrt - | if (math.abs(nsqrt - sqrt) < 0.01) { - | println(num, nsqrt) - | numbers.remove(num) - | } - | } - | } - (1.0,1.0) - (2.0,1.4142156862745097) - (7.0,2.64576704419029) - (4.0,2.0000000929222947) - ... - -[Компоновщики]({{ site.baseurl }}/ru/overviews/parallel-collections/architecture.html#core_abstractions) реализованы как `TrieMap`-- так как эта структура является многопоточной, при вызове метода трансформации создается только один компоновщик, разделяемый всеми процессорами. - -Как и в случае с другими параллельными изменяемыми коллекциями, экземпляры `TrieMap` и параллельных `ParTrieMap`, полученные вызовом методов `seq` или `par`, хранят данные в одном и том же хранилище, поэтому модификации одной коллекции видны в другой. Такие преобразования занимают постоянное время. - -## Характеристики производительности - -Характеристики производительности последовательных типов (sequence types): - -| | head | tail | apply | update| prepend | append | insert | -| -------- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | -| `ParArray` | C | L | C | C | L | L | L | -| `ParVector` | eC | eC | eC | eC | eC | eC | - | -| `ParRange` | C | C | C | - | - | - | - | - -Характеристики производительности множеств (set) и ассоциативных массивов (map): - -| | lookup | add | remove | -| -------- | ---- | ---- | ---- | -| **неизменяемые** | | | | -| `ParHashSet`/`ParHashMap`| eC | eC | eC | -| **изменяемые** | | | | -| `ParHashSet`/`ParHashMap`| C | C | C | -| `ParTrieMap` | eC | eC | eC | - - -### Расшифровка - -Обозначения в двух представленных выше таблицах означают следующее: - -| | | -| --- | ---- | -| **C** | Операция (быстрая) выполняется за постоянное время. | -| **eC** | Операция выполняется за фактически постоянное время, но только при соблюдении некоторых предположений, например о максимальной длине вектора или распределении хэш-кодов.| -| **aC** | Операция выполняется за амортизированное постоянное время. Некоторые вызовы операции могут выполняться медленнее, но при подсчете времени выполнения большого количества операций выходит, что в среднем на операцию требуется постоянное время. | -| **Log** | Операция занимает время, пропорциональное логарифму размера коллекции. | -| **L** | Операция линейна, то есть занимает время, пропорциональное размеру коллекции. | -| **-** | Операция не поддерживается. | - -Первая таблица трактует последовательные типы-- изменяемые и неизменяемые-- в контексте выполнения следующих операций: - -| | | -| --- | ---- | -| **head** | Получение первого элемента последовательности. | -| **tail** | Получение новой последовательности, состоящей из всех элементов исходной, кроме первого. | -| **apply** | Индексирование. | -| **update** | Функциональное обновление (с помощью `updated`) для неизменяемых последовательностей, обновление с побочными действиями (с помощью `update`) для изменяемых. | -| **prepend**| Добавление элемента в начало последовательности. Для неизменяемых последовательностей создается новая последовательность, для изменяемых-- модифицируется существующая. | -| **append** | Добавление элемента в конец последовательности. Для неизменяемых последовательностей создается новая последовательность, для изменяемых-- модифицируется существующая. | -| **insert** | Вставка элемента в выбранную позицию последовательности. Поддерживается только изменяемыми последовательностями. | - -Вторая таблица рассматривает изменяемые и неизменяемые множества и ассоциативные массивы в контексте следующих операций: - -| | | -| --- | ---- | -| **lookup** | Проверка принадлежности элемента множеству, или получение значения, ассоциированного с ключом. | -| **add** | Добавление нового элемента во множество или новой пары ключ/значение в ассоциативный массив. | -| **remove** | Удаление элемента из множества или ключа из ассоциативного массива. | -| **min** | Минимальный элемент множества или минимальный ключ ассоциативного массива. | - diff --git a/ru/overviews/parallel-collections/configuration.md b/ru/overviews/parallel-collections/configuration.md deleted file mode 100644 index b6e063f617..0000000000 --- a/ru/overviews/parallel-collections/configuration.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -layout: overview-large -title: Конфигурирование параллельных коллекций - -discourse: false - -partof: parallel-collections -language: ru -num: 7 ---- - -## Обслуживание задач - -Параллельные коллекции предоставляют возможность выбора методов планирования задач и распределения нагрузки на процессоры. В числе параметров каждой параллельной коллекции есть так называемый объект обслуживания задач, который и отвечает за это планирование. - -Внутри объект обслуживания задач содержит ссылку на пул потоков; кроме того он определяет, как и когда задачи разбиваются на более мелкие подзадачи. Подробнее о том, как конкретно происходит этот процесс, можно узнать в техническом отчете \[[1][1]\]. - -В настоящее время для параллельных коллекций доступно несколько реализаций объекта поддержки задач. Например, `ForkJoinTaskSupport` реализован посредством "fork-join" пула и используется по умолчанию на JVM 1.6 или более поздних. Менее эффективный `ThreadPoolTaskSupport` является резервом для JVM 1.5 и тех машин, которые не поддерживают пулы "fork-join". `ExecutionContextTaskSupport` по умолчанию берет из `scala.concurrent` объект контекста выполнения `ExecutionContext` и, таким образом, использует тот же пул потоков, что и `scala.concurrent` (в зависимости от версии JVM, это может быть пул "fork-join" или "thread pool executor"). По умолчанию каждой параллельной коллекции назначается именно обслуживание задач контекста выполнения, поэтому параллельные коллекции используют тот же пул "fork-join", что и API объектов "future". - -Сменить метод обслуживания задач для параллельной коллекции можно так: - - scala> import scala.collection.parallel._ - import scala.collection.parallel._ - - scala> val pc = mutable.ParArray(1, 2, 3) - pc: scala.collection.parallel.mutable.ParArray[Int] = ParArray(1, 2, 3) - - scala> pc.tasksupport = new ForkJoinTaskSupport(new scala.concurrent.forkjoin.ForkJoinPool(2)) - pc.tasksupport: scala.collection.parallel.TaskSupport = scala.collection.parallel.ForkJoinTaskSupport@4a5d484a - - scala> pc map { _ + 1 } - res0: scala.collection.parallel.mutable.ParArray[Int] = ParArray(2, 3, 4) - -Код выше настраивает параллельную коллекцию на использование "fork-join" пула с количеством потоков равным 2. Заставить коллекцию использовать "thread pool executor" можно так: - - scala> pc.tasksupport = new ThreadPoolTaskSupport() - pc.tasksupport: scala.collection.parallel.TaskSupport = scala.collection.parallel.ThreadPoolTaskSupport@1d914a39 - - scala> pc map { _ + 1 } - res1: scala.collection.parallel.mutable.ParArray[Int] = ParArray(2, 3, 4) - -Когда параллельная коллекция сериализуется, поле объекта обслуживания задач исключается из сериализуемых. Когда параллельная коллекция восстанавливается из полученной последовательности байт, это поле приобретает значение по умолчанию, то есть способ обслуживания задач берется из `ExecutionContext`. - -Чтобы реализовать собственный механизм поддержки задач, достаточно унаследовать трейт `TaskSupport` и реализовать следующие методы: - - def execute[R, Tp](task: Task[R, Tp]): () => R - - def executeAndWaitResult[R, Tp](task: Task[R, Tp]): R - - def parallelismLevel: Int - -Метод `execute` планирует асинхронное выполнение задачи и возвращает "future" в качестве ссылки к будущему результату выполнения. Метод `executeAndWait` делает то же самое, но возвращает результат только после завершения задачи. Метод `parallelismLevel` просто возвращает предпочитаемое количество ядер, которое будет использовано для вычислений. - -## Ссылки - -1. [On a Generic Parallel Collection Framework, June 2011][1] - - [1]: http://infoscience.epfl.ch/record/165523/files/techrep.pdf "parallel-collections" diff --git a/ru/overviews/parallel-collections/conversions.md b/ru/overviews/parallel-collections/conversions.md deleted file mode 100644 index 8372fcf90d..0000000000 --- a/ru/overviews/parallel-collections/conversions.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -layout: overview-large -title: Преобразования параллельных коллекций - -discourse: false - -partof: parallel-collections -language: ru -num: 3 ---- - -## Взаимные преобразования последовательных и параллельных коллекций - -Любая последовательная коллекция может быть преобразована в свою параллельную альтернативу вызовом метода `par`, причем некоторые типы последовательных коллекций имеют прямой параллельный аналог. Для таких коллекций конвертация эффективна-- она занимает постоянное время, так как и последовательная и параллельная коллекция представлены одной и той же структурой данных (за исключением изменяемых хэш-таблиц и хэш-множеств, преобразование которых требует больше времени в первый вызов метода `par`, тогда как последующие вызовы `par` занимают постоянное время). Нужно заметить, что если изменяемые коллекции делят одну лежащую в основе структуру данных, то изменения, сделанные в последовательной коллекции, будут видны в ее параллельной ответной части. - -| Последовательные | Параллельные | -| ---------------- | -------------- | -| **изменяемые** | | -| `Array` | `ParArray` | -| `HashMap` | `ParHashMap` | -| `HashSet` | `ParHashSet` | -| `TrieMap` | `ParTrieMap` | -| **неизменяемые** | | -| `Vector` | `ParVector` | -| `Range` | `ParRange` | -| `HashMap` | `ParHashMap` | -| `HashSet` | `ParHashSet` | - -Другие коллекции, такие, как списки, очереди или потоки, последовательны по своей сути, в том смысле, что элементы должны выбираться один за другим. Такие коллекции преобразуются в свои параллельные альтернативы копированием элементов в схожую параллельную коллекцию. Например, односвязный список преобразуется в стандартную неизменяемую параллельную последовательность, то есть в параллельный вектор. - -Любая параллельная коллекция может быть преобразована в её последовательный вариант вызовом метода `seq`. Конвертирование параллельной коллекции в последовательную эффективно всегда-- оно занимает постоянное время. Вызов `seq` на изменяемой параллельной коллекции возвращает последовательную, которая отображает ту же область памяти-- изменения, сделанные в одной коллекции, будут видимы в другой. - -## Преобразования между различными типами коллекций - -Параллельные коллекции могут конвертироваться в другие типы коллекций, не теряя при этом своей параллельности. Например, вызов метода `toSeq` последовательное множество преобразует в обычную последовательность, а параллельное-- в параллельную. Общий принцип такой: если есть параллельный вариант коллекции `X`, то метод `toX` преобразует коллекцию к типу `ParX`. - -Ниже приведена сводная таблица всех методов преобразования: - -| Метод | Тип возвращаемого значения | -| -------------- | -------------------------- | -| `toArray` | `Array` | -| `toList` | `List` | -| `toIndexedSeq` | `IndexedSeq` | -| `toStream` | `Stream` | -| `toIterator` | `Iterator` | -| `toBuffer` | `Buffer` | -| `toTraversable`| `GenTraverable` | -| `toIterable` | `ParIterable` | -| `toSeq` | `ParSeq` | -| `toSet` | `ParSet` | -| `toMap` | `ParMap` | diff --git a/ru/overviews/parallel-collections/ctries.md b/ru/overviews/parallel-collections/ctries.md deleted file mode 100644 index 3a23b67f28..0000000000 --- a/ru/overviews/parallel-collections/ctries.md +++ /dev/null @@ -1,121 +0,0 @@ ---- -layout: overview-large -title: Многопоточные префиксные деревья - -discourse: false - -partof: parallel-collections -language: ru -num: 4 ---- - -Большинство многопоточных структур данных не гарантирует неизменности порядка элементов в случае, если эта структура изменяется во время прохождения. То же верно, кстати, и в случае большинства изменяемых коллекций. Особенность многопоточных префиксных деревьев-- `tries`-- заключается в том, что они позволяют модифицировать само дерево, которое в данный момент просматривается. Сделанные изменения становятся видимыми только при следующем прохождении. Так ведут себя и последовательные префиксные деревья, и их параллельные аналоги; единственное отличие-- в том, что первые перебирают элементы последовательно, а вторые-- параллельно. - -Это замечательное свойство позволяет упростить ряд алгоритмов. Обычно это такие алгоритмы, в которых некоторый набор данных обрабатывается итеративно, причем для обработки различных элементов требуется различное количество итераций. - -В следующем примере вычисляются квадратные корни некоторого набор чисел. Каждая итерация обновляет значение квадратного корня. Числа, квадратные корни которых достигли необходимой точности, исключаются из перебираемого набора. - - case class Entry(num: Double) { - var sqrt = num - } - - val length = 50000 - - // готовим исходные данные - val entries = (1 until length) map { num => Entry(num.toDouble) } - val results = ParTrieMap() - for (e <- entries) results += ((e.num, e)) - - // вычисляем квадратные корни - while (results.nonEmpty) { - for ((num, e) <- results) { - val nsqrt = 0.5 * (e.sqrt + e.num / e.sqrt) - if (math.abs(nsqrt - e.sqrt) < 0.01) { - results.remove(num) - } else e.sqrt = nsqrt - } - } - -Отметим, что в приведенном выше вычислении квадратных корней вавилонским методом (\[[3][3]\]) некоторые значения могут сойтись гораздо быстрее, чем остальные. По этой причине мы исключаем их из `results`, чтобы перебирались только те элементы, которые нуждаются в дальнейшей обработке. - -Другим примером является алгоритм поиска в ширину, который итеративно расширяет очередь перебираемых узлов до тех пор, пока или не будет найден целевой узел, или не закончатся узлы, за счет которых можно расширить поиск. Определим точку на двухмерной карте как кортеж значений `Int`. Обозначим как `map` двухмерный массив булевых значений, которые обозначают, занята соответствующая ячейка или нет. Затем объявим два многопоточных дерева-- `open`, которое содержит все точки, которые требуется раскрыть, и `closed`, в котором хранятся уже обработанные точки. Мы намерены начать поиск с углов карты и найти путь к центру-- инициализируем ассоциативный массив `open` подходящими точками. Затем будем раскрывать параллельно все точки, содержащиеся в ассоциативном массиве `open` до тех пор, пока больше не останется точек. Каждый раз, когда точка раскрывается, она удаляется из массива `open` и помещается в массив `closed`. - -Выполнив все это, выведем путь от целевого до стартового узла. - - val length = 1000 - - // объявляем тип Node - type Node = (Int, Int); - type Parent = (Int, Int); - - // операции над типом Node - def up(n: Node) = (n._1, n._2 - 1); - def down(n: Node) = (n._1, n._2 + 1); - def left(n: Node) = (n._1 - 1, n._2); - def right(n: Node) = (n._1 + 1, n._2); - - // создаем карту и целевую точку - val target = (length / 2, length / 2); - val map = Array.tabulate(length, length)((x, y) => (x % 3) != 0 || (y % 3) != 0 || (x, y) == target) - def onMap(n: Node) = n._1 >= 0 && n._1 < length && n._2 >= 0 && n._2 < length - - // список open - фронт обработки - // список closed - уже обработанные точки - val open = ParTrieMap[Node, Parent]() - val closed = ParTrieMap[Node, Parent]() - - // добавляем несколько стартовых позиций - open((0, 0)) = null - open((length - 1, length - 1)) = null - open((0, length - 1)) = null - open((length - 1, 0)) = null - - // "жадный" поиск в ширину - while (open.nonEmpty && !open.contains(target)) { - for ((node, parent) <- open) { - def expand(next: Node) { - if (onMap(next) && map(next._1)(next._2) && !closed.contains(next) && !open.contains(next)) { - open(next) = node - } - } - expand(up(node)) - expand(down(node)) - expand(left(node)) - expand(right(node)) - closed(node) = parent - open.remove(node) - } - } - - // выводим путь - var pathnode = open(target) - while (closed.contains(pathnode)) { - print(pathnode + "->") - pathnode = closed(pathnode) - } - println() - -На GitHub есть пример реализации игры "Жизнь", который использует многопоточные хэш-деревья-- `Ctries`, чтобы выборочно симулировать только те части механизма игры, которые в настоящий момент активны \[[4][4]\]. -Он также включает в себя основанную на `Swing` визуализацию, которая позволяет посмотреть, как подстройка параметров влияет на производительность. - -Многопоточные префиксные деревья также поддерживают атомарную, неблокирующую(lock-free) операцию `snapshot`, выполнение которой осуществляется за постоянное время. Эта операция создает новое многопоточное дерево со всеми элементами на некоторый выбранный момент времени, создавая таким образом снимок состояния дерева в этот момент. -На самом деле, операция `snapshot` просто создает новый корень дерева. Последующие изменения отложенно перестраивают ту часть многопоточного дерева, которая соответствует изменению, и оставляет нетронутой ту часть, которая не изменилась. Прежде всего это означает, что операция 'snapshot' сама по себе не затратна, так как не происходит копирования элементов. Кроме того, так как оптимизация "копирования при записи" создает копии только измененных частей дерева, последующие модификации горизонтально масштабируемы. -Метод `readOnlySnapshot` чуть более эффективен, чем метод `snapshot`, но он возвращает неизменяемый ассоциативный массив, который доступен только для чтения. Многопоточные деревья также поддерживают атомарную операцию постоянного времени `clear`, основанную на рассмотренном механизме снимков. -Чтобы подробнее узнать о том, как работают многопоточные деревья и их снимки, смотрите \[[1][1]\] и \[[2][2]\]. - -На рассмотренном механизме снимков основана работа итераторов многопоточных деревьев. Прежде чем будет создан объект-итератор, берется снимок многопоточного дерева. Таким образом, итератор перебирает только те элементы дерева, которые присутствовали на момент создания снимка. Фактически, итераторы используют те снимки, которые дают доступ только на чтение. - -На том же механизме снимков основана операция `size`. В качестве примитивной реализации этой операции можно просто создать итератор (то есть, снимок) и перебрать все элементы, подсчитывая их. Таким образом, каждый вызов операции `size` будет требовать времени, прямо пропорционального числу элементов. Однако, многопоточные деревья в целях оптимизации кэшируют размеры своих отдельных частей, тем самым уменьшая временную сложность метода `size` до амортизированно-логарифмической. В результате получается, что если вызвать метод `size` один раз, можно осуществлять последующие вызовы `size` затрачивая минимум ресурсов, вычисляя, как правило, размеры только тех частей, которые изменились после последнего вызова `size`. Кроме того, вычисление размера параллельных многопоточных деревьев выполняется параллельно. - - -## Ссылки - -1. ["Cache-Aware" неблокирующие многопоточные хэш-деревья][1] -2. [Многопоточные деревья, поддерживающие эффективные неблокирующие снимки][2] -3. [Методы вычисления квадратных корней][3] -4. [Симуляция игры "Жизнь"][4] - - [1]: http://infoscience.epfl.ch/record/166908/files/ctries-techreport.pdf "Ctries-techreport" - [2]: http://lampwww.epfl.ch/~prokopec/ctries-snapshot.pdf "Ctries-snapshot" - [3]: http://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method "babylonian-method" - [4]: https://github.com/axel22/ScalaDays2012-TrieMap "game-of-life-ctries" diff --git a/ru/overviews/parallel-collections/custom-parallel-collections.md b/ru/overviews/parallel-collections/custom-parallel-collections.md deleted file mode 100644 index c83c485478..0000000000 --- a/ru/overviews/parallel-collections/custom-parallel-collections.md +++ /dev/null @@ -1,195 +0,0 @@ ---- -layout: overview-large -title: Создание пользовательской параллельной коллекции - -discourse: false - -partof: parallel-collections -language: ru -num: 6 ---- - -## Параллельная коллекция без компоновщиков - -Определить параллельную коллекцию без определения ее компоновщика возможно, так же, как возможно определить собственную последовательную коллекцию без определения ее строителей (`builders`). Вследствие отсутствия компоновщика получится, что методы трансформаций (т.е. `map`, `flatMap`, `collect`, `filter`, ...) по умолчанию будут возвращать ближайшую по иерархии стандартную коллекцию. Например, диапазоны строителей не имеют, и поэтому создание отображения элементов диапазона-- `map` -- создает вектор. - -В следующем примере определим параллельную коллекцию-строку. Так как строки по сути являются неизменяемыми последовательностями, сделаем их класс наследником `immutable.ParSeq[Char]`: - - class ParString(val str: String) - extends immutable.ParSeq[Char] { - -Затем определим методы, которые есть в любой неизменяемой последовательности: - - def apply(i: Int) = str.charAt(i) - - def length = str.length - -Кроме того, мы должны решить, что будем возвращать в качестве последовательного аналога нашей параллельной коллекции. Пусть это будет класс `WrappedString`: - - def seq = new collection.immutable.WrappedString(str) - -И наконец, требуется задать разделитель для наших параллельных строк. Назовем его `ParStringSplitter` и сделаем его потомком разделителя последовательностей, то есть типа `SeqSplitter[Char]`: - - def splitter = new ParStringSplitter(str, 0, str.length) - - class ParStringSplitter(private var s: String, private var i: Int, private val ntl: Int) - extends SeqSplitter[Char] { - - final def hasNext = i < ntl - - final def next = { - val r = s.charAt(i) - i += 1 - r - } - -В примере выше, `ntl` отображает общую длину строки, `i`-- текущую позицию, и наконец `s`-- саму строку. - -Итераторы (или разделители) параллельных коллекций требуют еще несколько методов помимо `next` и `hasNext`, характерных для итераторов последовательных коллекций. Для начала, у них есть метод `remaining`, возвращающий количество элементов, которые данному разделителю еще предстоит перебрать. Затем, метод `dup`, дублирующий текущий разделитель. - - def remaining = ntl - i - - def dup = new ParStringSplitter(s, i, ntl) - -И наконец, методы `split` и `psplit`, которые используются для создания разделителей, перебирающих подмножества элементов текущего разделителя. Для метода `split` действует соглашение, что он возвращает последовательность разделителей, перебирающих непересекающиеся подмножества элементов текущего разделителя, ни одно из которых не является пустым. Если текущий разделитель покрывает один или менее элементов, `split` возвращает саму последовательность этого разделителя. Метод `psplit` должен возвращать последовательность разделителей, перебирающих точно такое количество элементов, которое задано значениями размеров, указанных параметром `sizes`. Если параметр `sizes` требует отделить меньше элементов, чем покрыто текущим разделителем, то дополнительный разделитель со всеми остальными элементами размещается в конце последовательности. Если в параметре `sizes` указано больше элементов, чем содержится в текущем разделителе, для каждого размера, на который не хватило элементов, будет добавлен пустой разделитель. Наконец, вызов `split` или `psplit` делает текущий разделитель недействительным. - - def split = { - val rem = remaining - if (rem >= 2) psplit(rem / 2, rem - rem / 2) - else Seq(this) - } - - def psplit(sizes: Int*): Seq[ParStringSplitter] = { - val splitted = new ArrayBuffer[ParStringSplitter] - for (sz <- sizes) { - val next = (i + sz) min ntl - splitted += new ParStringSplitter(s, i, next) - i = next - } - if (remaining > 0) splitted += new ParStringSplitter(s, i, ntl) - splitted - } - } - } - -Выше приведена реализация метода `split` посредством вызова `psplit`, что часто наиболее оправдано в случае параллельных коллекций. Написать реализацию разделителя для параллельных ассоциативных массивов, множеств или итерируемых объектов чаще всего проще, так как они не требуют реализации метода `psplit`. - -Итак, мы получили класс параллельных строк. Единственным недостатком является то, что вызов методов трансформации, таких, как `filter`, произведет параллельный вектор вместо параллельной строки, что в ряде случаев может оказаться не самым оптимальным решением, так как воссоздание строки из вектора после фильтрации может оказаться затратным. - -## Параллельные коллекции с компоновщиками - -Допустим, мы хотим применить `filter` к символам параллельной строки, например, чтобы избавиться от запятых. Как отмечено выше, вызов `filter` вернет параллельный вектор, в то время как мы хотим получить строку (так как некоторые интерфейсы используемого API могут требовать последовательную строку). - -Чтобы избежать этого, для параллельной строки требуется написать компоновщик. На этот раз мы унаследуем трейт `ParSeqLike`, чтобы конкретизировать значение, возвращаемое методом `filter`-- а именно `ParString` вместо `ParSeq[Char]`. Третий параметр-тип трейта `ParSeqLike` указывает тип последовательного аналога параллельной коллекции (в этом отличие от последовательных трейтов вида `*Like`, имеющих только два параметра). - - class ParString(val str: String) - extends immutable.ParSeq[Char] - with ParSeqLike[Char, ParString, collection.immutable.WrappedString] - -Все методы остаются такими же, как в предыдущем примере, только дополнительно добавляется защищенный метод `newCombiner`, который используется при выполнении метода `filter`. - - protected[this] override def newCombiner: Combiner[Char, ParString] = new ParStringCombiner - -Следующим шагом определяем класс `ParStringCombiner`. Компоновщики являются подтипами строителей, в которых появляется дополнительный метод `combine`, принимающий другой компоновщик как аргумент и возвращающий новый компоновщик, который содержит элементы и текущего и принятого компоновщика. И текущий компоновщик, и компоновщик-аргумент становятся недействительными после вызова `combine`. Если передать аргументом сам текущий компоновщик, метод `combine` просто вернет его же как результат. Предполагается, что метод должен быть эффективным, то есть в худшем случае требовать для выполнения логарифмического времени по отношению к количеству элементов, так как в ходе параллельного вычисления он вызывается большое количество раз. - -Наш `ParStringCombiner` будет содержать последовательность строителей строк. Он будет реализовывать `+=` путем добавления элемента к последнему строителю строки в последовательности, и `combine` конкатенацией списков строителей строк текущего компоновщика и компоновщика-аргумента. Метод `result`, вызываемый в конце параллельного вычисления, произведет параллельную строку соединив все строители строк вместе. Таким образом, элементы копируются только один раз в конце, а не каждый раз, когда вызывается метод `combine`. В идеале, мы должны подумать о том, чтобы еще и копирование проводить параллельно (именно так и происходит в случае параллельных массивов), но без погружения в детали внутреннего представления строк это лучшее, чего мы можем добиться-- остается смириться с этим последовательным узким местом. - - private class ParStringCombiner extends Combiner[Char, ParString] { - var sz = 0 - val chunks = new ArrayBuffer[StringBuilder] += new StringBuilder - var lastc = chunks.last - - def size: Int = sz - - def +=(elem: Char): this.type = { - lastc += elem - sz += 1 - this - } - - def clear = { - chunks.clear - chunks += new StringBuilder - lastc = chunks.last - sz = 0 - } - - def result: ParString = { - val rsb = new StringBuilder - for (sb <- chunks) rsb.append(sb) - new ParString(rsb.toString) - } - - def combine[U <: Char, NewTo >: ParString](other: Combiner[U, NewTo]) = if (other eq this) this else { - val that = other.asInstanceOf[ParStringCombiner] - sz += that.sz - chunks ++= that.chunks - lastc = chunks.last - this - } - } - - -## Как реализовать собственный компоновщик? - -Тут нет стандартного рецепта, -- все зависит от имеющейся структуры данных, и обычно требует изобретательности со стороны того, кто пишет реализацию. Тем не менее, можно выделить несколько подходов, которые обычно применяются: - -1. Конкатенация и объединение. Некоторые структуры данных позволяют реализовать эти операции эффективно (обычно с логарифмической сложностью), и если требуемая коллекция представлена такой структурой данных, ее компоновщик может быть самой такой коллекцией. Особенно хорошо этот подход работает для подвешенных деревьев (finger trees), веревок (ropes) и различных видов куч. - -2. Двухфазное выполнение. Подход, применяемый в случае параллельных массивов и параллельных хэш-таблиц; он предполагает, что элементы могут быть эффективно рассортированы по готовым для конкатенации блокам, из которых результирующая структура данных может быть построена параллельно. В первую фазу блоки заполняются независимо различными процессорами, и в конце просто соединяются конкатенацией. Во вторую фазу происходит выделение памяти для целевой структуры данных, и после этого различные процессоры заполняют различные ее части, используя элементы непересекающихся блоков. -Следует принять меры для того, чтобы различные процессоры никогда не изменяли одну и ту же часть структуры данных, иначе не избежать трудноуловимых, связанных с многопоточностью ошибок. Такой подход легко применить к последовательностям с произвольным доступом, как было показано в предыдущем разделе. - -3. Многопоточная структура данных. Так как последние два подхода, в сущности, не требуют использования примитивных механизмов синхронизации, предполагается, что структура будет строиться несколькими потоками так, что два различных процессора никогда не будут изменять одну и ту же область памяти. Существует большое количество многопоточных структур данных, которые могут безопасно изменяться несколькими процессорами одновременно, среди таких можно упомянуть многопоточные списки с пропусками (skip lists), многопоточные хэш-таблицы, `split-ordered` списки и многопоточные АВЛ-деревья. -При этом требуется следить, чтобы у выбранной многопоточной структуры был горизонтально масштабируемый метод вставки. У многопоточных параллельных коллекций компоновщик может быть представлен самой коллекцией, и единственный его экземпляр обычно используется всеми процессорами, занятыми в выполнении параллельной операции. - -## Интеграция с фреймворком коллекций - -Наш класс `ParString` оказался не вполне завершен: несмотря на то, что мы реализовали собственный компоновщик, который будут использовать такие методы, как `filter`, `partition`, `takeWhile` или `span`, большинство методов трансформации требуют скрытый параметр-доказательство `CanBuildFrom` (подробное объяснение можно посмотреть в "Scala collections guide" (прим. перев. скорее {{ site.baseurl }}/overviews/core/architecture-of-scala-collections.html)). Чтобы обеспечить его доступность и тем самым полностью интегрировать наш класс `ParString` с фреймворком коллекций, требуется примешать дополнительный трейт `GenericParTemplate` и определить объект-компаньон для `ParString`. - - class ParString(val str: String) - extends immutable.ParSeq[Char] - with GenericParTemplate[Char, ParString] - with ParSeqLike[Char, ParString, collection.immutable.WrappedString] { - - def companion = ParString - -Внутрь объекта-компаньона помещаем скрытый параметр-доказательство `CanBuildFrom`. - - object ParString { - implicit def canBuildFrom: CanCombineFrom[ParString, Char, ParString] = - new CanCombinerFrom[ParString, Char, ParString] { - def apply(from: ParString) = newCombiner - def apply() = newCombiner - } - - def newBuilder: Combiner[Char, ParString] = newCombiner - - def newCombiner: Combiner[Char, ParString] = new ParStringCombiner - - def apply(elems: Char*): ParString = { - val cb = newCombiner - cb ++= elems - cb.result - } - } - -## Дальнейшие настройки-- многопоточные и другие коллекции - -Процесс реализации многопоточной коллекции (в отличие от параллельных, многопоточные коллекции могут подобно `collection.concurrent.TrieMap` изменяться одновременно несколькими потоками) не всегда прост и очевиден. При этом особенно нуждаются в тщательном обдумывании компоновщики. Компоновщики большинства _параллельных_ коллекций, которые были рассмотрены до этого момента, используют двухфазное выполнение. На первом этапе элементы добавляются различными процессорами к своим компоновщикам и последние объединяются вместе. На втором шаге, когда становятся доступными все элементы, строится результирующая коллекция. - -Другим подходом является построение результирующей коллекции как структуры элементов компоновщика. Для этого коллекция должна быть потокозащищенной-- компоновщик должен позволять выполнить _многопоточную_ вставку элемента. В этом случае один компоновщик может использоваться всеми процессорами. - -Если требуется сделать многопоточную коллекцию параллельной, в ее компоновщике нужно перегрузить метод `canBeShared` так, чтобы он возвращал `true`. Этим мы заставим проверять, что при выполнении параллельной операции создается только один компоновщик. Далее, метод `+=` должен быть потокозащищенным. И наконец, метод `combine` по-прежнему должен возвращать текущий компоновщик в случае, если он совпадает с аргументом, а в противном случае вполне может выбросить исключение. - -Чтобы добиться лучшей балансировки нагрузки, разделители делятся на более мелкие разделители. По умолчанию решение о том, что дальнейшее разделение не требуется, принимается на основе информации, возвращенной методом `remaining`. Для некоторых коллекций вызов метода `remaining` может быть затратным, и решение о разделении лучше принять другими способами. В этом случае нужно перегрузить метод `shouldSplitFurther` разделителя. - -В реализации по умолчанию разделитель делится, если число оставшихся элементов больше, чем размер коллекции деленный на взятый восемь раз уровень параллелизма. - - def shouldSplitFurther[S](coll: ParIterable[S], parallelismLevel: Int) = - remaining > thresholdFromSize(coll.size, parallelismLevel) - -Как вариант, разделитель может иметь счетчик количества проведенных над ним разделений и реализовывать метод `shouldSplitFurther`, возвращая `true`, если количество разделений больше, чем `3 + log(parallelismLevel)`. Это и позволяет избежать вызова метода `remaining`. - -Более того, если для некоторой коллекции вызов `remaining` затратен (то есть требует обработки большого числа элементов), то метод `isRemainingCheap` в разделителях следует перегрузить, так, чтобы он возвращал `false`. - -Наконец, если реализовать метод `remaining` в разделителях весьма затруднительно, можно возвращать `false` в перегруженном методе `isStrictSplitterCollection` соответствующей коллекции. Над такими коллекциями не получится выполнить ряд методов, в частности таких, которые требуют точности разделителей (последнее предполагает как раз, что метод `remaining` возвращает правильное значение). Но, что важно, это не относится к методам, используемым для обработки for-включений (for-comprehensions). diff --git a/ru/overviews/parallel-collections/overview.md b/ru/overviews/parallel-collections/overview.md deleted file mode 100644 index 8ba6449fe5..0000000000 --- a/ru/overviews/parallel-collections/overview.md +++ /dev/null @@ -1,180 +0,0 @@ ---- -layout: overview-large -title: Обзор - -discourse: false - -partof: parallel-collections -num: 1 -language: ru ---- - -**Авторы оригинала: Aleksandar Prokopec, Heather Miller** - -**Перевод Анастасии Маркиной** - -## Мотивация - -Пока производители процессоров в последние годы дружно переходили от одноядерных к многоядерным архитектурам, научное и производственное сообщества не менее дружно признали, что многопоточное программирование по-прежнему трудно сделать популярным. - -Чтобы упростить написание многопоточных программ, в стандартную библиотеку Scala были включены параллельные коллекции, которые скрыли от пользователей низкоуровневые подробности параллелизации, дав им привычную высокоуровневую абстракцию. Надежда была (и остается) на то, что скрытая под уровнем абстракции параллельность позволит на шаг приблизиться к ситуации, когда среднестатистический разработчик будет повседневно использовать в работе надежно исполняемый параллельный код. - -Идея проста: коллекции -- хорошо понятная и часто используемая программистами абстракция. Упорядоченность коллекций позволяет эффективно и прозрачно (для пользователя) обрабатывать их параллельно. Позволив пользователю "подменить" последовательные коллекции на те, что обрабатываются параллельно, решение Scala делает большой шаг вперед к охвату большего количества кода возможностями параллельной обработки. - -Рассмотрим следующий пример, где мы исполняем монадическую операцию на некоторой большой последовательной коллекции: - - val list = (1 to 10000).toList - list.map(_ + 42) - -Чтобы выполнить ту же самую операцию параллельно, требуется просто вызвать метод `par` -на последовательной коллекции `list`. После этого можно работать с параллельной коллекцией так же, как и с последовательной. То есть, пример выше примет вид: - - list.par.map(_ + 42) - -Библиотека параллельных коллекций Scala тесно связана с "последовательной" библиотекой коллекций Scala (представлена в версии 2.8), во многом потому, что последняя служила вдохновением к ее дизайну. Он предоставляет параллельную альтернативу ряду важных структур данных из библиотеки (последовательных) коллекций Scala, в том числе: - -* `ParArray` -* `ParVector` -* `mutable.ParHashMap` -* `mutable.ParHashSet` -* `immutable.ParHashMap` -* `immutable.ParHashSet` -* `ParRange` -* `ParTrieMap` (`collection.concurrent.TrieMap` впервые в версии 2.10) - -Библиотека параллельных коллекций Scala _расширяема_ также как и последовательные коллекции, представленные в стандартной библиотеке. Другими словами, как и в случае с обычными последовательными коллекциями, пользователи могут внедрять свои собственные типы коллекций, автоматически наследуя все предопределенные (параллельные) операции, доступные для других параллельных коллекций в стандартной библиотеке. - -## Несколько примеров - -Попробуем изобразить всеобщность и полезность представленных коллекций на ряде простых примеров, для каждого из которых характерно прозрачно-параллельное выполнение. - -_Примечание:_ Некоторые из последующих примеров оперируют небольшими коллекциями, для которых такой подход не рекомендуется. Они должны рассматриваться только как иллюстрация. Эвристически, ускорение становится заметным, когда размер коллекции дорастает до нескольких тысяч элементов. (Более подробно о взаимосвязи между размером коллекции и производительностью, смотрите [соответствующий подраздел]({{ site.baseurl}}/overviews/parallel-collections/performance.html#how_big_should_a_collection_be_to_go_parallel) раздела, посвященного [производительности]({{ site.baseurl }}/ru/overviews/parallel-collections/performance.html) в данном руководстве.) - -#### map - -Используем параллельную `map` для преобразования набора строк `String` в верхний регистр: - - scala> val lastNames = List("Smith","Jones","Frankenstein","Bach","Jackson","Rodin").par - lastNames: scala.collection.parallel.immutable.ParSeq[String] = ParVector(Smith, Jones, Frankenstein, Bach, Jackson, Rodin) - - scala> lastNames.map(_.toUpperCase) - res0: scala.collection.parallel.immutable.ParSeq[String] = ParVector(SMITH, JONES, FRANKENSTEIN, BACH, JACKSON, RODIN) - -#### fold - -Суммируем через `fold` на `ParArray`: - - scala> val parArray = (1 to 10000).toArray.par - parArray: scala.collection.parallel.mutable.ParArray[Int] = ParArray(1, 2, 3, ... - - scala> parArray.fold(0)(_ + _) - res0: Int = 50005000 - -#### filter - -Используем параллельный `filter` для отбора фамилий, которые начинаются с буквы "J" или стоящей дальше в алфавите: - - scala> val lastNames = List("Smith","Jones","Frankenstein","Bach","Jackson","Rodin").par - lastNames: scala.collection.parallel.immutable.ParSeq[String] = ParVector(Smith, Jones, Frankenstein, Bach, Jackson, Rodin) - - scala> lastNames.filter(_.head >= 'J') - res0: scala.collection.parallel.immutable.ParSeq[String] = ParVector(Smith, Jones, Jackson, Rodin) - -## Создание параллельной коллекции - -Параллельные коллекции предназначались для того, чтобы быть использованными таким же образом, как и последовательные; единственное значимое отличие -- в методе _получения_ параллельной коллекции. - -В общем виде, есть два варианта создания параллельной коллекции: - -Первый, с использованием ключевого слова `new` и подходящего оператора import: - - import scala.collection.parallel.immutable.ParVector - val pv = new ParVector[Int] - -Второй, _преобразованием_ последовательной коллекции: - - val pv = Vector(1,2,3,4,5,6,7,8,9).par - -Разовьем эту важную мысль -- последовательные коллекции можно конвертировать в параллельные вызовом метода `par` на последовательных, и, соответственно, параллельные коллекции также можно конвертировать в последовательные вызовом метода `seq` на параллельных. - -_На заметку:_ Коллекции, являющиеся последовательными в силу наследования (в том смысле, что доступ к их элементам требуется получать по порядку, один элемент за другим), такие, как списки, очереди и потоки (streams), преобразовываются в свои параллельные аналоги копированием элементов в соответствующие параллельные коллекции. Например, список `List` конвертируется в стандартную неизменяемую параллельную последовательность, то есть в `ParVector`. Естественно, что копирование, которое для этого требуется, вносит дополнительный расход производительности, которого не требуют другие типы коллекций, такие как `Array`, `Vector`, `HashMap` и т.д. - -Больше информации о конвертировании можно найти в разделах [преобразования]({{ site.baseurl }}/ru/overviews/parallel-collections/conversions.html) и [конкретные классы параллельных коллекций]({{ site.baseurl }}/ru/overviews/parallel-collections/concrete-parallel-collections.html) этого руководства. - -## Семантика - -В то время, как абстракция параллельной коллекции заставляет думать о ней так, как если бы речь шла о нормальной последовательной коллекции, важно помнить, что семантика различается, особенно в том, что касается побочных эффектов и неассоциативных операций. - -Для того, чтобы увидеть, что происходит, для начала представим, _как именно_ операции выполняются параллельно. В концепции, когда фреймворк параллельных коллекций Scala распараллеливает операцию на соответствующей коллекции, он рекурсивно "разбивает" данную коллекцию, параллельно выполняет операцию на каждом разделе коллекции, а затем "комбинирует" все полученные результаты. - -Эти многопоточные, "неупорядоченные" семантики параллельных коллекций приводят к следующим скрытым следствиям: - -1. **Операции, имеющие побочные эффекты, могут нарушать детерминизм** -2. **Неассоциативные операции могут нарушать детерминизм** - -### Операции, имеющие побочные эффекты. - -Вследствие использования фреймворком параллельных коллекций семантики _многопоточного_ выполнения, в большинстве случаев для соблюдения детерминизма требуется избегать выполнения на коллекциях операций, которые выполняют побочные действия. В качестве простого примера попробуем использовать метод доступа `foreach` для увеличения значения переменной `var`, объявленной вне замыкания, которое было передано `foreach`. - - scala> var sum = 0 - sum: Int = 0 - - scala> val list = (1 to 1000).toList.par - list: scala.collection.parallel.immutable.ParSeq[Int] = ParVector(1, 2, 3,… - - scala> list.foreach(sum += _); sum - res01: Int = 467766 - - scala> var sum = 0 - sum: Int = 0 - - scala> list.foreach(sum += _); sum - res02: Int = 457073 - - scala> var sum = 0 - sum: Int = 0 - - scala> list.foreach(sum += _); sum - res03: Int = 468520 - -В примере видно, что несмотря на то, что каждый раз `sum` инициализируется в 0, при каждом новом вызове `foreach` на предложенном `list`, `sum` получает различное значение. Источником недетерминизма является так называемая _гонка_-- параллельное чтение/запись одной и той же изменяемой переменной. - -В примере выше возможен случай, когда два потока прочитают _одно и то же_ значение переменной `sum`, потратят некоторое время на выполнение операции над этим значением `sum`, а потом попытаются записать новое значение в `sum`, что может привести к перезаписи (а следовательно, к потере) значимого результата, как показано ниже: - - Поток A: читает значение sum, sum = 0 значение sum: 0 - Поток B: читает значение sum, sum = 0 значение sum: 0 - Поток A: увеличивает sum на 760, пишет sum = 760 значение sum: 760 - Поток B: увеличивает sum на 12, пишет sum = 12 значение sum: 12 - -Приведенный выше пример демонстрирует сценарий, где два потока успевают прочитать одно и то же значение, `0`, прежде чем один или другой из них успеет прибавить к этому `0` элемент из своего куска параллельной коллекции. В этом случае `Поток A` читает `0` и прибавляет к нему свой элемент, `0+760`, в то время, как `Поток B` прибавляет `0` к своему элементу, `0+12`. После того, как они вычислили свои суммы, каждый из них записывает свое значение в `sum`. Получилось так, что `Поток A` успевает записать значение первым, только для того, чтобы это помещенное в `sum` значение было практически сразу же перезаписано потоком `B`, тем самым полностью перезаписав (и потеряв) значение `760`. - -### Неассоциативные операции - -Из-за _"неупорядоченной"_ семантики, нелишней осторожностью становится требование выполнять только ассоциативные операции во избежание недетерминированности. То есть, если мы имеем параллельную коллекцию `pcoll`, нужно убедиться, что при вызове на `pcoll` функции более высокого уровня, такой как `pcoll.reduce(func)`, порядок, в котором `func` применяется к элементам `pcoll`, может быть произвольным. Простым и очевидным примером неассоциативной операции является вычитание: - - scala> val list = (1 to 1000).toList.par - list: scala.collection.parallel.immutable.ParSeq[Int] = ParVector(1, 2, 3,… - - scala> list.reduce(_-_) - res01: Int = -228888 - - scala> list.reduce(_-_) - res02: Int = -61000 - - scala> list.reduce(_-_) - res03: Int = -331818 - -В примере выше, мы берем `ParVector[Int]`, вызываем функцию `reduce`, и передаем ей `_-_`, которая просто берет два неименованных элемента, и вычитает один из другого. Вследствие того, что фреймворк параллельных коллекций порождает потоки и независимо выполняет `reduce(_-_)` на разных частях коллекции, результат двух запусков `reduce(_-_)` на одной и той же коллекции не будет одним и тем же. - -_Примечание:_ Часто возникает мысль, что так же, как и в случае с неассоциативными, некоммутативные операции, переданные в более высокую уровнем функцию на параллельной коллекции, приводят к недетеминированному поведению. Это неверно, простой пример -- конкатенация строк -- ассоциативная, но некоммутативная операция: - - scala> val strings = List("abc","def","ghi","jk","lmnop","qrs","tuv","wx","yz").par - strings: scala.collection.parallel.immutable.ParSeq[java.lang.String] = ParVector(abc, def, ghi, jk, lmnop, qrs, tuv, wx, yz) - - scala> val alphabet = strings.reduce(_++_) - alphabet: java.lang.String = abcdefghijklmnopqrstuvwxyz - -_"Неупорядоченная"_ семантика параллельных коллекций означает только то, что операции будут выполнены не по порядку (во _временном_ отношении. То есть, не последовательно), она не означает, что результат будет "*перемешан*" относительно изначального порядка (в _пространственном_ отношении). Напротив, результат будет практически всегда пересобран _по-порядку_-- то есть, параллельная коллекция, разбитая на части в порядке A, B, C, будет снова объединена в том же порядке A, B, C, а не в каком-то произвольном, например, B, C, A. - -Если требуется больше информации о том, как разделяются и комбинируются операции на различных типах коллекций, посетите раздел [Архитектура]({{ site.baseurl }}/ru/overviews/parallel-collections/architecture.html) этого руководства. - diff --git a/ru/overviews/parallel-collections/performance.md b/ru/overviews/parallel-collections/performance.md deleted file mode 100644 index eba98f9257..0000000000 --- a/ru/overviews/parallel-collections/performance.md +++ /dev/null @@ -1,190 +0,0 @@ ---- -layout: overview-large -title: Измерение производительности - -discourse: false - -partof: parallel-collections -num: 8 -outof: 8 -language: ru ---- - -## Производительность JVM - -При описании модели производительности выполнения кода на JVM иногда ограничиваются несколькими комментариями, и как результат-- не всегда становится хорошо понятно, что в силу различных причин написанный код может быть не таким производительным или расширяемым, как можно было бы ожидать. В этой главе будут приведены несколько примеров. - -Одной из причин является то, что процесс компиляции выполняющегося на JVM приложения не такой, как у языка со статической компиляцией (как можно увидеть здесь \[[2][2]\]). Компиляторы Java и Scala обходятся минимальной оптимизацией при преобразовании исходных текстов в байткод JVM. При первом запуске на большинстве современных виртуальных Java-машин байткод преобразуется в машинный код той архитектуры, на которой он запущен. Это преобразование называется компиляцией "на лету" или JIT-компиляцией (JIT от just-in-time). Однако из-за того, что компиляция "на лету" должна быть быстрой, уровень оптимизации при такой компиляции остается низким. Более того, чтобы избежать повторной компиляции, компилятор HotSpot оптимизирует только те участки кода, которые выполняются часто. Поэтому тот, кто пишет тест производительности, должен учитывать, что программа может показывать разную производительность каждый раз, когда ее запускают: многократное выполнение одного и того же куска кода (то есть, метода) на одном экземпляре JVM может демонстрировать очень разные результаты замеров производительности в зависимости от того, оптимизировался ли определенный код между запусками. Более того, измеренное время выполнения некоторого участка кода может включать в себя время, за которое произошла сама оптимизация JIT-компилятором, что сделает результат измерения нерепрезентативным. - -Кроме этого, результат может включать в себя потраченное на стороне JVM время на осуществление операций автоматического управления памятью. Время от времени выполнение программы прерывается и вызывается сборщик мусора. Если исследуемая программа размещает хоть какие-нибудь данные в куче (а большинство программ JVM размещают), значит сборщик мусора должен запуститься, возможно, исказив при этом результаты измерений. Можно нивелировать влияние сборщика мусора на результат, запустив измеряемую программу множество раз, и тем самым спровоцировав большое количество циклов сборки мусора. - -Одной из распространенных причин ухудшения производительности является упаковка и распаковка примитивов, которые неявно происходят в случаях, когда примитивный тип передается аргументом в обобщенный (generic) метод. Чтобы примитивные типы можно было передать в метод с параметром обобщенного типа, они во время выполнения преобразуются в представляющие их объекты. Этот процесс замедляет выполнение, а кроме того порождает необходимость в дополнительном выделении памяти и, соответственно, создает дополнительный мусор в куче. - -В качестве распространенной причины ухудшения параллельной производительности можно назвать соперничество за память (memory contention), возникающее из-за того, что программист не может явно указать, где следует размещать объекты. Фактически, из-за влияния сборщика мусора, это соперничество может произойти на более поздней стадии жизни приложения, а именно после того, как объекты начнут перемещаться в памяти. Такие влияния нужно учитывать при написании теста. - -## Пример микротеста производительности - -Существует несколько подходов, позволяющих избежать описанных выше эффектов во время измерений. В первую очередь следует убедиться, что JIT-компилятор преобразовал исходный текст в машинный код (и что последний был оптимизирован), прогнав микротест производительности достаточное количество раз. Этот процесс известен как фаза разогрева (warm-up). - -Для того, чтобы уменьшить число помех, вызванных сборкой мусора от объектов, размещенных другими участками программы или несвязанной компиляцией "на лету", требуется запустить микротест на отдельном экземпляре JVM. - -Кроме того, запуск следует производить на серверной версии HotSpot JVM, которая выполняет более агрессивную оптимизацию. - -Наконец, чтобы уменьшить вероятность того, что сборка мусора произойдет посреди микротеста, лучше всего добиться выполнения цикла сборки мусора перед началом теста, а следующий цикл отложить настолько, насколько это возможно. - -В стандартной библиотеке Scala предопределен трейт `scala.testing.Benchmark`, спроектированный с учетом приведенных выше соображений. Ниже приведен пример тестирования производительности операции `map` многопоточного префиксного дерева: - - import collection.parallel.mutable.ParTrieMap - import collection.parallel.ForkJoinTaskSupport - - object Map extends testing.Benchmark { - val length = sys.props("length").toInt - val par = sys.props("par").toInt - val partrie = ParTrieMap((0 until length) zip (0 until length): _*) - - partrie.tasksupport = new ForkJoinTaskSupport(new scala.concurrent.forkjoin.ForkJoinPool(par)) - - def run = { - partrie map { - kv => kv - } - } - } - -Метод `run` содержит код микротеста, который будет повторно запускаться для измерения времени своего выполнения. Объект `Map`, расширяющий трейт `scala.testing.Benchmark`, запрашивает передаваемые системой параметры уровня параллелизма `par` и количества элементов дерева `length`. - -После компиляции программу, приведенную выше, следует запустить так: - - java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=1 -Dlength=300000 Map 10 - -Флаг `server` требует использовать серверную VM. Флаг `cp` означает "classpath", то есть указывает, что файлы классов требуется искать в текущем каталоге и в jar-архиве библиотеки Scala. Аргументы `-Dpar` и `-Dlength`-- это количество потоков и количество элементов соответственно. Наконец, `10` означает что тест производительности будет запущен на одной и той же JVM именно 10 раз. - -Устанавливая количество потоков `par` в `1`, `2`, `4` и `8`, получаем следующее время выполнения на четырехъядерном i7 с поддержкой гиперпоточности: - - Map$ 126 57 56 57 54 54 54 53 53 53 - Map$ 90 99 28 28 26 26 26 26 26 26 - Map$ 201 17 17 16 15 15 16 14 18 15 - Map$ 182 12 13 17 16 14 14 12 12 12 - -Можно заметить, что на первые запуски требуется больше времени, но после оптимизации кода оно уменьшается. Кроме того, мы можем увидеть что гиперпотоковость не дает большого преимущества в нашем примере, это следует из того, что увеличение количества потоков от `4` до `8` не приводит к значительному увеличению производительности. - -## Насколько большую коллекцию стоит сделать параллельной? - -Этот вопрос задается часто, но ответ на него достаточно запутан. - -Размер коллекции, при котором оправданы затраты на параллелизацию, в действительности зависит от многих факторов. Некоторые из них (но не все) приведены ниже: - -- Архитектура системы. Различные типы CPU имеют различную архитектуру и различные характеристики масштабируемости. Помимо этого, машина может быть многоядерной, а может иметь несколько процессоров, взаимодействующих через материнскую плату. -- Производитель и версия JVM. Различные виртуальные машины применяют различные оптимизации кода во время выполнения и реализуют различные механизмы синхронизации и управления памятью. Некоторые из них не поддерживают `ForkJoinPool`, возвращая нас к использованию `ThreadPoolExecutor`, что приводит к увеличению накладных расходов. -- Поэлементная нагрузка. Величина нагрузки, оказываемой обработкой одного элемента, зависит от функции или предиката, которые требуется выполнить параллельно. Чем меньше эта нагрузка, тем выше должно быть количество элементов для получения ускорения производительности при параллельном выполнении. -- Выбранная коллекция. Например, разделители `ParArray` и `ParTrieMap` перебирают элементы коллекции с различными скоростями, а значит разницу количества нагрузки при обработке каждого элемента создает уже сам перебор. -- Выбранная операция. Например, у `ParVector` намного медленнее методы трансформации (такие, как `filter`) чем методы получения доступа (как `foreach`) -- Побочные эффекты. При изменении областей памяти несколькими потоками или при использовании механизмов синхронизации внутри тела `foreach`, `map`, и тому подобных, может возникнуть соперничество. -- Управление памятью. Размещение большого количества объектов может спровоцировать цикл сборки мусора. В зависимости от способа передачи ссылок на новые объекты, цикл сборки мусора может занимать больше или меньше времени. - -Даже рассматривая вышеперечисленные факторы по отдельности, не так-то просто рассуждать о влиянии каждого, а тем более дать точный ответ, каким же должен быть размер коллекции. Чтобы в первом приближении проиллюстрировать, каким же он должен быть, приведем пример выполнения быстрой и не вызывающей побочных эффектов операции сокращения параллельного вектора (в нашем случае-- суммированием) на четырехъядерном процессоре i7 (без использования гиперпоточности) на JDK7: - - import collection.parallel.immutable.ParVector - - object Reduce extends testing.Benchmark { - val length = sys.props("length").toInt - val par = sys.props("par").toInt - val parvector = ParVector((0 until length): _*) - - parvector.tasksupport = new collection.parallel.ForkJoinTaskSupport(new scala.concurrent.forkjoin.ForkJoinPool(par)) - - def run = { - parvector reduce { - (a, b) => a + b - } - } - } - - object ReduceSeq extends testing.Benchmark { - val length = sys.props("length").toInt - val vector = collection.immutable.Vector((0 until length): _*) - - def run = { - vector reduce { - (a, b) => a + b - } - } - } - -Сначала запустим тест производительности с `250000` элементами и получим следующие результаты для `1`, `2` и `4` потоков: - - java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=1 -Dlength=250000 Reduce 10 10 - Reduce$ 54 24 18 18 18 19 19 18 19 19 - java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=2 -Dlength=250000 Reduce 10 10 - Reduce$ 60 19 17 13 13 13 13 14 12 13 - java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=4 -Dlength=250000 Reduce 10 10 - Reduce$ 62 17 15 14 13 11 11 11 11 9 - -Затем уменьшим количество элементов до `120000` и будем использовать `4` потока для сравнения со временем сокращения последовательного вектора: - - java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=4 -Dlength=120000 Reduce 10 10 - Reduce$ 54 10 8 8 8 7 8 7 6 5 - java -server -cp .:../../build/pack/lib/scala-library.jar -Dlength=120000 ReduceSeq 10 10 - ReduceSeq$ 31 7 8 8 7 7 7 8 7 8 - -Похоже, что `120000` близко к пограничному значению в этом случае. - -В качестве еще одного примера возьмем метод `map` (метод трансформации) коллекции `mutable.ParHashMap` и запустим следующий тест производительности в той же среде: - - import collection.parallel.mutable.ParHashMap - - object Map extends testing.Benchmark { - val length = sys.props("length").toInt - val par = sys.props("par").toInt - val phm = ParHashMap((0 until length) zip (0 until length): _*) - - phm.tasksupport = new collection.parallel.ForkJoinTaskSupport(new scala.concurrent.forkjoin.ForkJoinPool(par)) - - def run = { - phm map { - kv => kv - } - } - } - - object MapSeq extends testing.Benchmark { - val length = sys.props("length").toInt - val hm = collection.mutable.HashMap((0 until length) zip (0 until length): _*) - - def run = { - hm map { - kv => kv - } - } - } - -Для `120000` элементов получаем следующие значения времени на количестве потоков от `1` до `4`: - - java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=1 -Dlength=120000 Map 10 10 - Map$ 187 108 97 96 96 95 95 95 96 95 - java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=2 -Dlength=120000 Map 10 10 - Map$ 138 68 57 56 57 56 56 55 54 55 - java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=4 -Dlength=120000 Map 10 10 - Map$ 124 54 42 40 38 41 40 40 39 39 - -Теперь уменьшим число элементов до `15000` и сравним с последовательным хэш-отображением: - - java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=1 -Dlength=15000 Map 10 10 - Map$ 41 13 10 10 10 9 9 9 10 9 - java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=2 -Dlength=15000 Map 10 10 - Map$ 48 15 9 8 7 7 6 7 8 6 - java -server -cp .:../../build/pack/lib/scala-library.jar -Dlength=15000 MapSeq 10 10 - MapSeq$ 39 9 9 9 8 9 9 9 9 9 - -Для выбранных в этом случае коллекции и операции есть смысл сделать вычисление параллельным при количестве элементов больше `15000` (в общем случае хэш-отображения и хэш-множества возможно делать параллельными на меньших количествах элементов, чем требовалось бы для массивов или векторов). - -## Ссылки - -1. [Anatomy of a flawed microbenchmark, Brian Goetz][1] -2. [Dynamic compilation and performance measurement, Brian Goetz][2] - - [1]: http://www.ibm.com/developerworks/java/library/j-jtp02225/index.html "flawed-benchmark" - [2]: http://www.ibm.com/developerworks/library/j-jtp12214/ "dynamic-compilation" - - - diff --git a/scripts/ci.sh b/scripts/ci.sh new file mode 100755 index 0000000000..13e23eb863 --- /dev/null +++ b/scripts/ci.sh @@ -0,0 +1,6 @@ +#!/bin/bash - +. /usr/local/rvm/scripts/rvm +bundle install +./scripts/run-tut.sh +rm -r tut-tmp +bundle exec jekyll build diff --git a/search.html b/search.html deleted file mode 100644 index c95285b3a6..0000000000 --- a/search.html +++ /dev/null @@ -1,35 +0,0 @@ ---- -layout: search -title: Search ---- - -
                  Loading
                  - - - \ No newline at end of file diff --git a/sips/.jekyll-metadata b/sips/.jekyll-metadata deleted file mode 100644 index 7851d3cff2..0000000000 Binary files a/sips/.jekyll-metadata and /dev/null differ diff --git a/sips/README.md b/sips/README.md deleted file mode 100644 index 8f96a6bd3b..0000000000 --- a/sips/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# Scala Improvement Process Documents - -This directory contains the source of the SIP website, including all pending/finished/rejected SIPs. - - -## Migrating a SIP to other states - -To reject a SIP simply: - - git mv pending/_posts/{sip-file} rejected/_posts/{sip-file} - -To mark a SIP completed simply: - - git mv pending/_posts/{sip-file} completed/_posts/{sip-file} - diff --git a/sips/completed/_posts/2009-05-28-scala-compiler-phase-plugin-in.md b/sips/completed/_posts/2009-05-28-scala-compiler-phase-plugin-in.md deleted file mode 100644 index b74dcdfa1c..0000000000 --- a/sips/completed/_posts/2009-05-28-scala-compiler-phase-plugin-in.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -layout: sip -title: SID-2 Scala Compiler Phase and Plug-In Initialization for Scala 2.8 ---- - -This was an older SID that can be found [here](http://www.scala-lang.org/sid/2) - - - diff --git a/sips/completed/_posts/2009-06-02-early-member-definitions.md b/sips/completed/_posts/2009-06-02-early-member-definitions.md deleted file mode 100644 index 59e0a418a4..0000000000 --- a/sips/completed/_posts/2009-06-02-early-member-definitions.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -layout: sip -title: SID-4 - Early Member Definitions ---- - -This was an older SID that can be found [here](http://www.scala-lang.org/sid/4) \ No newline at end of file diff --git a/sips/completed/_posts/2009-11-02-scala-swing-overview.md b/sips/completed/_posts/2009-11-02-scala-swing-overview.md deleted file mode 100644 index 1ed6f74004..0000000000 --- a/sips/completed/_posts/2009-11-02-scala-swing-overview.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -layout: sip -title: SID-8 - Scala Swing Overview ---- - -This was an older SID that can be found [here](http://www.scala-lang.org/sid/8) diff --git a/sips/completed/_posts/2010-01-27-internals-of-scala-annotations.md b/sips/completed/_posts/2010-01-27-internals-of-scala-annotations.md deleted file mode 100644 index 4a408efc17..0000000000 --- a/sips/completed/_posts/2010-01-27-internals-of-scala-annotations.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -layout: sip -title: SID-5 - Internals of Scala Annotations ---- - -This was an older SID that can be found [here](http://www.scala-lang.org/sid/5) \ No newline at end of file diff --git a/sips/completed/_posts/2010-05-06-scala-specialization.md b/sips/completed/_posts/2010-05-06-scala-specialization.md deleted file mode 100644 index eaa707fb2e..0000000000 --- a/sips/completed/_posts/2010-05-06-scala-specialization.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -layout: sip -title: SID-9 - Scala Specialization ---- - -This was an older SID that can be found [here](http://www.scala-lang.org/sid/9) \ No newline at end of file diff --git a/sips/completed/_posts/2010-06-01-picked-signatures.md b/sips/completed/_posts/2010-06-01-picked-signatures.md deleted file mode 100644 index c6b4cd2c91..0000000000 --- a/sips/completed/_posts/2010-06-01-picked-signatures.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -layout: sip -title: SID-10 - Storage of pickled Scala signatures in class files ---- - -This was an older SID that can be found [here](http://www.scala-lang.org/sid/10) \ No newline at end of file diff --git a/sips/completed/_posts/2010-07-20-new-collection-classes.md b/sips/completed/_posts/2010-07-20-new-collection-classes.md deleted file mode 100644 index f05cff8f39..0000000000 --- a/sips/completed/_posts/2010-07-20-new-collection-classes.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -layout: sip -title: SID-3 - New Collection classes ---- - -This was an older SID that can be found [here](http://www.scala-lang.org/sid/3) \ No newline at end of file diff --git a/sips/completed/_posts/2011-10-13-string-interpolation.md b/sips/completed/_posts/2011-10-13-string-interpolation.md deleted file mode 100644 index b46a26df8b..0000000000 --- a/sips/completed/_posts/2011-10-13-string-interpolation.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -layout: sip -title: SIP-11 - String Interpolation - -vote-status: accepted -vote-text: This SIP has already been accepted. We expect a SIP for 2.11 that will allow the desugared form of interpolated strings in the pattern matcher to become valid syntax. This SIP only allows the sugared interpolated strings to work. ---- - -**By: Martin Odersky** - -This SIP is an embedded google document. If you have trouble with this embedded document, you can [visit the -document on Google Docs](https://docs.google.com/document/d/1NdxNxZYodPA-c4MLr33KzwzKFkzm9iW9POexT9PkJsU/edit?hl=en_US). - - - diff --git a/sips/completed/_posts/2012-03-13-type-dynamic.md b/sips/completed/_posts/2012-03-13-type-dynamic.md deleted file mode 100644 index aa44f33c5f..0000000000 --- a/sips/completed/_posts/2012-03-13-type-dynamic.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -layout: sip -title: SIP-17 - Type Dynamic - -vote-status: accepted -vote-text: This SIP has already been accepted. ---- - - -This SIP is an embedded google document. If you have trouble with this embedded document, you can visit the [document on Google Docs](https://docs.google.com/document/d/1XaNgZ06AR7bXJA9-jHrAiBVUwqReqG4-av6beoLaf3U/edit). - - diff --git a/sips/index.md b/sips/index.md deleted file mode 100644 index 2130392767..0000000000 --- a/sips/index.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -layout: sip-landing -title: Scala Improvement Process ---- - - -# Welcome # - -This is the landing page for the Scala Improvement Process (SIP). The -Scala Library Improvement Process (SLIP) has its own distinct mechanism -documented at [Scala SLIP github project page](https://www.github.com/scala/slip). -In time, the SIP mechanism may be split out into a github oriented process -similar to the SLIP mechanism. - -The SIP process replaced the older SID (Scala Improvement Document) process, -however the old completed SID documents are still available to review in the -[completed section of the SIP list](sip-list.html). - -## SLIPs - -For changes and additions to the Scala core libraries (i.e. those distributed -with the main scala-lang download), a still rigorous (yet less demanding) process -called the Scala Library Improvement Process (SLIP) exists. - -SLIPs are intended for any library addition or modification that does not require -associated changes to the Scala language or compiler. - -[Submitting a SLIP](./slip-submission.html) covers the process. - -## SIPs - -A **SIP** (_Scala Improvement Process_) is a process for submitting changes to -the Scala language. Its main motivation is to become the primary mechanism to -propose, discuss and implement language changes. In this process, all changes to -the language go through design documents, called Scala Improvement Proposals -(SIPs), which are openly discussed by a committee and only upon reaching a -consensus are accepted to be merged into the Scala compiler. - -The aim of the Scala Improvement Process is to apply the openness and -collaboration that have shaped Scala's documentation and implementation to the -process of evolving the language. SIPs are for changes to the Scala -language and/or compiler and are subject to a -[rigorous review process](./sip-submission.html) and are usually accompanied by -changes to the [Scala language specification](http://www.scala-lang.org/files/archive/spec/2.11/), -lots of review and discussion on -the [scala-internals](https://groups.google.com/forum/#!forum/scala-internals) mailing list -and voting/approval milestones. Please read -[Submitting a SIP](./sip-submission.html) and our [SIP tutorial](./sip-tutorial.html) for -more information. diff --git a/sips/minutes-list.md b/sips/minutes-list.md deleted file mode 100644 index fb836b0953..0000000000 --- a/sips/minutes-list.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -layout: sip-landing -title: Minutes ---- - -This page hosts all the meeting notes (minutes) of the SIP meetings, starting -from July 2016. - -### Minutes ### -
                    - {% for post in site.categories.minutes %} - {% if post.layout == 'sip-landing' %} -
                  • {{ post.title }} ( {{ post.date | date: "%b %Y" }} )
                  • - {% endif %} - {% endfor %} -
                  diff --git a/sips/minutes/_posts/2017-02-14-sip-minutes.md b/sips/minutes/_posts/2017-02-14-sip-minutes.md deleted file mode 100644 index 0058ebda87..0000000000 --- a/sips/minutes/_posts/2017-02-14-sip-minutes.md +++ /dev/null @@ -1,260 +0,0 @@ ---- -layout: sip-landing -title: SIP Meeting Minutes - 14th February 2017 ---- - -# Minutes - -The following agenda was distributed to attendees: - -| Topic | Reviewer | -| --- | --- | -| [SIP-XX: Improving binary compatibility with @stableABI](http://docs.scala-lang.org/sips/pending/binary-compatibility.html) | Dmitry Petrashko | -| [SIP-NN - Allow referring to other arguments in default parameters](http://docs.scala-lang.org/sips/pending/refer-other-arguments-in-args.html) | Pathikrit Bhowmick | -| [SIP 25 - @static fields and methods in Scala objects(SI-4581)](http://docs.scala-lang.org/sips/pending/static-members.html) | Dmitry Petrashko, Sébastien Doeraene and Martin Odersky | -| [SIP-33 - Match infix & prefix types to meet expression rules](http://docs.scala-lang.org/sips/pending/make-types-behave-like-expressions.html) | Oron Port | - -Jorge Vicente Cantero was the Process Lead. Travis (@travissarles) was acting secretary of the meeting. - -## Date, Time and Location - -The meeting took place at 5:00pm Central European Time / 8:00am Pacific Daylight -Time on Tuesday, 14th February 2017 via Google Hangouts. - -Minutes were taken by Travis Lee, acting secretary. - -## Attendees - -Attendees Present: - -* Martin Odersky ([@odersky](https://github.com/odersky)), EPFL -* Dmitry Petrashko ([@DarkDimius](https://github.com/DarkDimius)), EPFL -* Lukas Rytz (Taking Adriaan Moore's place) ([@lrytz](https://github.com/lrytz)), Lightbend -* Seth Tisue ([@SethTisue](https://github.com/SethTisue)), Lightbend -* Iulian Dragos ([@dragos](https://github.com/dragos)), Independent -* Sébastien Doeraene ([@sjrd](https://github.com/sjrd)), EPFL -* Eugene Burmako ([@xeno-by](https://github.com/xeno-by)), EPFL -* Jorge Vicente Cantero ([@jvican](https://github.com/jvican)), Process Lead - -Absent: -* Heather Miller ([@heathermiller](https://github.com/heathermiller)), Scala Center -* Josh Suereth ([jsuereth](https://github.com/jsuereth)), Independent - -## Proceedings - -### Opening Remarks - -### SIP 25 - @static fields and methods in Scala objects(SI-4581) -Jorge asks Dmitry to explain the biggest changes since the last proposal. -The biggest changes were addressing the feedback of the reader. -**Dmitry** First, he covers whether the static annotation can behave like the tail recursive annotation, which doesn't actually impact compilation but only warns if it isn't possible to make something static. Dmitry doesn't think the static annotation should have the same semantics because it affects binary compatibility. - -Also, it depends on the superclass. If the superclass defined a field with the same name, the subclass can't have it. If we decide to make something static, we only be able to do it if the superclass doesn't have the fields with the same name. If static is done automatically and not triggered by the user we will enforce very strong requirements on superclass objects which is no-go. - -I proposed a scheme (which I elaborate more on the details frictions in the SIP). I present several examples of possible issues in this case. - -The other question was clarify how does the static effect initialization order and also describe if it effects binary compatibility. In general, emitting fields as static changes when they get initialized. They get initialized when the class is being class-loaded. The SIP in the current state requires static fields to be the first field defined in the object which means that these fields will be initialized before the other fields (indepenently of whether they are marked static or not). This leads to the fact that inside the object itself it will be possible to observe if something is static or not. Unless you rely on some class loader magic or reflection. At the same time, it is still possible to observe whether a field is static or not using multiple classes. Let's say you have a superclass of the object. If the superclass tries to see if the static field is initialized. In the case the field is static, it will be initialized before the superclass constructor executes. In case it's not static, it won't be initialized. - -In short, it can make initlization happen earlier but never later. By enforcing syntax restriction would make it harder to observe this. But still it is possible to observe initialization requirements. This is a sweet spot because static should be independent. The point is you want some fields to be initialized without the initialization of an entire object. There is a side-effect that an object is initialized and there are multiple ways to observe it somehow. - -The current SIP tries to make it behave as expected by the users in common cases. - -**Lukas** Let's take a simple example, you have an object with a static field and a non-static field. Now the user code, the program access the non=static field first. That means the module gets initialized, but the static field lives in the companion class, right? So the static field is not initialized necessary even though you access the module. Assuming the field initializers have side effects then you can observe the differences. Then the static field will only be initialized at some point later when you use the class and not when you access the module. - -**Dmitry** The idea is that the module should force execution of static initializers of the fields of the companion class. Which means that you won't be able to observe the difference in the order. It will look like all of them initialized at the same time. - -**Martin** How can that be done? - -**Dmitry** Just refer to the class in bytecode. Just mention the signature. - -**Martin** Does that mean we have to change the immediate code to enforce that? - -**Dmitry** Yeah you need to make one reference. It's very easy to write an expression in Scala which returns a null but ____ (7:45) - -**Martin** Sure but we can't do that right now. - -**Dmitry** I wouldn't do it. - -**Martin** Are there good reasons to do that anyway? - -**Dmitry** Maintaining semantics. If we don't do it, you can observe the fact that some fields are initialized, some aren't. - -**Martin** But we only have to do it for those companion objects that have both statics and fields. It doesn't leak into binary compatibility. - -**Jorge** You mentioned syntactic restrictions. Which kind? - -**Dmitry** Static fields should proceed any non-static fields. the fact that static fields can be initialized earlier but not later means that you won't be able to observe the fact ____ (8:40). If we were to allow non static fields to proceed, because static fields can be initialized earlier, you can see ____. - -**Sébastien** What about non-field statements? - -**Dmitry** They should also be after all the statics. - -**Sébastien** That's not written in the SIP. - -**Dmitry** Will update. The last question is whether it will effect binary compatibility. YES! The point is you should be able to call and access stuff which is static through a more efficient way on the JVM. So removing @static annotation will be a binary incompatible change. If we add forwarders, adding static won't be a binary incompatible change because you'll still have fowarders which forward to the static thing. Both methods and fields. - -**Lukas** But static fields are emitted as just fields and there's not getters and setters? - -**Dmitry** Yes, but all the static stuff is emitted in the class. The question would be whether we emit anything in the object. We can still emit getter and setter in the object which will allow us to maintian binary compatibility with stuff which was compiled before static annotation was added. By following this idea, adding static is binary compatible,removing isn't. - -**Sébastien** There's also an accessory to implement abstract definitions coming from a superclass or even overriding. - -**Dmitry** With current the current proposal static fields don't implement and don't override stuff. It's forbidden to define a static field or method if there is a thing with the same name defined in any of the superclasses. - -**Jorge** That would mean that you can't add static fields without hurting binary compatibility because if you cannot override, then you want to emit a static field which has a concrete name but that name is inherited from a super trait. - -**Dmitry** It won't compile then. It doesn't follow the requirements. If it compiles, it will be binary compatible. - -**Sébastien** What if the superclass of the object defines something with the same name? There's nothing that prevents me from implemnting or overriding something from the superclass of the object. I think that's fine. - -**Dmitry** The question would be about the initialization order - -**Sébastien** It's not a problem because if you call it via the super class it means you haven't initialized the object completely anyway already. - -**Dmitry** First of all it will disallow us to emit the final flag simply because final static fields can only be initialized from the static constructor. So if you have a final static field it can't implement setters. - -**Sébastien** But you can't have a getter and a setter that read and write the static field. - -**Dmitry** You can't write a _static final_ field again. Static fields are fine. If the static initialization is final, the only place where instructions which are writing it are allowed by hotspot are static initializer. So a possible restriction would be to say static final fields can't implement super class signatures. It would be very good to emit real static final fields because hotspot includes optimizations of those. Let's say if you were to discover some configuration statically and then use this to decide whether your implementation will take one branch or another, JIT will be able to ____ (14:32) eliminate this stuff. If the field isn't static, isn't final you don't have this guarantee. - -**Iulian** And do you need the setter even for vars? If you've marked it final only for vars would that work? - -**Dmitry** If it implements a super-trait vals, traits still have setters, even for vals. - -**Sébastien** Yes, but that's only if it's mixed in. If it's overridden in the object, you don't need the setter. And if it's mixed in from the interface it doesn't have the static annotation anyway because that's now how ____ (15:20). - -**Martin** Does it even have setters anymore? In dotty it definitely doesn't. I thought in 2.12 they changed the scheme now as well for trait vals. I don't think they require setters anymore. - -**Dmitry** We don't require them but I'm not sure about Scalac. Does Scalac use initializers or trait setters? - -**Sébastien** Trait setters. - -**Dmitry** For us, we can easily say that we support final but we're trying to take into account scalac. So is there some important use case where you want to have static fields implement not-static signatures. - -**Sébastien** No. - -**Dmitry** Would you be willing to lean on a conservative way in this case? - -**Sébastien** I was just trying to understand why there is a restriction. There doesn't seem to be any reason to but if there is a reason to, I don't see a use case. - -**Dmitry** I would propose to record this but not change the SIP. To summarize in short, static is an annotation which does affect the compilation scheme which enforces some requirements where it can be which try to hide the fact that semantic would have changed by making it hard to observe this. The assumption is that most common users won't be able to observe the difference in semantics because we've restricted the syntax so that it would be hard to observe it. At the same time, for advanced users, it would allow to emit static fields and methods which will help if someone wants to write highly optimized code or interact with Java. I think the SIP is good as is. Given the fact ____ proposed some suggestions and clarifications about actual changes. - -**Jorge** The question is whether this should be accepted or not. The problem is we don't have any implementation for Scalac. Does someone at Lightbend plan to work on this sometime soon? - -**Seth** I don't think that's something we've discussed as a team yet. - -**Jorge** We have to pass here both on this proposal as is right now but I think this could be dangerous in the case where we don't have an implementation for Scalac because maybe the details change and assume something in Scalac that the SIP is not able to predict or guard against it. Let's wait until next month and I will double check whether this is possible or not. Then I will get in touch with the Lightbend team to see whether this can be implemented or not. We'll decide in a month whether it should be accepted. - -**Sébastien** ScalaJS already implemented it under another name but it's supposed to be conservative with respect to the aesthetic SIP in the sense that things that are allowed now with @jsstatic will also be allowed with @static. @static might open up a little bit more. - -**Conclusion** The static SIP proposal has to be implemented in Scala, as it's already present in Dotty. Triplequote (Iulian Dragos and Mirco Dotta) has offered to provide an implementation targeting 2.12.3. - -### SIP-NN - [Allow referring to other arguments in default parameters](https://github.com/scala/scala.github.com/pull/653) (22:30) -Sébastien is the reviewer of this proposal. - -**Sébastien** The SIP is a generalization of why we can use in default values of parameters. Especially referring to other arguments. In current Scala we can refer to arguments in previous parameter lists. This SIP wants to open that up. The way it's currently written, any parameter whether it's in the same parameter list or a previous one, it's also allowed to refer to argument on the right. The text needs to be elaborated on use cases. Doesn't address implementation concerns. Jorge answered on the PR with analysis of feasibility. I'm convinced that the version where we can also refer to a parameter on the right is infeasible because you can have arbitrary cycles and you don't know _______ ( 24:55) and it's completely impossible. - -Other than that, in principle the SIP looks reasonable. It's possible to implement but it will cause more bytecode because now the third parameter will always need to receive the first two parameters to decide its value. We cannot decide that whether based on the default value actually refers to the previous parameters because that would be unstable in respect to binary compatibility. You need to always give to the default accessor all the prior parameters and that means it can potentially increase bytecode size. That needs to be analyzed maybe with a prototype, compare with Scala library. - -**Jorge** I implemented this. I did a study and analysis of whether referring to parameters on the right is visible and I've explained in a comment in the PR why it's not. Basically this is a change that would require breaking binary compatibility and this would be targeting 2.13 so we are not gonna see it any time soon. I think that it would be very useful to have a look at the numbers to see how it affects bytecode size. I'll run some benchmarks and [report the results in the PR](https://github.com/scala/scala/pull/5641). - -**Sébastien** SIP addendum, Type Members: In current Scala in the same way that you can only refer to parameters on terms previous parameter lists you can also only refer to path-dependent types of parameters in previous parameter lists and there is a small section in the SIP that says in the same vein we should allow to refer to path-dependent types of parameters in the same parameter list probably on the left. But for that one I don't have a good intuition of what effects it would have on type inference because type inference works parameter list per parameter list. The fact that you cannot refer to path-dependent types from the same parameter list means you can complete type inference from one parameter list without juggling path-dependent types within the same thing. Then when you move to the next one it's already inferred from the previous parameters. So it seems simpler but it's just a guest. Martin, would that be problematic? - -**Martin** It would be a completely sweeping change. It's one of the key types that suddenly becomes recursive. So you can imagine what that means. Every time we construct such a type we can't do it inductively anymore. So basically it's the difference between polytypes and method types. I'm not saying it's impossible but it would be a huge change the compiler to do that. It's probably beyond what we can do for Scalac and just for Dotty we could think about it but it would be a very big change. - -**Conclusion** The proposal has been numbered as SIP-32. The reference to type members seems tricky in implementation and interaction and it may be removed as the analysis of this SIP continues. The reference to other arguments in the same parameter list has been implemented by Jorge in [scala/scala#5641](https://github.com/scala/scala/pull/5641). - -### SIP-XX: Improving binary compatibility with @stableABI (33:30) -**Dmitry** This proposes annotations which does not change the compilation scheme. A bit of background, Scala is being released with versions which can be either minor or major. So 2.11 is major version compared to 2.10. 2.11.1 and 2.11.2 are minor versions. Scala currently guarantees binary-compatibility between minor versions. At the same time, big parts of the scala community live in different major versions of the compiler which require them to publish artifacts multiple times because the same artifact will be incompatible if used in a different compiler. - -**Martin** Or write it in Java - -**Dmitry** The current situations has several solutions. The first is write it in Java, the second is make it a source dependency, download the source, and compile it in runtime and the third one use your best judgement is to try to write Scala which you assume will be safe. At the same time there is a tool which is called MiMa which helps you to see whether you did it right. MiMa allows you to compare two already-compiled artifacts and say whether they're compatible or not. This SIP proposes something which will complement MiMa in indicating whether the thing will be compatible with the next version. So currently if you were to write a file in Scala compiled with 2.10 and then compile it to 2.11, MiMa can after-the-fact say that it's incompatible and previous version should have been more conservative with the features it used. It does it after the fact when 2.11 was already released. Your artifacts are already on Bintray and it's too late. stableABI augments this use case by allowing you to get a guarantee that this artifact will be reliably compiled by all the compilers which call themselves Scala, across all the major versions, and can be used by the code compiled by those compilers. The idea would be that stableABI classes can be either used for projects which need to survive multiple Scala major versions or for other languages which don't have such a strong binary API guarantee such as Java and Kotlin. And additionally it has a very strong use case of allowing to use features of future compilers and future language releases in libraries which try to support users who are still on the old versions. - -There are multiple use cases covered by this SIP. I think the two most important ones which are coming now are the migration from Dotty to Scala and the fact that we'll have two major releases existing at the same time. It would be nice if there was a common language for two compilers where people can reliably be in the safe situation publishing wise. If they publish an artifact compiled by Dotty, it can be safely used by Scalac, even if internally they use DOT advanced features. At the same time they won't be sure that they can use some features of Scalac that Dotty doesn't support and they will be able to use them inside the classes as long as they don't leak. So stableABI adds a check to the compiler which more or less ensures that there is no leakage of advanced features being used which could affect binaryABI. The guarantee which is assumed to be provided is if the same class is compiled with stableABI and it succeeds compilation it can be a replacement for the previous class if compiled by a different compiler. If the class has been changed by the user, they should use MiMa to find that the change was binary incompatible. - -**Eugene** The migration to Dotty is something that is highly anticipated in the community. Concrete proposals are hard to facilitate this change. It's gonna be a big change. Very welcome. How do you write stuff that's going to be used from Java reliably? - -**Martin** What I didn't see in the proposal was, so to move this forward you need to specify a minimum set of features that will be under stableABI. So if I write stableABI, you have to specify at least which sort of features will be accepted by the compiler. - -**Dmitry** Instead of specify which features will be accepted, I said that the ground truth will be the source code. So the rule that is currently written is if compiler changes the signature from the one written by the user, it shouldn't be stableABI. The current specification more or less says if something isn't de-sugared in a way that affects stableABI, it's supported. We can additionally list features which aren't affected by this. But I think that the actual implementation, true, should be a strong overestimation. - -**Martin** But in the end because it's something that binds not just the current compilers but all future compilers, once you guarantee a feature of stableABI you have to keep it. You can't change it anymore. So it needs to have a very strong specification what this is and the minimal one too. We don't want to overcommit ourselves. - -**Dmitry** stableABI says how do you consume the class if it's successfully compiled. So let's say a future compiler fails to compile it, it's perfectly fine. We're talking about stableABI, not stable source code. Similarly, one of the motivations is there is some use cases which are compiled by Scalac... - -**Martin** You say I have stableABI and Scala 2.12 accepts it and then Scala 2.13 comes up and says now I changed this thing so I won't compile this anymore. - -**Dmitry** But you can still use the artifact compiled by the previous one. - -**Martin** So you can break it, but you have to tell the user that you broke it. - -**Dmitry** There will be a compilation error that says doesn't compile. - -**Martin** I guess there would be a strong normative thing to say, well once one compiler guarantees certain things are okay under stableABI, future compilers will try not to break that. Otherwise, it wouldn't be that useful. - -**Dmitry** It would be a nice guarantee to have. But so far the SIP tries to ensure more or less safe publication on the maven so the artifact can be consumed reliably by the users. It's more providing safety for the users, not for the creator of the library. - -**Martin** In the end the compiler will have to check it and I think we have to give guidance to compilers what they should accept under stableABI. - -**Dmitry** Do you think there should be a minimal set of features which is accepted and there should be a warning if there is a feature used outside of this set? - -**Martin** Yes. We want to start now. Because I use something that a future compiler will break and it's not very useful to find out I can't upgrade my stuff because it's no longer stableABI. It will be useful that the compiler tells me now, look this thing is not guaranteed to be maintained in all future versions. Don't use it if you want to have an abstraction that for interchange. - -**Dmitry** The binary compatibility is if you've already compiled it, you can just give the compiled artifact to the future compiler which will safely consume it. It doesn't mean that the future compiler can compile it. But you can use the already compiled artifact. - -**Martin** I would think it would be much more interesting if it were source as well. That the future compiler would guarantee to compile it to the same bytecodes. Why do people write stuff in Java instead of Scala? Because you can't recompile this thing in a future compiler. We don't know whether the layout is the same or not so that was the thing where we need an antidote and say no if you write stableABI then even a Scala compiler will guarantee that it will be the same in future versions. If the thing succeeds in source, then it will be mapped to the same binary signatures as previously. We need to define a feature set now where that will be the case. Otherwise how are you going to implement that? - -**Dmitry** The current proposed criteria is it's the same signature written by the user. No de-sugarings for users. Let's say a user uses repeated arguments. Repeated arguments changes the binary signature. If the compiler can't add new members to stableABI classes and the compiler can't change the binary ____ for existing members. For vals, we synthesize a getter. You have a member of the class which was written by the user. Similarly for vars you also get a setter. Lazy vals gets the accessor which lazy vals synthesize two members which have funny signatures and they have funny names. Default methods is a thing which is checked explicitly here. It's the only thing. - -**We need this both in spec and doc**. - -**Dmitry** If you have a stableABI class, can it's arguments take a Scala Option or not? Currently Option is not a stableABI class which means you can be in a funny situation in which you succeed to call a method which takes an option, during the execution it fails _____ (50:13). The current proposal says that if you take non-stableABI classes as arguments or return types it gives you a warning. There isn't an all-or nothing approach that gets implemented. The proposal is that one day the library may decide to have some superclasses which it promises are stableABI for collections, for options, for all the types that are stable. This will be API to use those classes from stableABI classes and from other languages. - -**Sébastien** The goal is to be able to reuse artifacts from another compiler version or from a different compiler entirely but what happens to the @scala annotations? Is the classfile API might be the same between the same between 2.11 and 2.12 but it doesn't mean that the serialized form of the Scala signature notation is the same and can be read by the other compiler. So if you compile you source code against the binary artifacts that was published on maven your compilation will fail potentially with a crash or something like that because it cannot read the Scala-specific information from the class. - -**Dmitry** The proposal is not to emit stable Scala signatures. - -**Sébastien** So it really looks like a Java class file. Needs to be mentioned in the proposal. - -**Martin** If you don't emit a Scala signature then you can't have a co- or contravariant type parameter because they are only expressed in Scala signatures, in Java it's not there. I don't see how that follows from the current proposal. Also, isn't it platform dependent? - -**Sébastien** We do have a Java signature. Scala-JS doesn't disable classfile emission. When you say quickly compile, it uses the classfiles to quickly compile. when you use macros, it will extend from those classfiles. When you use an IDE it reduces the classfiles to identify things. When you use sbt, it uses classfiles to detect the changes. However, they aren't used by the ScalaJS linker. - -**Seth** Does this need to be part of the compiler or can it move forward as a plugin or just as a check performed in MiMa? MiMa just compares two different APIs. Can it have this other job as well: seeing if it does anything outside of the boundaries. - -**Dmitry** It could be a plugin, but it's not the right responsibility. Whoever develops the pluin does not have a way to enforce its rules by future compilers. Even though it provides guarantees to users, people providing these guarantees should be the people building the future versions. MiMa would need to be come half compiler. It's possible but not practical. If we say we emit Scala signatures, it's a strong promise and we allow users more. If everyone agrees, I would be glad. - -**Martin** Five years from now, do we even know whether Scala compilers will emit Java signatures? To put that in the spec seems too pre-implemention-oriented. We might need a minimum Scala signature, even if we don't emit a Java one. The way the signatures are treated should be an implemenation aspect which should be exactly orthogonal to what we do with stableABI that we want to have something that is stable across lots of implementations. - -**Jorge** We could make an exception that if we change the platform, then this annotation wouldn't apply. - -**Dmitry** The current proposal proposes only top-level classes by _____ (1:01:43). - -**Martin** It just has to be the guarantee of the whole package. The compiler has to translate this somehow so that future compilers will be able to read it in all eternity. That's the contract of stableABI. - -**Dmitry** Does this automatically mean that all future compilers should emit Scala signatures? - -**Martin** No, they just have to read whatever the previous one produced that had this thing. - -**Dmitry** So it means that the artifact compiled by Dotty that doesn't emit Scala signatures won't be able to consume it from Scalac. - -**Martin** That is true. You want to make a rule that newer compilers can ____ (1:02:54) the older ones but not the other way around. - -**Conclusion**: This proposal has been numbered as SIP-34. This is a complicated proposal that needs synchronization between the Scala and Dotty team to decide which encodings are good enough to make binary compatible. When Dmitry, the author of this proposal, figures out which features should be binary compatible and has more information on the future implementation, the SIP Committee will start the review period. - -### SIP-33 - Match infix & prefix types to meet expression rules (1:04:00) - -**Jorge** Making a change to the parser to make types behave as expressions. The other part of the proposal is about prefix types. Just like unary operators, he wants to have unary prefixes for type. So you can create a unary operator for types. - -**Iulian** Covariant and contravariant operators can cause confusion. - -**Eugene** But at least it's not ambiguous. - -**Sébastien** At least as long as we don't have Covariant type alias or abstract type members. If I could define `type +A`, what does that mean? - -**Eugene** If it's on the lefthand side of the equals signs type member, then it's covariant. As long as it's in a binding position, unary infix, should work. - -**Jorge** We will vote later. - -**Conclusion** The vote took place outside the meeting and the proposal was numbered. All of the committee members (including those absent) have accepted the change. diff --git a/sips/pending/futures-promises.html b/sips/pending/futures-promises.html deleted file mode 100644 index c50a334d7d..0000000000 --- a/sips/pending/futures-promises.html +++ /dev/null @@ -1,10 +0,0 @@ - - - -Moved - - -

                  Moved

                  -

                  This page has moved to http://docs.scala-lang.org/sips/completed/futures-promises.html.

                  - - diff --git a/sips/pending/implicit-classes.html b/sips/pending/implicit-classes.html deleted file mode 100644 index e2773696ba..0000000000 --- a/sips/pending/implicit-classes.html +++ /dev/null @@ -1,10 +0,0 @@ - - - -Moved - - -

                  Moved

                  -

                  This page has moved to http://docs.scala-lang.org/sips/completed/implicit-classes.html.

                  - - diff --git a/sips/pending/inline-classes.html b/sips/pending/inline-classes.html deleted file mode 100644 index 34e979080c..0000000000 --- a/sips/pending/inline-classes.html +++ /dev/null @@ -1,10 +0,0 @@ - - - -Moved - - -

                  Moved

                  -

                  This page has moved to http://docs.scala-lang.org/sips/completed/value-classes.html.

                  - - diff --git a/sips/pending/modularizing-language-features.html b/sips/pending/modularizing-language-features.html deleted file mode 100644 index 1bd61b7efc..0000000000 --- a/sips/pending/modularizing-language-features.html +++ /dev/null @@ -1,10 +0,0 @@ - - - -Moved - - -

                  Moved

                  -

                  This page has moved to http://docs.scala-lang.org/sips/completed/modularizing-language-features.html.

                  - - diff --git a/sips/pending/string-interpolation.html b/sips/pending/string-interpolation.html deleted file mode 100644 index 9086ffc1ae..0000000000 --- a/sips/pending/string-interpolation.html +++ /dev/null @@ -1,10 +0,0 @@ - - - -Moved - - -

                  Moved

                  -

                  This page has moved to http://docs.scala-lang.org/sips/completed/string-interpolation.html.

                  - - diff --git a/sips/pending/type-dynamic.html b/sips/pending/type-dynamic.html deleted file mode 100644 index 1921af4ad3..0000000000 --- a/sips/pending/type-dynamic.html +++ /dev/null @@ -1,10 +0,0 @@ - - - -Moved - - -

                  Moved

                  -

                  This page has moved to http://docs.scala-lang.org/sips/completed/type-dynamic.html.

                  - - diff --git a/sips/pending/value-classes.html b/sips/pending/value-classes.html deleted file mode 100644 index 34e979080c..0000000000 --- a/sips/pending/value-classes.html +++ /dev/null @@ -1,10 +0,0 @@ - - - -Moved - - -

                  Moved

                  -

                  This page has moved to http://docs.scala-lang.org/sips/completed/value-classes.html.

                  - - diff --git a/sips/sip-list.md b/sips/sip-list.md deleted file mode 100644 index 13283b633e..0000000000 --- a/sips/sip-list.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -layout: sip-landing -title: List of SIPs ---- - -### Completed SIPs ### -
                    - {% for post in site.categories.completed %} - {% if post.layout == 'sip' %} -
                  • {{ post.title }} ( {{ post.date | date: "%b %Y" }} )
                  • - {% endif %} - {% endfor %} -
                  - -### Rejected SIPs ### -
                    - {% for post in site.categories.rejected %} - {% if post.layout == 'sip' %} -
                  • {{ post.title }} ( {{ post.date | date: "%b %Y" }} )
                  • - {% endif %} - {% endfor %} -
                  - diff --git a/sips/slip-submission.md b/sips/slip-submission.md deleted file mode 100644 index e85865e3f8..0000000000 --- a/sips/slip-submission.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -layout: sip-landing -title: SLIP Submission Process ---- - -## How do I get started? ## - -Before proposing a new SLIP or new library functionality, it's a good idea -to get a feel for the core libraries first, and there's no better way than -by helping fix some of the -[core library issues in community tickets](http://scala-lang.org/contribute/#community_tickets). - -## How do I submit? ## - -Follow the instructions on the -[Scala SLIP github project page](https://www.github.com/scala/slip) - -Some more helpful resources: - -* [SLIP Gitter Channel](https://gitter.im/scala/slip) to chat or ask for help. -* [Markdown Syntax](http://daringfireball.net/projects/markdown/syntax) - for help with the markdown syntax used for SLIP documentation. -* [scala-internals](https://groups.google.com/scala-internals) for on-the-record - discussions about SLIP proposals. -* [Scala Process on Google+](https://plus.google.com/scalaslip) for details on the - next SLIP/SIP meeting and other information. \ No newline at end of file diff --git a/style/index.md b/style/index.md deleted file mode 100644 index 07ace255b3..0000000000 --- a/style/index.md +++ /dev/null @@ -1,167 +0,0 @@ ---- -layout: index -title: Scala Style Guide ---- - - - -
                  - -
                  -

                  About

                  -
                  - -

                  This document is intended to outline some basic Scala stylistic guidelines which should be followed with more or less fervency. Wherever possible, this guide attempts to detail why a particular style is encouraged and how it relates to other alternatives. As with all style guides, treat this document as a list of rules to be broken. There are certainly times when alternative styles should be preferred over the ones given here.

                  - -

                  Thanks to

                  -

                  Daniel Spiewak and David Copeland for putting this style guide together, and Simon Ochsenreither for converting it to Markdown.

                  - -
                  diff --git a/style/overview.md b/style/overview.md deleted file mode 100644 index e813e46182..0000000000 --- a/style/overview.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -layout: overview-large -title: Overview - -partof: style-guide -num: 1 -outof: 10 - -next-page: indentation ---- - -Please see the [table of contents of the style guide]({{ site.baseurl }}/style) for an outline-style overview. diff --git a/tutorials/FAQ/collections.md b/tutorials/FAQ/collections.md deleted file mode 100644 index df2bfa075e..0000000000 --- a/tutorials/FAQ/collections.md +++ /dev/null @@ -1,377 +0,0 @@ ---- -layout: overview-large -title: How are the collections structured? Which one should I choose? - -discourse: true - -partof: FAQ -num: 8 ---- -## Foreword - -There's a [2.8 collection walk-through][1] by Martin Odersky which should -probably be your first reference. It has been supplemented as well with -[architectural notes][2], which will be of particular interest to those who -want to design their own collections. - -The rest of this answer was written way before any such thing existed (in fact, -before 2.8.0 itself was released). - -You can find a paper about it as [Scala SID #3][3]. Other papers in that area -should be interesting as well to people interested in the differences between -Scala 2.7 and 2.8. - -I'll quote from the paper, selectively, and complement with some thoughts of -mine. There are also some images, generated by Matthias at decodified.com, and -the original SVG files can be found [here][4]. - -## The collection classes/traits themselves - -There are actually three hierarchies of traits for the collections: one for -mutable collections, one for immutable collections, and one which doesn't make -any assumptions about the collections. - -There's also a distinction between parallel, serial and maybe-parallel -collections, which was introduced with Scala 2.9. I'll talk about them in the -next section. The hierarchy described in this section refers _exclusively to -non-parallel collections_. - -The following image shows the non-specific hierarchy introduced with Scala 2.8: -![General collection hierarchy][5] - -All elements shown are traits. In the other two hierarchies there are also -classes directly inheriting the traits as well as classes which can be _viewed -as_ belonging in that hierarchy through implicit conversion to wrapper classes. -The legend for these graphs can be found after them. - -Graph for immutable hierarchy: - - -Graph for mutable hierarchy: - - -Legend: - -![Graph legend][8] - -Here's an abbreviated ASCII depiction of the collection hierarchy, for those who can't see the images. - - Traversable - | - | - Iterable - | - +------------------+--------------------+ - Map Set Seq - | | | - | +----+----+ +-----+------+ - Sorted Map SortedSet BitSet Buffer Vector LinearSeq - - -## Parallel Collections - -When Scala 2.9 introduced parallel collections, one of the design goals was to -make their use as seamless as possible. In the simplest terms, one can replace -a non-parallel (serial) collection with a parallel one, and instantly reap the -benefits. - -However, since all collections until then were serial, many algorithms using -them assumed and depended on the fact that they _were_ serial. Parallel -collections fed to the methods with such assumptions would fail. For this -reason, all the hierarchy described in the previous section _mandates serial -processing_. - -Two new hierarchies were created to support the parallel collections. - -The parallel collections hierarchy has the same names for traits, but preceded -with `Par`: `ParIterable`, `ParSeq`, `ParMap` and `ParSet`. Note that there is -no `ParTraversable`, since any collection supporting parallel access is capable -of supporting the stronger `ParIterable` trait. It doesn't have some of the -more specialized traits present in the serial hierarchy either. This whole -hierarchy is found under the directory `scala.collection.parallel`. - -The classes implementing parallel collections also differ, with `ParHashMap` -and `ParHashSet` for both mutable and immutable parallel collections, plus -`ParRange` and `ParVector` implementing `immutable.ParSeq` and `ParArray` -implementing `mutable.ParSeq`. - -Another hierarchy also exists that mirrors the traits of serial and parallel -collections, but with a prefix `Gen`: `GenTraversable`, `GenIterable`, -`GenSeq`, `GenMap` and `GenSet`. These traits are _parents_ to both parallel -and serial collections. This means that a method taking a `Seq` cannot receive -a parallel collection, but a method taking a `GenSeq` is expected to work with -both serial and parallel collections. - -Given the way these hierarchies were structured, code written for Scala 2.8 was -fully compatible with Scala 2.9, and demanded serial behavior. Without being -rewritten, it cannot take advantage of parallel collections, but the changes -required are very small. - -### Using Parallel Collections - -Any collection can be converted into a parallel one by calling the method `par` -on it. Likewise, any collection can be converted into a serial one by calling -the method `seq` on it. - -If the collection was already of the type requested (parallel or serial), no -conversion will take place. If one calls `seq` on a parallel collection or -`par` on a serial collection, however, a new collection with the requested -characteristic will be generated. - -Do not confuse `seq`, which turns a collection into a non-parallel collection, -with `toSeq`, which returns a `Seq` created from the elements of the -collection. Calling `toSeq` on a parallel collection will return a `ParSeq`, -not a serial collection. - -## The Main Traits - -While there are many implementing classes and subtraits, there are some basic -traits in the hierarchy, each of which providing more methods or more specific -guarantees, but reducing the number of classes that could implement them. - -In the following subsections, I'll give a brief description of the main traits -and the idea behind them. - -### Trait TraversableOnce - -This trait is pretty much like trait `Traversable` described below, but with -the limitation that you can only use it _once_. That is, any methods called on -a `TraversableOnce` _may_ render it unusable. - -This limitation makes it possible for the same methods to be shared between the -collections and `Iterator`. This makes it possible for a method that works with -an `Iterator` but not using `Iterator`-specific methods to actually be able to -work with any collection at all, plus iterators, if rewritten to accept -`TraversableOnce`. - -Because `TraversableOnce` unifies collections and iterators, and iterators are -not considered collections, it does not appear in the previous graphs, which -concern themselves only with collections. - -### Trait Traversable - -At the top of the _collection_ hierarchy is trait `Traversable`. Its only -abstract operation is - - def foreach[U](f: Elem => U) - -The operation is meant to traverse all elements of the collection, and apply -the given operation f to each element. The application is done for its side -effect only; in fact any function result of f is discarded by foreach. - -Traversible objects can be finite or infinite. An example of an infinite -traversable object is the stream of natural numbers `Stream.from(0)`. The -method `hasDefiniteSize` indicates whether a collection is possibly infinite. -If `hasDefiniteSize` returns true, the collection is certainly finite. If it -returns false, the collection has not been not fully elaborated yet, so it -might be infinite or finite. - -This class defines methods which can be efficiently implemented in terms of -`foreach` (over 40 of them). - -### Trait Iterable - -This trait declares an abstract method `iterator` that returns an iterator that -yields all the collection’s elements one by one. The `foreach` method in -`Iterable` is implemented in terms of `iterator`. Subclasses of `Iterable` -often override foreach with a direct implementation for efficiency. - -Class `Iterable` also adds some less-often used methods to `Traversable`, which -can be implemented efficiently only if an `iterator` is available. They are -summarized below. - - xs.iterator An iterator that yields every element in xs, in the same order as foreach traverses elements. - xs takeRight n A collection consisting of the last n elements of xs (or, some arbitrary n elements, if no order is defined). - xs dropRight n The rest of the collection except xs takeRight n. - xs sameElements ys A test whether xs and ys contain the same elements in the same order - -### Seq, Set and Map - -After `Iterable` there come three base traits which inherit from it: `Seq`, -`Set`, and `Map`. All three have an `apply` method and all three implement the -`PartialFunction` trait, but the meaning of `apply` is different in each case. - -I trust the meaning of `Seq`, `Set` and `Map` is intuitive. After them, the -classes break up in specific implementations that offer particular guarantees -with regards to performance, and the methods it makes available as a result of -it. Also available are some traits with further refinements, such as -`LinearSeq`, `IndexedSeq` and `SortedSet`. - -## Complete Overview - -### Base Classes and Traits - -* `TraversableOnce` -- All methods and behavior common to collections and iterators. - - * `Traversable` -- Basic collection class. Can be implemented just with `foreach`. - - * `TraversableProxy` -- Proxy for a `Traversable`. Just point `self` to the real collection. - * `TraversableView` -- A Traversable with some non-strict methods. - * `TraversableForwarder` -- Forwards most methods to `underlying`, except `toString`, `hashCode`, `equals`, `stringPrefix`, `newBuilder`, `view` and all calls creating a new iterable object of the same kind. - * `mutable.Traversable` and `immutable.Traversable` -- same thing as `Traversable`, but restricting the collection type. - * Other special-cases `Iterable` classes, such as `MetaData`, exists. - * `Iterable` -- A collection for which an `Iterator` can be created (through `iterator`). - * `IterableProxy`, `IterableView`, `mutable` and `immutable.Iterable`. - - * `Iterator` -- A trait which is not descendant of `Traversable`. Define `next` and `hasNext`. - * `CountedIterator` -- An `Iterator` defining `count`, which returns the elements seen so far. - * `BufferedIterator` -- Defines `head`, which returns the next element without consuming it. - * Other special-cases `Iterator` classes, such as `Source`, exists. - -### The Sequences - -* `Seq` -- A sequence of elements. One assumes a well-defined size and element repetition. Extends `PartialFunction` as well. - - * `IndexedSeq` -- Sequences that support O(1) element access and O(1) length computation. - * `IndexedSeqView` - * `immutable.PagedSeq` -- An implementation of `IndexedSeq` where the elements are produced on-demand by a function passed through the constructor. - * `immutable.IndexedSeq` - - * `immutable.Range` -- A delimited sequence of integers, closed on the lower end, open on the high end, and with a step. - * `immutable.Range.Inclusive` -- A `Range` closed on the high end as well. - * `immutable.NumericRange` -- A more generic version of `Range` which works with any `Integral`. - * `immutable.NumericRange.Inclusive`, `immutable.NumericRange.Exclusive`. - * `immutable.WrappedString`, `immutable.RichString` -- Wrappers which enables seeing a `String` as a `Seq[Char]`, while still preserving the `String` methods. I'm not sure what the difference between them is. - - * `mutable.IndexedSeq` - * `mutable.GenericArray` -- An `Seq`-based array-like structure. Note that the "class" `Array` is Java's `Array`, which is more of a memory storage method than a class. - * `mutable.ResizableArray` -- Internal class used by classes based on resizable arrays. - * `mutable.PriorityQueue`, `mutable.SynchronizedPriorityQueue` -- Classes implementing prioritized queues -- queues where the elements are dequeued according to an `Ordering` first, and order of queueing last. - * `mutable.PriorityQueueProxy` -- an abstract `Proxy` for a `PriorityQueue`. - - * `LinearSeq` -- A trait for linear sequences, with efficient time for `isEmpty`, `head` and `tail`. - - * `immutable.LinearSeq` - * `immutable.List` -- An immutable, singlely-linked, list implementation. - * `immutable.Stream` -- A lazy-list. Its elements are only computed on-demand, but memoized (kept in memory) afterwards. It can be theoretically infinite. - * `mutable.LinearSeq` - * `mutable.DoublyLinkedList` -- A list with mutable `prev`, `head` (`elem`) and `tail` (`next`). - * `mutable.LinkedList` -- A list with mutable `head` (`elem`) and `tail` (`next`). - * `mutable.MutableList` -- A class used internally to implement classes based on mutable lists. - * `mutable.Queue`, `mutable.QueueProxy` -- A data structure optimized for FIFO (First-In, First-Out) operations. - * `mutable.QueueProxy` -- A `Proxy` for a `mutable.Queue`. - - * `SeqProxy`, `SeqView`, `SeqForwarder` - - * `immutable.Seq` - - * `immutable.Queue` -- A class implementing a FIFO-optimized (First-In, First-Out) data structure. There is no common superclass of both `mutable` and `immutable` queues. - * `immutable.Stack` -- A class implementing a LIFO-optimized (Last-In, First-Out) data structure. There is no common superclass of both `mutable` `immutable` stacks. - * `immutable.Vector` -- ? - * `scala.xml.NodeSeq` -- A specialized XML class which extends `immutable.Seq`. - * `immutable.IndexedSeq` -- As seen above. - * `immutable.LinearSeq` -- As seen above. - - * `mutable.ArrayStack` -- A class implementing a LIFO-optimized data structure using arrays. Supposedly significantly faster than a normal stack. - * `mutable.Stack`, `mutable.SynchronizedStack` -- Classes implementing a LIFO-optimized data structure. - * `mutable.StackProxy` -- A `Proxy` for a `mutable.Stack`.. - * `mutable.Seq` - - * `mutable.Buffer` -- Sequence of elements which can be changed by appending, prepending or inserting new members. - * `mutable.ArrayBuffer` -- An implementation of the `mutable.Buffer` class, with constant amortized time for the append, update and random access operations. It has some specialized subclasses, such as `NodeBuffer`. - * `mutable.BufferProxy`, `mutable.SynchronizedBuffer`. - * `mutable.ListBuffer` -- A buffer backed by a list. It provides constant time append and prepend, with most other operations being linear. - * `mutable.ObservableBuffer` -- A *mixin* trait which, when mixed to a `Buffer`, provides notification events through a `Publisher` interfaces. - * `mutable.IndexedSeq` -- As seen above. - * `mutable.LinearSeq` -- As seen above. - -### The Sets - -* `Set` -- A set is a collection that includes at most one of any object. - - * `BitSet` -- A set of integers stored as a bitset. - * `immutable.BitSet` - * `mutable.BitSet` - - * `SortedSet` -- A set whose elements are ordered. - * `immutable.SortedSet` - * `immutable.TreeSet` -- An implementation of a `SortedSet` based on a tree. - - * `SetProxy` -- A `Proxy` for a `Set`. - - * `immutable.Set` - * `immutable.HashSet` -- An implementation of `Set` based on element hashing. - * `immutable.ListSet` -- An implementation of `Set` based on lists. - * Additional set classes exists to provide optimized implementations for sets from 0 to 4 elements. - * `immutable.SetProxy` -- A `Proxy` for an immutable `Set`. - - * `mutable.Set` - * `mutable.HashSet` -- An implementation of `Set` based on element hashing. - * `mutable.ImmutableSetAdaptor` -- A class implementing a mutable `Set` from an immutable `Set`. - * `LinkedHashSet` -- An implementation of `Set` based on lists. - * `ObservableSet` -- A *mixin* trait which, when mixed with a `Set`, provides notification events through a `Publisher` interface. - * `SetProxy` -- A `Proxy` for a `Set`. - * `SynchronizedSet` -- A *mixin* trait which, when mixed with a `Set`, provides notification events through a `Publisher` interface. - -### The Maps - -* `Map` -- An `Iterable` of `Tuple2`, which also provides methods for retrieving a value (the second element of the tuple) given a key (the first element of the tuple). Extends `PartialFunction` as well. - * `MapProxy` -- A `Proxy` for a `Map`. - * `DefaultMap` -- A trait implementing some of `Map`'s abstract methods. - * `SortedMap` -- A `Map` whose keys are sorted. - * `immutable.SortMap` - * `immutable.TreeMap` -- A class implementing `immutable.SortedMap`. - * `immutable.Map` - * `immutable.MapProxy` - * `immutable.HashMap` -- A class implementing `immutable.Map` through key hashing. - * `immutable.IntMap` -- A class implementing `immutable.Map` specialized for `Int` keys. Uses a tree based on the binary digits of the keys. - * `immutable.ListMap` -- A class implementing `immutable.Map` through lists. - * `immutable.LongMap` -- A class implementing `immutable.Map` specialized for `Long` keys. See `IntMap`. - * There are additional classes optimized for an specific number of elements. - * `mutable.Map` - * `mutable.HashMap` -- A class implementing `mutable.Map` through key hashing. - * `mutable.ImmutableMapAdaptor` -- A class implementing a `mutable.Map` from an existing `immutable.Map`. - * `mutable.LinkedHashMap` -- ? - * `mutable.ListMap` -- A class implementing `mutable.Map` through lists. - * `mutable.MultiMap` -- A class accepting more than one distinct value for each key. - * `mutable.ObservableMap` -- A *mixin* which, when mixed with a `Map`, publishes events to observers through a `Publisher` interface. - * `mutable.OpenHashMap` -- A class based on an open hashing algorithm. - * `mutable.SynchronizedMap` -- A *mixin* which should be mixed with a `Map` to provide a version of it with synchronized methods. - * `mutable.MapProxy`. - -## Bonus Questions - -* Why the Like classes exist (e.g. TraversableLike)? - -This was done to achieve maximum code reuse. The concrete *generic* -implementation for classes with a certain structure (a traversable, a map, etc) -is done in the Like classes. The classes intended for general consumption, -then, override selected methods that can be optmized. - -* What the companion methods are for (e.g. List.companion)? - -The builder for the classes, ie, the object which knows how to create instances -of that class in a way that can be used by methods like `map`, is created by a -method in the companion object. So, in order to build an object of type X, I -need to get that builder from the companion object of X. Unfortunately, there -is no way, in Scala, to get from class X to object X. Because of that, there is -a method defined in each instance of X, `companion`, which returns the -companion object of class X. - -While there might be some use for such method in normal programs, its target is -enabling code reuse in the collection library. - -* How I know what implicit objects are in scope at a given point? - -You aren't supposed to care about that. They are implicit precisely so that you -don't need to figure out how to make it work. - -These implicits exists to enable the methods on the collections to be defined -on parent classes but still return a collection of the same type. For example, -the `map` method is defined on `TraversableLike`, but if you used on a `List` -you'll get a `List` back. - -This answer was originally submitted in response to [this question][9] on Stack -Overflow. - - - [1]: http://docs.scala-lang.org/overviews/collections/introduction.html - [2]: http://docs.scala-lang.org/overviews/core/architecture-of-scala-collections.html - [3]: http://www.scala-lang.org/sid/3 - [4]: https://github.com/sirthias/scala-collections-charts/downloads - [5]: http://i.stack.imgur.com/bSVyA.png - [6]: http://i.stack.imgur.com/2fjoA.png - [7]: http://i.stack.imgur.com/Dsptl.png - [8]: http://i.stack.imgur.com/szWUr.png - [9]: http://stackoverflow.com/q/1722137/53013 - diff --git a/tutorials/index.md b/tutorials/index.md deleted file mode 100644 index 9d83fa0dba..0000000000 --- a/tutorials/index.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -layout: index -title: Tutorials ---- - -
                  - -
                  -

                  New to Scala?

                  -

                  Tutorials geared for people coming...

                  - -
                  - -
                  -

                  FAQ

                  Frequently Asked Questions (and their answers!)

                  -
                  - {% for pg in site.pages %} - {% if pg.partof == "FAQ" and pg.outof %} - {% assign totalPagesFAQ = pg.outof %} - {% endif %} - {% endfor %} - - {% if totalPagesFAQ %} -
                    - {% for i in (1..totalPagesFAQ) %} - {% for pg in site.pages %} - {% if pg.partof == "FAQ" and pg.num and pg.num == i %} -
                  • {{ pg.title }}
                  • - {% endif %} - {% endfor %} - {% endfor %} -
                  - {% else %} **ERROR**. Couldn't find the total number of pages in this set of tutorial articles. Have you declared the `outof` tag in your YAML front matter? - {% endif %} - -
                  - -
                  -
                  -

                  A Tour of Scala

                  Bite-size pieces of the essentials...

                  -
                  - {% include tutorial-tour-list.txt %} -
                  - - diff --git a/tutorials/partest-guide.md b/tutorials/partest-guide.md deleted file mode 100644 index e681c569fe..0000000000 --- a/tutorials/partest-guide.md +++ /dev/null @@ -1,66 +0,0 @@ ---- -layout: page -title: Running the Test Suite ---- - -Partest is a custom parallel testing tool that we use to run the test suite for the Scala compiler and library. Go the scala project folder from your local checkout and run it via `ant` or standalone as follows. - -## Using ant - -The test suite can be run by using ant from the command line: - - $ ant test.suite - -## Standalone - -There are launch scripts `partest` and `partest.bat` in the `test` folder of the scala project. To have partest run failing tests only and print details about test failures to the console, you can use - - ./test/partest --show-diff --show-log --failed - -You can get a summary of the usage by running partest without arguments. - -* Most commonly you want to invoke partest with an option that tells it which part of the tests to run. For example `--all`, `--pos`, `--neg` or `--run`. -* You can test individual files by specifying individual test files (`.scala` files) as options. Several files can be tested if they are from the same category, e.g., `pos`. -* You can enable output of log and diff using the `-show-log` and `-show-diff` options. -* If you get into real trouble, and want to find out what partest does, you can run it with option `--verbose`. This info is useful as part of bug reports. -* Set custom path from where to load classes: `-classpath ` and `-buildpath `. -* You can use the `SCALAC_OPTS` environment variable to pass command line options to the compiler. -* You can use the `JAVA_OPTS` environment variable to pass command line options to the runner (e.g., for `run/jvm` tests). -* The launch scripts run partest as follows: - - scala -cp scala.tools.partest.nest.NestRunner - - Partest classes from a `quick` build, e.g., can be found in `./build/quick/classes/partest/`. - - Partest will tell you where it loads compiler/library classes from by adding the `partest.debug` property: - - scala -Dpartest.debug=true -cp scala.tools.partest.nest.NestRunner - - - -## ScalaCheck tests - -Tests that depend on [ScalaCheck](https://github.com/rickynils/scalacheck) can be added under folder `./test/files/scalacheck`. A sample test: - - import org.scalacheck._ - import Prop._ - - object Test { - val prop_ConcatLists = property{ (l1: ListInt, l2: ListInt) => - l1.size + l2.size == (l1 ::: l2).size - } - - val tests = List(("prop_ConcatLists", prop_ConcatLists)) - } - -## Troubleshooting - -### Windows - -Some tests might fail because line endings in the `.check` files and the produced results do not match. In that case, set either - - git config core.autocrlf false - -or - - git config core.autocrlf input \ No newline at end of file diff --git a/tutorials/scala-for-java-programmers.md b/tutorials/scala-for-java-programmers.md deleted file mode 100644 index b8618b431c..0000000000 --- a/tutorials/scala-for-java-programmers.md +++ /dev/null @@ -1,723 +0,0 @@ ---- -layout: overview -title: A Scala Tutorial for Java Programmers -overview: scala-for-java-programmers - -discourse: true -multilingual-overview: true -languages: [es, ko, de, it, zh-tw] ---- - -By Michel Schinz and Philipp Haller - -## Introduction - -This document gives a quick introduction to the Scala language and -compiler. It is intended for people who already have some programming -experience and want an overview of what they can do with Scala. A -basic knowledge of object-oriented programming, especially in Java, is -assumed. - -## A First Example - -As a first example, we will use the standard *Hello world* program. It -is not very fascinating but makes it easy to demonstrate the use of -the Scala tools without knowing too much about the language. Here is -how it looks: - - object HelloWorld { - def main(args: Array[String]) { - println("Hello, world!") - } - } - -The structure of this program should be familiar to Java programmers: -it consists of one method called `main` which takes the command -line arguments, an array of strings, as parameter; the body of this -method consists of a single call to the predefined method `println` -with the friendly greeting as argument. The `main` method does not -return a value (it is a procedure method). Therefore, it is not necessary -to declare a return type. - -What is less familiar to Java programmers is the `object` -declaration containing the `main` method. Such a declaration -introduces what is commonly known as a *singleton object*, that -is a class with a single instance. The declaration above thus declares -both a class called `HelloWorld` and an instance of that class, -also called `HelloWorld`. This instance is created on demand, -the first time it is used. - -The astute reader might have noticed that the `main` method is -not declared as `static` here. This is because static members -(methods or fields) do not exist in Scala. Rather than defining static -members, the Scala programmer declares these members in singleton -objects. - -### Compiling the example - -To compile the example, we use `scalac`, the Scala compiler. `scalac` -works like most compilers: it takes a source file as argument, maybe -some options, and produces one or several object files. The object -files it produces are standard Java class files. - -If we save the above program in a file called -`HelloWorld.scala`, we can compile it by issuing the following -command (the greater-than sign `>` represents the shell prompt -and should not be typed): - - > scalac HelloWorld.scala - -This will generate a few class files in the current directory. One of -them will be called `HelloWorld.class`, and contains a class -which can be directly executed using the `scala` command, as the -following section shows. - -### Running the example - -Once compiled, a Scala program can be run using the `scala` command. -Its usage is very similar to the `java` command used to run Java -programs, and accepts the same options. The above example can be -executed using the following command, which produces the expected -output: - - > scala -classpath . HelloWorld - - Hello, world! - -## Interaction with Java - -One of Scala's strengths is that it makes it very easy to interact -with Java code. All classes from the `java.lang` package are -imported by default, while others need to be imported explicitly. - -Let's look at an example that demonstrates this. We want to obtain -and format the current date according to the conventions used in a -specific country, say France. (Other regions such as the -French-speaking part of Switzerland use the same conventions.) - -Java's class libraries define powerful utility classes, such as -`Date` and `DateFormat`. Since Scala interoperates -seemlessly with Java, there is no need to implement equivalent -classes in the Scala class library--we can simply import the classes -of the corresponding Java packages: - - import java.util.{Date, Locale} - import java.text.DateFormat - import java.text.DateFormat._ - - object FrenchDate { - def main(args: Array[String]) { - val now = new Date - val df = getDateInstance(LONG, Locale.FRANCE) - println(df format now) - } - } - -Scala's import statement looks very similar to Java's equivalent, -however, it is more powerful. Multiple classes can be imported from -the same package by enclosing them in curly braces as on the first -line. Another difference is that when importing all the names of a -package or class, one uses the underscore character (`_`) instead -of the asterisk (`*`). That's because the asterisk is a valid -Scala identifier (e.g. method name), as we will see later. - -The import statement on the third line therefore imports all members -of the `DateFormat` class. This makes the static method -`getDateInstance` and the static field `LONG` directly -visible. - -Inside the `main` method we first create an instance of Java's -`Date` class which by default contains the current date. Next, we -define a date format using the static `getDateInstance` method -that we imported previously. Finally, we print the current date -formatted according to the localized `DateFormat` instance. This -last line shows an interesting property of Scala's syntax. Methods -taking one argument can be used with an infix syntax. That is, the -expression - - df format now - -is just another, slightly less verbose way of writing the expression - - df.format(now) - -This might seem like a minor syntactic detail, but it has important -consequences, one of which will be explored in the next section. - -To conclude this section about integration with Java, it should be -noted that it is also possible to inherit from Java classes and -implement Java interfaces directly in Scala. - -## Everything is an Object - -Scala is a pure object-oriented language in the sense that -*everything* is an object, including numbers or functions. It -differs from Java in that respect, since Java distinguishes -primitive types (such as `boolean` and `int`) from reference -types, and does not enable one to manipulate functions as values. - -### Numbers are objects - -Since numbers are objects, they also have methods. And in fact, an -arithmetic expression like the following: - - 1 + 2 * 3 / x - -consists exclusively of method calls, because it is equivalent to the -following expression, as we saw in the previous section: - - (1).+(((2).*(3))./(x)) - -This also means that `+`, `*`, etc. are valid identifiers -in Scala. - -The parentheses around the numbers in the second version are necessary -because Scala's lexer uses a longest match rule for tokens. -Therefore, it would break the following expression: - - 1.+(2) - -into the tokens `1.`, `+`, and `2`. The reason that -this tokenization is chosen is because `1.` is a longer valid -match than `1`. The token `1.` is interpreted as the -literal `1.0`, making it a `Double` rather than an -`Int`. Writing the expression as: - - (1).+(2) - -prevents `1` from being interpreted as a `Double`. - -### Functions are objects - -Perhaps more surprising for the Java programmer, functions are also -objects in Scala. It is therefore possible to pass functions as -arguments, to store them in variables, and to return them from other -functions. This ability to manipulate functions as values is one of -the cornerstone of a very interesting programming paradigm called -*functional programming*. - -As a very simple example of why it can be useful to use functions as -values, let's consider a timer function whose aim is to perform some -action every second. How do we pass it the action to perform? Quite -logically, as a function. This very simple kind of function passing -should be familiar to many programmers: it is often used in -user-interface code, to register call-back functions which get called -when some event occurs. - -In the following program, the timer function is called -`oncePerSecond`, and it gets a call-back function as argument. -The type of this function is written `() => Unit` and is the type -of all functions which take no arguments and return nothing (the type -`Unit` is similar to `void` in C/C++). The main function of -this program simply calls this timer function with a call-back which -prints a sentence on the terminal. In other words, this program -endlessly prints the sentence "time flies like an arrow" every -second. - - object Timer { - def oncePerSecond(callback: () => Unit) { - while (true) { callback(); Thread sleep 1000 } - } - def timeFlies() { - println("time flies like an arrow...") - } - def main(args: Array[String]) { - oncePerSecond(timeFlies) - } - } - -Note that in order to print the string, we used the predefined method -`println` instead of using the one from `System.out`. - -#### Anonymous functions - -While this program is easy to understand, it can be refined a bit. -First of all, notice that the function `timeFlies` is only -defined in order to be passed later to the `oncePerSecond` -function. Having to name that function, which is only used once, might -seem unnecessary, and it would in fact be nice to be able to construct -this function just as it is passed to `oncePerSecond`. This is -possible in Scala using *anonymous functions*, which are exactly -that: functions without a name. The revised version of our timer -program using an anonymous function instead of *timeFlies* looks -like that: - - object TimerAnonymous { - def oncePerSecond(callback: () => Unit) { - while (true) { callback(); Thread sleep 1000 } - } - def main(args: Array[String]) { - oncePerSecond(() => - println("time flies like an arrow...")) - } - } - -The presence of an anonymous function in this example is revealed by -the right arrow `=>` which separates the function's argument -list from its body. In this example, the argument list is empty, as -witnessed by the empty pair of parenthesis on the left of the arrow. -The body of the function is the same as the one of `timeFlies` -above. - -## Classes - -As we have seen above, Scala is an object-oriented language, and as -such it has a concept of class. (For the sake of completeness, - it should be noted that some object-oriented languages do not have - the concept of class, but Scala is not one of them.) -Classes in Scala are declared using a syntax which is close to -Java's syntax. One important difference is that classes in Scala can -have parameters. This is illustrated in the following definition of -complex numbers. - - class Complex(real: Double, imaginary: Double) { - def re() = real - def im() = imaginary - } - -This `Complex` class takes two arguments, which are the real and -imaginary part of the complex. These arguments must be passed when -creating an instance of class `Complex`, as follows: `new - Complex(1.5, 2.3)`. The class contains two methods, called `re` -and `im`, which give access to these two parts. - -It should be noted that the return type of these two methods is not -given explicitly. It will be inferred automatically by the compiler, -which looks at the right-hand side of these methods and deduces that -both return a value of type `Double`. - -The compiler is not always able to infer types like it does here, and -there is unfortunately no simple rule to know exactly when it will be, -and when not. In practice, this is usually not a problem since the -compiler complains when it is not able to infer a type which was not -given explicitly. As a simple rule, beginner Scala programmers should -try to omit type declarations which seem to be easy to deduce from the -context, and see if the compiler agrees. After some time, the -programmer should get a good feeling about when to omit types, and -when to specify them explicitly. - -### Methods without arguments - -A small problem of the methods `re` and `im` is that, in -order to call them, one has to put an empty pair of parenthesis after -their name, as the following example shows: - - object ComplexNumbers { - def main(args: Array[String]) { - val c = new Complex(1.2, 3.4) - println("imaginary part: " + c.im()) - } - } - -It would be nicer to be able to access the real and imaginary parts -like if they were fields, without putting the empty pair of -parenthesis. This is perfectly doable in Scala, simply by defining -them as methods *without arguments*. Such methods differ from -methods with zero arguments in that they don't have parenthesis after -their name, neither in their definition nor in their use. Our -`Complex` class can be rewritten as follows: - - class Complex(real: Double, imaginary: Double) { - def re = real - def im = imaginary - } - - -### Inheritance and overriding - -All classes in Scala inherit from a super-class. When no super-class -is specified, as in the `Complex` example of previous section, -`scala.AnyRef` is implicitly used. - -It is possible to override methods inherited from a super-class in -Scala. It is however mandatory to explicitly specify that a method -overrides another one using the `override` modifier, in order to -avoid accidental overriding. As an example, our `Complex` class -can be augmented with a redefinition of the `toString` method -inherited from `Object`. - - class Complex(real: Double, imaginary: Double) { - def re = real - def im = imaginary - override def toString() = - "" + re + (if (im < 0) "" else "+") + im + "i" - } - - -## Case Classes and Pattern Matching - -A kind of data structure that often appears in programs is the tree. -For example, interpreters and compilers usually represent programs -internally as trees; XML documents are trees; and several kinds of -containers are based on trees, like red-black trees. - -We will now examine how such trees are represented and manipulated in -Scala through a small calculator program. The aim of this program is -to manipulate very simple arithmetic expressions composed of sums, -integer constants and variables. Two examples of such expressions are -`1+2` and `(x+x)+(7+y)`. - -We first have to decide on a representation for such expressions. The -most natural one is the tree, where nodes are operations (here, the -addition) and leaves are values (here constants or variables). - -In Java, such a tree would be represented using an abstract -super-class for the trees, and one concrete sub-class per node or -leaf. In a functional programming language, one would use an algebraic -data-type for the same purpose. Scala provides the concept of -*case classes* which is somewhat in between the two. Here is how -they can be used to define the type of the trees for our example: - - abstract class Tree - case class Sum(l: Tree, r: Tree) extends Tree - case class Var(n: String) extends Tree - case class Const(v: Int) extends Tree - -The fact that classes `Sum`, `Var` and `Const` are -declared as case classes means that they differ from standard classes -in several respects: - -- the `new` keyword is not mandatory to create instances of - these classes (i.e., one can write `Const(5)` instead of - `new Const(5)`), -- getter functions are automatically defined for the constructor - parameters (i.e., it is possible to get the value of the `v` - constructor parameter of some instance `c` of class - `Const` just by writing `c.v`), -- default definitions for methods `equals` and - `hashCode` are provided, which work on the *structure* of - the instances and not on their identity, -- a default definition for method `toString` is provided, and - prints the value in a "source form" (e.g., the tree for expression - `x+1` prints as `Sum(Var(x),Const(1))`), -- instances of these classes can be decomposed through - *pattern matching* as we will see below. - -Now that we have defined the data-type to represent our arithmetic -expressions, we can start defining operations to manipulate them. We -will start with a function to evaluate an expression in some -*environment*. The aim of the environment is to give values to -variables. For example, the expression `x+1` evaluated in an -environment which associates the value `5` to variable `x`, written -`{ x -> 5 }`, gives `6` as result. - -We therefore have to find a way to represent environments. We could of -course use some associative data-structure like a hash table, but we -can also directly use functions! An environment is really nothing more -than a function which associates a value to a (variable) name. The -environment `{ x -> 5 }` given above can simply be written as -follows in Scala: - - { case "x" => 5 } - -This notation defines a function which, when given the string -`"x"` as argument, returns the integer `5`, and fails with an -exception otherwise. - -Before writing the evaluation function, let us give a name to the type -of the environments. We could of course always use the type -`String => Int` for environments, but it simplifies the program -if we introduce a name for this type, and makes future changes easier. -This is accomplished in Scala with the following notation: - - type Environment = String => Int - -From then on, the type `Environment` can be used as an alias of -the type of functions from `String` to `Int`. - -We can now give the definition of the evaluation function. -Conceptually, it is very simple: the value of a sum of two expressions -is simply the sum of the value of these expressions; the value of a -variable is obtained directly from the environment; and the value of a -constant is the constant itself. Expressing this in Scala is not more -difficult: - - def eval(t: Tree, env: Environment): Int = t match { - case Sum(l, r) => eval(l, env) + eval(r, env) - case Var(n) => env(n) - case Const(v) => v - } - -This evaluation function works by performing *pattern matching* -on the tree `t`. Intuitively, the meaning of the above definition -should be clear: - -1. it first checks if the tree `t` is a `Sum`, and if it - is, it binds the left sub-tree to a new variable called `l` and - the right sub-tree to a variable called `r`, and then proceeds - with the evaluation of the expression following the arrow; this - expression can (and does) make use of the variables bound by the - pattern appearing on the left of the arrow, i.e., `l` and - `r`, -2. if the first check does not succeed, that is, if the tree is not - a `Sum`, it goes on and checks if `t` is a `Var`; if - it is, it binds the name contained in the `Var` node to a - variable `n` and proceeds with the right-hand expression, -3. if the second check also fails, that is if `t` is neither a - `Sum` nor a `Var`, it checks if it is a `Const`, and - if it is, it binds the value contained in the `Const` node to a - variable `v` and proceeds with the right-hand side, -4. finally, if all checks fail, an exception is raised to signal - the failure of the pattern matching expression; this could happen - here only if more sub-classes of `Tree` were declared. - -We see that the basic idea of pattern matching is to attempt to match -a value to a series of patterns, and as soon as a pattern matches, -extract and name various parts of the value, to finally evaluate some -code which typically makes use of these named parts. - -A seasoned object-oriented programmer might wonder why we did not -define `eval` as a *method* of class `Tree` and its -subclasses. We could have done it actually, since Scala allows method -definitions in case classes just like in normal classes. Deciding -whether to use pattern matching or methods is therefore a matter of -taste, but it also has important implications on extensibility: - -- when using methods, it is easy to add a new kind of node as this - can be done just by defining a sub-class of `Tree` for it; on - the other hand, adding a new operation to manipulate the tree is - tedious, as it requires modifications to all sub-classes of - `Tree`, -- when using pattern matching, the situation is reversed: adding a - new kind of node requires the modification of all functions which do - pattern matching on the tree, to take the new node into account; on - the other hand, adding a new operation is easy, by just defining it - as an independent function. - -To explore pattern matching further, let us define another operation -on arithmetic expressions: symbolic derivation. The reader might -remember the following rules regarding this operation: - -1. the derivative of a sum is the sum of the derivatives, -2. the derivative of some variable `v` is one if `v` is the - variable relative to which the derivation takes place, and zero - otherwise, -3. the derivative of a constant is zero. - -These rules can be translated almost literally into Scala code, to -obtain the following definition: - - def derive(t: Tree, v: String): Tree = t match { - case Sum(l, r) => Sum(derive(l, v), derive(r, v)) - case Var(n) if (v == n) => Const(1) - case _ => Const(0) - } - -This function introduces two new concepts related to pattern matching. -First of all, the `case` expression for variables has a -*guard*, an expression following the `if` keyword. This -guard prevents pattern matching from succeeding unless its expression -is true. Here it is used to make sure that we return the constant `1` -only if the name of the variable being derived is the same as the -derivation variable `v`. The second new feature of pattern -matching used here is the *wildcard*, written `_`, which is -a pattern matching any value, without giving it a name. - -We did not explore the whole power of pattern matching yet, but we -will stop here in order to keep this document short. We still want to -see how the two functions above perform on a real example. For that -purpose, let's write a simple `main` function which performs -several operations on the expression `(x+x)+(7+y)`: it first computes -its value in the environment `{ x -> 5, y -> 7 }`, then -computes its derivative relative to `x` and then `y`. - - def main(args: Array[String]) { - val exp: Tree = Sum(Sum(Var("x"),Var("x")),Sum(Const(7),Var("y"))) - val env: Environment = { case "x" => 5 case "y" => 7 } - println("Expression: " + exp) - println("Evaluation with x=5, y=7: " + eval(exp, env)) - println("Derivative relative to x:\n " + derive(exp, "x")) - println("Derivative relative to y:\n " + derive(exp, "y")) - } - -Executing this program, we get the expected output: - - Expression: Sum(Sum(Var(x),Var(x)),Sum(Const(7),Var(y))) - Evaluation with x=5, y=7: 24 - Derivative relative to x: - Sum(Sum(Const(1),Const(1)),Sum(Const(0),Const(0))) - Derivative relative to y: - Sum(Sum(Const(0),Const(0)),Sum(Const(0),Const(1))) - -By examining the output, we see that the result of the derivative -should be simplified before being presented to the user. Defining a -basic simplification function using pattern matching is an interesting -(but surprisingly tricky) problem, left as an exercise for the reader. - -## Traits - -Apart from inheriting code from a super-class, a Scala class can also -import code from one or several *traits*. - -Maybe the easiest way for a Java programmer to understand what traits -are is to view them as interfaces which can also contain code. In -Scala, when a class inherits from a trait, it implements that trait's -interface, and inherits all the code contained in the trait. - -To see the usefulness of traits, let's look at a classical example: -ordered objects. It is often useful to be able to compare objects of a -given class among themselves, for example to sort them. In Java, -objects which are comparable implement the `Comparable` -interface. In Scala, we can do a bit better than in Java by defining -our equivalent of `Comparable` as a trait, which we will call -`Ord`. - -When comparing objects, six different predicates can be useful: -smaller, smaller or equal, equal, not equal, greater or equal, and -greater. However, defining all of them is fastidious, especially since -four out of these six can be expressed using the remaining two. That -is, given the equal and smaller predicates (for example), one can -express the other ones. In Scala, all these observations can be -nicely captured by the following trait declaration: - - trait Ord { - def < (that: Any): Boolean - def <=(that: Any): Boolean = (this < that) || (this == that) - def > (that: Any): Boolean = !(this <= that) - def >=(that: Any): Boolean = !(this < that) - } - -This definition both creates a new type called `Ord`, which -plays the same role as Java's `Comparable` interface, and -default implementations of three predicates in terms of a fourth, -abstract one. The predicates for equality and inequality do not appear -here since they are by default present in all objects. - -The type `Any` which is used above is the type which is a -super-type of all other types in Scala. It can be seen as a more -general version of Java's `Object` type, since it is also a -super-type of basic types like `Int`, `Float`, etc. - -To make objects of a class comparable, it is therefore sufficient to -define the predicates which test equality and inferiority, and mix in -the `Ord` class above. As an example, let's define a -`Date` class representing dates in the Gregorian calendar. Such -dates are composed of a day, a month and a year, which we will all -represent as integers. We therefore start the definition of the -`Date` class as follows: - - class Date(y: Int, m: Int, d: Int) extends Ord { - def year = y - def month = m - def day = d - override def toString(): String = year + "-" + month + "-" + day - -The important part here is the `extends Ord` declaration which -follows the class name and parameters. It declares that the -`Date` class inherits from the `Ord` trait. - -Then, we redefine the `equals` method, inherited from -`Object`, so that it correctly compares dates by comparing their -individual fields. The default implementation of `equals` is not -usable, because as in Java it compares objects physically. We arrive -at the following definition: - - override def equals(that: Any): Boolean = - that.isInstanceOf[Date] && { - val o = that.asInstanceOf[Date] - o.day == day && o.month == month && o.year == year - } - -This method makes use of the predefined methods `isInstanceOf` -and `asInstanceOf`. The first one, `isInstanceOf`, -corresponds to Java's `instanceof` operator, and returns true -if and only if the object on which it is applied is an instance of the -given type. The second one, `asInstanceOf`, corresponds to -Java's cast operator: if the object is an instance of the given type, -it is viewed as such, otherwise a `ClassCastException` is -thrown. - -Finally, the last method to define is the predicate which tests for -inferiority, as follows. It makes use of another predefined method, -`error`, which throws an exception with the given error message. - - def <(that: Any): Boolean = { - if (!that.isInstanceOf[Date]) - error("cannot compare " + that + " and a Date") - - val o = that.asInstanceOf[Date] - (year < o.year) || - (year == o.year && (month < o.month || - (month == o.month && day < o.day))) - } - -This completes the definition of the `Date` class. Instances of -this class can be seen either as dates or as comparable objects. -Moreover, they all define the six comparison predicates mentioned -above: `equals` and `<` because they appear directly in -the definition of the `Date` class, and the others because they -are inherited from the `Ord` trait. - -Traits are useful in other situations than the one shown here, of -course, but discussing their applications in length is outside the -scope of this document. - -## Genericity - -The last characteristic of Scala we will explore in this tutorial is -genericity. Java programmers should be well aware of the problems -posed by the lack of genericity in their language, a shortcoming which -is addressed in Java 1.5. - -Genericity is the ability to write code parametrized by types. For -example, a programmer writing a library for linked lists faces the -problem of deciding which type to give to the elements of the list. -Since this list is meant to be used in many different contexts, it is -not possible to decide that the type of the elements has to be, say, -`Int`. This would be completely arbitrary and overly -restrictive. - -Java programmers resort to using `Object`, which is the -super-type of all objects. This solution is however far from being -ideal, since it doesn't work for basic types (`int`, -`long`, `float`, etc.) and it implies that a lot of -dynamic type casts have to be inserted by the programmer. - -Scala makes it possible to define generic classes (and methods) to -solve this problem. Let us examine this with an example of the -simplest container class possible: a reference, which can either be -empty or point to an object of some type. - - class Reference[T] { - private var contents: T = _ - def set(value: T) { contents = value } - def get: T = contents - } - -The class `Reference` is parametrized by a type, called `T`, -which is the type of its element. This type is used in the body of the -class as the type of the `contents` variable, the argument of -the `set` method, and the return type of the `get` method. - -The above code sample introduces variables in Scala, which should not -require further explanations. It is however interesting to see that -the initial value given to that variable is `_`, which represents -a default value. This default value is 0 for numeric types, -`false` for the `Boolean` type, `()` for the `Unit` -type and `null` for all object types. - -To use this `Reference` class, one needs to specify which type to use -for the type parameter `T`, that is the type of the element -contained by the cell. For example, to create and use a cell holding -an integer, one could write the following: - - object IntegerReference { - def main(args: Array[String]) { - val cell = new Reference[Int] - cell.set(13) - println("Reference contains the half of " + (cell.get * 2)) - } - } - -As can be seen in that example, it is not necessary to cast the value -returned by the `get` method before using it as an integer. It -is also not possible to store anything but an integer in that -particular cell, since it was declared as holding an integer. - -## Conclusion - -This document gave a quick overview of the Scala language and -presented some basic examples. The interested reader can go on, for example, by -reading the document *Scala By Example*, which -contains much more advanced examples, and consult the *Scala - Language Specification* when needed. diff --git a/tutorials/tour/_posts/2017-02-13-abstract-types.md b/tutorials/tour/_posts/2017-02-13-abstract-types.md deleted file mode 100644 index fb3135494a..0000000000 --- a/tutorials/tour/_posts/2017-02-13-abstract-types.md +++ /dev/null @@ -1,73 +0,0 @@ ---- -layout: tutorial -title: Abstract Types - -discourse: true - -tutorial: scala-tour -categories: tour -num: 23 -next-page: compound-types -previous-page: inner-classes -prerequisite-knowledge: variance, upper-type-bound ---- - -Traits and abstract classes can have an abstract type member. This means that the concrete implementations define the actual type. Here's an example: - -```tut -trait Buffer { - type T - val element: T -} -``` -Here we have defined an abstract `type T`. It is used to describe the type of `element`. We can extend this trait in an abstract class, adding an upper-type-bound to `T` to make it more specific. - -```tut -abstract class SeqBuffer extends Buffer { - type U - type T <: Seq[U] - def length = element.length -} -``` -Notice how we can use yet another abstract `type U` as an upper-type-bound. This `class SeqBuffer` allows us to store only sequences in the buffer by stating that type `T` has to be a subtype of `Seq[U]` for a new abstract type `U`. - -Traits or [classes](classes.html) with abstract type members are often used in combination with anonymous class instantiations. To illustrate this, we now look at a program which deals with a sequence buffer that refers to a list of integers: - -```tut -abstract class IntSeqBuffer extends SeqBuffer { - type U = Int -} - - -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) -``` -Here the factory `newIntSeqBuf` uses an anonymous class implementation of `IntSeqBuf` (i.e. `new IntSeqBuffer`), setting `type T` to a `List[Int]`. - -It is also possible to turn abstract type members into type parameters of classes and vice versa. Here is a version of the code above which only uses type parameters: - -```tut -abstract class Buffer[+T] { - val element: T -} -abstract class SeqBuffer[U, +T <: Seq[U]] extends Buffer[T] { - def length = element.length -} - -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) -``` - -Note that we have to use [variance annotations](variances.html) here (`+T <: Seq[U]`) in order to hide the concrete sequence implementation type of the object returned from method `newIntSeqBuf`. Furthermore, there are cases where it is not possible to replace abstract types with type parameters. diff --git a/tutorials/tour/_posts/2017-02-13-annotations.md b/tutorials/tour/_posts/2017-02-13-annotations.md deleted file mode 100644 index 20cacd5909..0000000000 --- a/tutorials/tour/_posts/2017-02-13-annotations.md +++ /dev/null @@ -1,126 +0,0 @@ ---- -layout: tutorial -title: Annotations - -discourse: true - -tutorial: scala-tour -categories: tour -num: 32 -next-page: default-parameter-values -previous-page: by-name-parameters ---- - -Annotations associate meta-information with definitions. For example, the annotation `@deprecated` before a method causes the compiler to print a warning if the method is used. -``` -object DeprecationDemo extends App { - @deprecated - def hello = "hola" - - hello -} -``` -This will compile but the compiler will print a warning: "there was one deprecation warning". - -An annotation clause applies to the first definition or declaration following it. More than one annotation clause may precede a definition and declaration. The order in which these clauses are given does not matter. - - -## Annotations that ensure correctness of encodings -Certain annotations will actually cause compilation to fail if a condition(s) is not met. For example, the annotation `@tailrec` ensures that a method is [tail-recursive](https://en.wikipedia.org/wiki/Tail_call). Tail-recursion can keep memory requirements constant. Here's how it's used in a method which calculates the factorial: -```tut -import scala.annotation.tailrec - -def factorial(x: Int): Int = { - - @tailrec - def factorialHelper(x: Int, accumulator: Int): Int = { - if (x == 1) accumulator else factorialHelper(x - 1, accumulator * x) - } - factorialHelper(x, 1) -} -``` -The `factorialHelper` method has the `@tailrec` which ensures the method is indeed tail-recursive. If we were to change the implementation of `factorialHelper` to the following, it would fail: -``` -import scala.annotation.tailrec - -def factorial(x: Int): Int = { - @tailrec - def factorialHelper(x: Int): Int = { - if (x == 1) 1 else x * factorialHelper(x - 1) - } - factorialHelper(x) -} -``` -We would get the message "Recursive call not in tail position". - - -## Annotations affecting code generation -Some annotations like `@inline` affect the generated code (i.e. your jar file might have different bytes than if you hadn't used the annotation). Inlining means inserting the code in a method's body at the call site. The resulting bytecode is longer, but hopefully runs faster. Using the annotation `@inline` does not ensure that a method will be inlined, but it will cause the compiler to do it if and only if some heuristics about the size of the generated code are met. - -### Java Annotations ### -When writing Scala code which interoperates with Java, there are a few differences in annotation syntax to note. -**Note:** Make sure you use the `-target:jvm-1.8` option with Java annotations. - -Java has user-defined metadata in the form of [annotations](https://docs.oracle.com/javase/tutorial/java/annotations/). A key feature of annotations is that they rely on specifying name-value pairs to initialize their elements. For instance, if we need an annotation to track the source of some class we might define it as - -``` -@interface Source { - public String URL(); - public String mail(); -} -``` - -And then apply it as follows - -``` -@Source(URL = "http://coders.com/", - mail = "support@coders.com") -public class MyClass extends HisClass ... -``` - -An annotation application in Scala looks like a constructor invocation, for instantiating a Java annotation one has to use named arguments: - -``` -@Source(URL = "http://coders.com/", - mail = "support@coders.com") -class MyScalaClass ... -``` - -This syntax is quite tedious if the annotation contains only one element (without default value) so, by convention, if the name is specified as `value` it can be applied in Java using a constructor-like syntax: - -``` -@interface SourceURL { - public String value(); - public String mail() default ""; -} -``` - -And then apply it as follows - -``` -@SourceURL("http://coders.com/") -public class MyClass extends HisClass ... -``` - -In this case, Scala provides the same possibility - -``` -@SourceURL("http://coders.com/") -class MyScalaClass ... -``` - -The `mail` element was specified with a default value so we need not explicitly provide a value for it. However, if we need to do it we can not mix-and-match the two styles in Java: - -``` -@SourceURL(value = "http://coders.com/", - mail = "support@coders.com") -public class MyClass extends HisClass ... -``` - -Scala provides more flexibility in this respect - -``` -@SourceURL("http://coders.com/", - mail = "support@coders.com") - class MyScalaClass ... -``` diff --git a/tutorials/tour/_posts/2017-02-13-basics.md b/tutorials/tour/_posts/2017-02-13-basics.md deleted file mode 100644 index 95e9c31d9b..0000000000 --- a/tutorials/tour/_posts/2017-02-13-basics.md +++ /dev/null @@ -1,311 +0,0 @@ ---- -layout: tutorial -title: Basics - -discourse: true - -tutorial: scala-tour -categories: tour -num: 2 -next-page: unified-types -previous-page: tour-of-scala ---- - -In this page, we will cover basics of Scala. - -## Trying Scala in the Browser - -You can run Scala in your browser with ScalaFiddle. - -1. Go to [https://scalafiddle.io](https://scalafiddle.io). -2. Paste `println("Hello, world!")` in the left pane. -3. Hit "Run" button. Output appears in the right pane. - -This is an easy, zero-setup way to experiment with pieces of Scala code. - -## Expressions - -Expressions are computable statements. -``` -1 + 1 -``` -You can output results of expressions using `println`. - -```tut -println(1) // 1 -println(1 + 1) // 2 -println("Hello!") // Hello! -println("Hello," + " world!") // Hello, world! -``` - -### Values - -You can name results of expressions with the `val` keyword. - -```tut -val x = 1 + 1 -println(x) // 2 -``` - -Named results, such as `x` here, are called values. Referencing -a value does not re-compute it. - -Values cannot be re-assigned. - -```tut:nofail -val x = 1 + 1 -x = 3 // This does not compile. -``` - -Types of values can be inferred, but you can also explicitly state the type, like this: - -```tut -val x: Int = 1 + 1 -``` - -Notice how the type declaration `Int` comes after the identifier `x`. You also need a `:`. - -### Variables - -Variables are like values, except you can re-assign them. You can define a variable with the `var` keyword. - -```tut -var x = 1 + 1 -x = 3 // This compiles because "x" is declared with the "var" keyword. -println(x * x) // 9 -``` - -As with values, you can explicitly state the type if you want: - -```tut -var x: Int = 1 + 1 -``` - - -## Blocks - -You can combine expressions by surrounding them with `{}`. We call this a block. - -The result of the last expression in the block is the result of the overall block, too. - -```tut -println({ - val x = 1 + 1 - x + 1 -}) // 3 -``` - -## Functions - -Functions are expressions that take parameters. - -You can define an anonymous function (i.e. no name) that returns a given integer plus one: - -```tut -(x: Int) => x + 1 -``` - -On the left of `=>` is a list of parameters. On the right is an expression involving the parameters. - -You can also name functions. - -```tut -val addOne = (x: Int) => x + 1 -println(addOne(1)) // 2 -``` - -Functions may take multiple parameters. - -```tut -val add = (x: Int, y: Int) => x + y -println(add(1, 2)) // 3 -``` - -Or it can take no parameters. - -```tut -val getTheAnswer = () => 42 -println(getTheAnswer()) // 42 -``` - -We will cover functions in depth [later](anonymous-function-syntax.html). - -## Methods - -Methods look and behave very similar to functions, but there are a few key differences between them. - -Methods are defined with the `def` keyword. `def` is followed by a name, parameter lists, a return type, and a body. - -```tut -def add(x: Int, y: Int): Int = x + y -println(add(1, 2)) // 3 -``` - -Notice how the return type is declared _after_ the parameter list and a colon `: Int`. - -Methods can take multiple parameter lists. - -```tut -def addThenMultiply(x: Int, y: Int)(multiplier: Int): Int = (x + y) * multiplier -println(addThenMultiply(1, 2)(3)) // 9 -``` - -Or no parameter lists at all. - -```tut -def name: String = System.getProperty("name") -println("Hello, " + name + "!") -``` - -There are some other differences, but for now, you can think of them as something similar to functions. - -Methods can have multi-line expressions as well. -```tut -def getSquareString(input: Double): String = { - val square = input * input - square.toString -} -``` -The last expression in the body is the method's return value. (Scala does have a `return` keyword, but it's rarely used.) - -## Classes - -You can define classes with the `class` keyword followed by its name and constructor parameters. - -```tut -class Greeter(prefix: String, suffix: String) { - def greet(name: String): Unit = - println(prefix + name + suffix) -} -``` -The return type of the method `greet` is `Unit`, which says there's nothing meaningful to return. It's used similarly to `void` in Java and C. (A difference is that because every Scala expression must have some value, there is actually a singleton value of type Unit, written (). It carries no information.) - -You can make an instance of a class with the `new` keyword. - -```tut -val greeter = new Greeter("Hello, ", "!") -greeter.greet("Scala developer") // Hello, Scala developer! -``` - -We will cover classes in depth [later](classes.html). - -## Case Classes - -Scala has a special type of class called a "case" class. By default, case classes are immutable and compared by value. You can define case classes with the `case class` keywords. - -```tut -case class Point(x: Int, y: Int) -``` - -You can instantiate case classes without `new` keyword. - -```tut -val point = Point(1, 2) -val anotherPoint = Point(1, 2) -val yetAnotherPoint = Point(2, 2) -``` - -And they are compared by value. - -```tut -if (point == anotherPoint) { - println(point + " and " + anotherPoint + " are the same.") -} else { - println(point + " and " + anotherPoint + " are different.") -} -// Point(1,2) and Point(1,2) are the same. - -if (point == yetAnotherPoint) { - println(point + " and " + yetAnotherPoint + " are the same.") -} else { - println(point + " and " + yetAnotherPoint + " are different.") -} -// Point(1,2) and Point(2,2) are different. -``` - -There is a lot more to case classes that we'd like to introduce, and we are convinced you will fall in love with them! We will cover them in depth [later](case-classes.html). - -## Objects - -Objects are single instances of their own definitions. You can think of them as singletons of their own classes. - -You can define objects with the `object` keyword. - -```tut -object IdFactory { - private var counter = 0 - def create(): Int = { - counter += 1 - counter - } -} -``` - -You can access an object by referring to its name. - -```tut -val newId: Int = IdFactory.create() -println(newId) // 1 -val newerId: Int = IdFactory.create() -println(newerId) // 2 -``` - -We will cover objects in depth [later](singleton-objects.html). - -## Traits - -Traits are types containing certain fields and methods. Multiple traits can be combined. - -You can define traits with `trait` keyword. - -```tut -trait Greeter { - def greet(name: String): Unit -} -``` - -Traits can also have default implementations. - -```tut -trait Greeter { - def greet(name: String): Unit = - println("Hello, " + name + "!") -} -``` - -You can extend traits with the `extends` keyword and override an implementation with the `override` keyword. - -```tut -class DefaultGreeter extends Greeter - -class CustomizableGreeter(prefix: String, postfix: String) extends Greeter { - override def greet(name: String): Unit = { - println(prefix + name + postfix) - } -} - -val greeter = new DefaultGreeter() -greeter.greet("Scala developer") // Hello, Scala developer! - -val customGreeter = new CustomizableGreeter("How are you, ", "?") -customGreeter.greet("Scala developer") // How are you, Scala developer? -``` - -Here, `DefaultGreeter` extends only a single trait, but it could extend multiple traits. - -We will cover traits in depth [later](traits.html). - -## Main Method - -The main method is an entry point of a program. The Java Virtual -Machine requires a main method to be named `main` and take one -argument, an array of strings. - -Using an object, you can define a main method as follows: - -```tut -object Main { - def main(args: Array[String]): Unit = - println("Hello, Scala developer!") -} -``` diff --git a/tutorials/tour/_posts/2017-02-13-by-name-parameters.md b/tutorials/tour/_posts/2017-02-13-by-name-parameters.md deleted file mode 100644 index 6372f26458..0000000000 --- a/tutorials/tour/_posts/2017-02-13-by-name-parameters.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -layout: tutorial -title: By-name Parameters - -discourse: true - -tutorial: scala-tour -categories: tour -num: 31 -next-page: annotations -previous-page: operators ---- - -_By-name parameters_ are only evaluated when used. They are in contrast to _by-value parameters_. To make a parameter called by-name, simply prepend `=>` to its type. -```tut -def calculate(input: => Int) = input * 37 -``` -By-name parameters have the the advantage that they are not evaluated if they aren't used in the function body. On the other hand, by-value parameters have the advantage that they are evaluated only once. - -Here's an example of how we could implement a while loop: - -```tut -def whileLoop(condition: => Boolean)(body: => Unit): Unit = - if (condition) { - body - whileLoop(condition)(body) - } - -var i = 2 - -whileLoop (i > 0) { - println(i) - i -= 1 -} // prints 2 1 -``` -The method `whileLoop` uses multiple parameter lists to take a condition and a body of the loop. If the `condition` is true, the `body` is executed and then a recursive call to whileLoop is made. If the `condition` is false, the body is never evaluated because we prepended `=>` to the type of `body`. - -Now when we pass `i > 0` as our `condition` and `println(i); i-= 1` as the `body`, it behaves like the standard while loop in many languages. - -This ability to delay evaluation of a parameter until it is used can help performance if the parameter is computationally intensive to evaluate or a longer-running block of code such as fetching a URL. diff --git a/tutorials/tour/_posts/2017-02-13-case-classes.md b/tutorials/tour/_posts/2017-02-13-case-classes.md deleted file mode 100644 index 3a028bb4fe..0000000000 --- a/tutorials/tour/_posts/2017-02-13-case-classes.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -layout: tutorial -title: Case Classes - -discourse: true - -tutorial: scala-tour -categories: tour -num: 11 -next-page: pattern-matching -previous-page: currying -prerequisite-knowledge: classes, basics, mutability ---- - -Case classes are like regular classes with a few key differences which we will go over. Case classes are good for modeling immutable data. In the next step of the tour, we'll see how they are useful in [pattern matching](pattern-matching.html). - -## Defining a case class -A minimal case class requires the keywords `case class`, an identifier, and a parameter list (which may be empty): -```tut -case class Book(isbn: String) - -val frankenstein = Book("978-0486282114") -``` -Notice how the keyword `new` was not used to instantiate the `Message` case class. This is because case classes have an `apply` method by default which takes care of object construction. - -When you create a case class with parameters, the parameters are public `val`s. -``` -case class Message(sender: String, recipient: String, body: String) -val message1 = Message("guillaume@quebec.ca", "jorge@catalonia.es", "Ça va ?") - -println(message1.sender) // prints guillaume@quebec.ca -message1.sender = "travis@washington.us" // this line does not compile -``` -You can't reassign `message1.sender` because it is a `val` (i.e. immutable). It is possible to use `var`s in case classes but this is discouraged. - -## Comparison -Case classes are compared by structure and not by reference: -``` -case class Message(sender: String, recipient: String, body: String) - -val message2 = Message("jorge@catalonia.es", "guillaume@quebec.ca", "Com va?") -val message3 = Message("jorge@catalonia.es", "guillaume@quebec.ca", "Com va?") -val messagesAreTheSame = message2 == message3 // true -``` -Even though `message2` and `message3` refer to different objects, the value of each object is equal. - -## Copying -You can create a (shallow) copy of an instance of a case class simply by using the `copy` method. You can optionally change the constructor arguments. -``` -case class Message(sender: String, recipient: String, body: String) -val message4 = Message("julien@bretagne.fr", "travis@washington.us", "Me zo o komz gant ma amezeg") -val message5 = message4.copy(sender = message4.recipient, recipient = "claire@bourgogne.fr") -message5.sender // travis@washington.us -message5.recipient // claire@bourgogne.fr -message5.body // "Me zo o komz gant ma amezeg" -``` -The recipient of `message4` is used as the sender of `message5` but the `body` of `message4` was copied directly. diff --git a/tutorials/tour/_posts/2017-02-13-classes.md b/tutorials/tour/_posts/2017-02-13-classes.md deleted file mode 100644 index fd4db7e220..0000000000 --- a/tutorials/tour/_posts/2017-02-13-classes.md +++ /dev/null @@ -1,111 +0,0 @@ ---- -layout: tutorial -title: Classes - -discourse: true - -tutorial: scala-tour -categories: tour -num: 4 -next-page: traits -previous-page: unified-types -topics: classes -prerequisite-knowledge: no-return-keyword, type-declaration-syntax, string-interpolation, procedures ---- - -Classes in Scala are blueprints for creating objects. They can contain methods, -values, variables, types, objects, traits, and classes which are collectively called _members_. Types, objects, and traits will be covered later in the tour. - -## Defining a class -A minimal class definition is simply the keyword `class` and -an identifier. Class names should be capitalized. -```tut -class User - -val user1 = new User -``` -The keyword `new` is used to create an instance of the class. `User` has a default constructor which takes no arguments because no constructor was defined. However, you'll often want a constructor and class body. Here is an example class definition for a 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 = - s"($x, $y)" -} - -val point1 = new Point(2, 3) -point1.x // 2 -println(point1) // prints (x, y) -``` - -This `Point` class has four members: the variables `x` and `y` and the methods `move` and -`toString`. Unlike many other languages, the primary constructor is in the class signature `(var x: Int, var y: Int)`. The `move` method takes two integer arguments and returns the Unit value `()`, which carries no information. This corresponds roughly with `void` in Java-like languages. `toString`, on the other hand, does not take any arguments but returns a `String` value. Since `toString` overrides `toString` from [`AnyRef`](unified-types.html), it is tagged with the `override` keyword. - -## Constructors - -Constructors can have optional parameters by providing a default value like so: - -```tut -class Point(var x: Int = 0, var y: Int = 0) - -val origin = new Point // x and y are both set to 0 -val point1 = new Point(1) -println(point1.x) // prints 1 - -``` - -In this version of the `Point` class, `x` and `y` have the default value `0` so no arguments are required. However, because the constructor reads arguments left to right, if you just wanted to pass in a `y` value, you would need to name the parameter. -``` -class Point(var x: Int = 0, var y: Int = 0) -val point2 = new Point(y=2) -println(point2.y) // prints 2 -``` - -This is also a good practice to enhance clarity. - -## Private Members and Getter/Setter Syntax -Members are public by default. Use the `private` access modifier -to hide them from outside of the class. -```tut -class Point { - private var _x = 0 - private var _y = 0 - private val bound = 100 - - def x = _x - def x_= (newValue: Int): Unit = { - if (newValue < bound) _x = newValue else printWarning - } - - def y = _y - def y_= (newValue: Int): Unit = { - if (newValue < bound) _y = newValue else printWarning - } - - private def printWarning = println("WARNING: Out of bounds") -} - -val point1 = new Point -point1.x = 99 -point1.y = 101 // prints the warning -``` -In this version of the `Point` class, the data is stored in private variables `_x` and `_y`. There are methods `def x` and `def y` for accessing the private data. `def x_=` and `def y_=` are for validating and setting the value of `_x` and `_y`. Notice the special syntax for the setters: the method has `_=` appended to the identifier of the getter and the parameters come after. - -Primary constructor parameters with `val` and `var` are public. However, because `val`s are immutable, you can't write the following. -``` -class Point(val x: Int, val y: Int) -val point = new Point(1, 2) -point.x = 3 // <-- does not compile -``` - -Parameters without `val` or `var` are private values, visible only within the class. -``` -class Point(x: Int, y: Int) -val point = new Point(1, 2) -point.x // <-- does not compile -``` diff --git a/tutorials/tour/_posts/2017-02-13-compound-types.md b/tutorials/tour/_posts/2017-02-13-compound-types.md deleted file mode 100644 index bdf9a559a6..0000000000 --- a/tutorials/tour/_posts/2017-02-13-compound-types.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -layout: tutorial -title: Compound Types - -discourse: true - -tutorial: scala-tour -categories: tour -num: 24 -next-page: self-types -previous-page: abstract-types ---- - -Sometimes it is necessary to express that the type of an object is a subtype of several other types. In Scala this can be expressed with the help of *compound types*, which are intersections of object types. - -Suppose we have two traits `Cloneable` and `Resetable`: - -```tut -trait Cloneable extends java.lang.Cloneable { - override def clone(): Cloneable = { - super.clone().asInstanceOf[Cloneable] - } -} -trait Resetable { - def reset: Unit -} -``` - -Now suppose we want to write a function `cloneAndReset` which takes an object, clones it and resets the original object: - -``` -def cloneAndReset(obj: ?): Cloneable = { - val cloned = obj.clone() - obj.reset - cloned -} -``` - -The question arises what the type of the parameter `obj` is. If it's `Cloneable` then the object can be `clone`d, but not `reset`; if it's `Resetable` we can `reset` it, but there is no `clone` operation. To avoid type casts in such a situation, we can specify the type of `obj` to be both `Cloneable` and `Resetable`. This compound type is written like this in Scala: `Cloneable with Resetable`. - -Here's the updated function: - -``` -def cloneAndReset(obj: Cloneable with Resetable): Cloneable = { - //... -} -``` - -Compound types can consist of several object types and they may have a single refinement which can be used to narrow the signature of existing object members. -The general form is: `A with B with C ... { refinement }` - -An example for the use of refinements is given on the page about [abstract types](abstract-types.html). diff --git a/tutorials/tour/_posts/2017-02-13-currying.md b/tutorials/tour/_posts/2017-02-13-currying.md deleted file mode 100644 index 1471b5ddf6..0000000000 --- a/tutorials/tour/_posts/2017-02-13-currying.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -layout: tutorial -title: Currying - -discourse: true - -tutorial: scala-tour -categories: tour -num: 10 -next-page: case-classes -previous-page: nested-functions ---- - -Methods may define multiple parameter lists. When a method is called with a fewer number of parameter lists, then this will yield a function taking the missing parameter lists as its arguments. - -Here is an example: - -```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))) -} -``` - -_Note: method `modN` is partially applied in the two `filter` calls; i.e. only its first argument is actually applied. The term `modN(2)` yields a function of type `Int => Boolean` and is thus a possible candidate for the second argument of function `filter`._ - -Here's the output of the program above: - -``` -List(2,4,6,8) -List(3,6) -``` diff --git a/tutorials/tour/_posts/2017-02-13-default-parameter-values.md b/tutorials/tour/_posts/2017-02-13-default-parameter-values.md deleted file mode 100644 index 1c6329d083..0000000000 --- a/tutorials/tour/_posts/2017-02-13-default-parameter-values.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -layout: tutorial -title: Default Parameter Values - -discourse: true - -tutorial: scala-tour -categories: tour -num: 33 -next-page: named-arguments -previous-page: annotations -prerequisite-knowledge: named-arguments, function syntax ---- - -Scala provides the ability to give parameters default values that can be used to allow a caller to omit those parameters. - -```tut -def log(message: String, level: String = "INFO") = println(s"$level: $message") - -log("System starting") // prints INFO: System starting -log("User not found", "WARNING") // prints WARNING: User not found -``` - -The parameter `level` has a default value so it is optional. On the last line, the argument `"WARNING"` overrides the default argument `"INFO"`. Where you might do overloaded methods in Java, you can use methods with optional parameters to achieve the same effect. However, if the caller omits an argument, any following arguments must be named. - -```tut -class Point(val x: Double = 0, val y: Double = 0) - -val point1 = new Point(y = 1) -``` -Here we have to say `y = 1`. - -Note that default parameters in Scala are not optional when called from Java code: - -```tut -// Point.scala -class Point(val x: Double = 0, val y: Double = 0) -``` - -```java -// Main.java -public class Main { - public static void main(String[] args) { - Point point = new Point(1); // does not compile - } -} -``` diff --git a/tutorials/tour/_posts/2017-02-13-extractor-objects.md b/tutorials/tour/_posts/2017-02-13-extractor-objects.md deleted file mode 100644 index 598be8ffdc..0000000000 --- a/tutorials/tour/_posts/2017-02-13-extractor-objects.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -layout: tutorial -title: Extractor Objects - -discourse: true - -tutorial: scala-tour -categories: tour -num: 16 -next-page: for-comprehensions -previous-page: regular-expression-patterns ---- - -An extractor object is an object with an `unapply` method. Whereas the `apply` method is like a constructor which takes arguments and creates an object, the `unapply` takes an object and tries to give back the arguments. This is most often used in pattern matching and partial functions. - -```tut -import scala.util.Random - -object CustomerID { - - def apply(name: String) = s"$name--${Random.nextLong}" - - def unapply(customerID: String): Option[String] = { - val name = customerID.split("--").head - if (name.nonEmpty) Some(name) else None - } -} - -val customer1ID = CustomerID("Sukyoung") // Sukyoung--23098234908 -customer1ID match { - case CustomerID(name) => println(name) // prints Sukyoung - case _ => println("Could not extract a CustomerID") -} -``` - -The `apply` method creates a `CustomerID` string from a `name`. The `unapply` does the inverse to get the `name` back. When we call `CustomerID("Sukyoung")`, this is shorthand syntax for calling `CustomerID.apply("Sukyoung")`. When we call `case CustomerID(name) => customer1ID`, we're calling the unapply method. - -The unapply method can also be used to assign a value. - -```tut -val customer2ID = CustomerID("Nico") -val CustomerID(name) = customer2ID -println(name) // prints Nico -``` - -This is equivalent to `val name = CustomerID.unapply(customer2ID).get`. If there is no match, a `scala.MatchError` is thrown: - -```tut:fail -val CustomerID(name2) = "--asdfasdfasdf" -``` - -The return type of an `unapply` should be chosen as follows: - -* If it is just a test, return a `Boolean`. For instance `case even()` -* If it returns a single sub-value of type T, return an `Option[T]` -* If you want to return several sub-values `T1,...,Tn`, group them in an optional tuple `Option[(T1,...,Tn)]`. - -Sometimes, the number of sub-values is fixed and we would like to return a sequence. For this reason, you can also define patterns through `unapplySeq` which returns `Option[Seq[T]]` This mechanism is used for instance in pattern `case List(x1, ..., xn)`. diff --git a/tutorials/tour/_posts/2017-02-13-for-comprehensions.md b/tutorials/tour/_posts/2017-02-13-for-comprehensions.md deleted file mode 100644 index 0483e48d45..0000000000 --- a/tutorials/tour/_posts/2017-02-13-for-comprehensions.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -layout: tutorial -title: For Comprehensions - -discourse: true - -tutorial: scala-tour -categories: tour -num: 17 -next-page: generic-classes -previous-page: extractor-objects ---- - -Scala offers a lightweight notation for expressing *sequence comprehensions*. Comprehensions have the form `for (enumerators) yield e`, where `enumerators` refers to a semicolon-separated list of enumerators. An *enumerator* is either a generator which introduces new variables, or it is a filter. A comprehension evaluates the body `e` for each binding generated by the enumerators and returns a sequence of these values. - -Here's an example: - -```tut -case class User(val name: String, val age: Int) - -val userBase = List(new User("Travis", 28), - new User("Kelly", 33), - new User("Jennifer", 44), - new User("Dennis", 23)) - -val twentySomethings = for (user <- userBase if (user.age >=20 && user.age < 30)) - yield user.name // i.e. add this to a list - -twentySomethings.foreach(name => println(name)) // prints Travis Dennis -``` -The `for` loop used with a `yield` statement actually creates a `List`. Because we said `yield user.name`, it's a `List[String]`. `user <- userBase` is our generator and `if (user.age >=20 && user.age < 30)` is a guard that filters out users who are in their 20s. - -Here is a more complicated example using two generators. It computes all pairs of numbers between `0` and `n-1` whose sum is equal to a given value `v`: - -```tut -def foo(n: Int, v: Int) = - for (i <- 0 until n; - j <- i until n if i + j == v) - yield (i, j) - -foo(10, 10) foreach { - case (i, j) => - print(s"($i, $j) ") // prints (1, 9) (2, 8) (3, 7) (4, 6) (5, 5) -} - -``` -Here `n == 10` and `v == 10`. On the first iteration, `i == 0` and `j == 0` so `i + j != v` and therefore nothing is yielded. `j` gets incremented 9 more times before `i` gets incremented to `1`. Without the `if` guard, this would simply print the following: -``` - -(0, 0) (0, 1) (0, 2) (0, 3) (0, 4) (0, 5) (0, 6) (0, 7) (0, 8) (0, 9) (1, 1) ... -``` diff --git a/tutorials/tour/_posts/2017-02-13-generic-classes.md b/tutorials/tour/_posts/2017-02-13-generic-classes.md deleted file mode 100644 index a9e2faee19..0000000000 --- a/tutorials/tour/_posts/2017-02-13-generic-classes.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -layout: tutorial -title: Generic Classes - -discourse: true - -tutorial: scala-tour -categories: tour -num: 18 -next-page: variances -previous-page: for-comprehensions -assumed-knowledge: classes unified-types ---- -Generic classes are classes which take a type as a parameter. They are particularly useful for collection classes. - -## Defining a generic class -Generic classes take a type as a parameter within square brackets `[]`. One convention is to use the letter `A` as type parameter identifier, though any parameter name may be used. -```tut -class Stack[A] { - private var elements: List[A] = Nil - def push(x: A) { elements = x :: elements } - def peek: A = elements.head - def pop(): A = { - val currentTop = peek - elements = elements.tail - currentTop - } -} -``` -This implementation of a `Stack` class takes any type `A` as a parameter. This means the underlying list, `var elements: List[A] = Nil`, can only store elements of type `A`. The procedure `def push` only accepts objects of type `A` (note: `elements = x :: elements` reassigns `elements` to a new list created by prepending `x` to the current `elements`). - -## Usage - -To use a generic class, put the type in the square brackets in place of `A`. -``` -val stack = new Stack[Int] -stack.push(1) -stack.push(2) -println(stack.pop) // prints 2 -println(stack.pop) // prints 1 -``` -The instance `stack` can only take Ints. However, if the type argument had subtypes, those could be passed in: -``` -class Fruit -class Apple extends Fruit -class Banana extends Fruit - -val stack = new Stack[Fruit] -val apple = new Apple -val banana = new Banana - -stack.push(apple) -stack.push(banana) -``` -Class `Apple` and `Banana` both extend `Fruit` so we can push instances `apple` and `banana` onto the stack of `Fruit`. - -_Note: subtyping of generic types is *invariant*. This means that if we have a stack of characters of type `Stack[Char]` then it cannot be used as an integer stack of type `Stack[Int]`. This would be unsound because it would enable us to enter true integers into the character stack. To conclude, `Stack[A]` is only a subtype of `Stack[B]` if and only if `B = A`. Since this can be quite restrictive, Scala offers a [type parameter annotation mechanism](variances.html) to control the subtyping behavior of generic types._ diff --git a/tutorials/tour/_posts/2017-02-13-higher-order-functions.md b/tutorials/tour/_posts/2017-02-13-higher-order-functions.md deleted file mode 100644 index 81ef2e696c..0000000000 --- a/tutorials/tour/_posts/2017-02-13-higher-order-functions.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -layout: tutorial -title: Higher-order Functions - -discourse: true - -tutorial: scala-tour -categories: tour -num: 8 -next-page: nested-functions -previous-page: mixin-class-composition ---- - -Scala allows the definition of higher-order functions. These are functions that _take other functions as parameters_, or whose _result is a function_. Here is a function `apply` which takes another function `f` and a value `v` and applies function `f` to `v`: - -```tut -def apply(f: Int => String, v: Int) = f(v) -``` - -_Note: methods are automatically coerced to functions if the context requires this._ - -Here is another example: - -```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)) -} -``` - -Execution yields the output: - -``` -[7] -``` - -In this example, the method `decorator.layout` is coerced automatically to a value of type `Int => String` as required by method `apply`. Please note that method `decorator.layout` is a _polymorphic method_ (i.e. it abstracts over some of its signature types) and the Scala compiler has to instantiate its method type first appropriately. diff --git a/tutorials/tour/_posts/2017-02-13-implicit-conversions.md b/tutorials/tour/_posts/2017-02-13-implicit-conversions.md deleted file mode 100644 index 0f5f634ec9..0000000000 --- a/tutorials/tour/_posts/2017-02-13-implicit-conversions.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -layout: tutorial -title: Implicit Conversions - -discourse: true - -tutorial: scala-tour -categories: tour -num: 27 -next-page: polymorphic-methods -previous-page: implicit-parameters ---- - -An implicit conversion from type `S` to type `T` is defined by an implicit value which has function type `S => T`, or by an implicit method convertible to a value of that type. - -Implicit conversions are applied in two situations: - -* If an expression `e` is of type `S`, and `S` does not conform to the expression's expected type `T`. -* In a selection `e.m` with `e` of type `S`, if the selector `m` does not denote a member of `S`. - -In the first case, a conversion `c` is searched for which is applicable to `e` and whose result type conforms to `T`. -In the second case, a conversion `c` is searched for which is applicable to `e` and whose result contains a member named `m`. - -The following operation on the two lists xs and ys of type `List[Int]` is legal: - -``` -xs <= ys -``` - -assuming the implicit methods `list2ordered` and `int2ordered` defined below are in scope: - -``` -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] { /* .. */ } -``` - -The implicitly imported object `scala.Predef` declares several predefined types (e.g. `Pair`) and methods (e.g. `assert`) but also several implicit conversions. - -For example, when calling a Java method that expects a `java.lang.Integer`, you are free to pass it a `scala.Int` instead. That's because Predef includes the following implicit conversions: - -```tut -import scala.language.implicitConversions - -implicit def int2Integer(x: Int) = - java.lang.Integer.valueOf(x) -``` - -Because implicit conversions can have pitfalls if used indiscriminately the compiler warns when compiling the implicit conversion definition. - -To turn off the warnings take either of these actions: - -* Import `scala.language.implicitConversions` into the scope of the implicit conversion definition -* Invoke the compiler with `-language:implicitConversions` - -No warning is emitted when the conversion is applied by the compiler. diff --git a/tutorials/tour/_posts/2017-02-13-implicit-parameters.md b/tutorials/tour/_posts/2017-02-13-implicit-parameters.md deleted file mode 100644 index 70d69a63b1..0000000000 --- a/tutorials/tour/_posts/2017-02-13-implicit-parameters.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -layout: tutorial -title: Implicit Parameters - -discourse: true - -tutorial: scala-tour -categories: tour -num: 26 -next-page: implicit-conversions -previous-page: self-types ---- - -A method with _implicit parameters_ can be applied to arguments just like a normal method. In this case the implicit label has no effect. However, if such a method misses arguments for its implicit parameters, such arguments will be automatically provided. - -The actual arguments that are eligible to be passed to an implicit parameter fall into two categories: - -* First, eligible are all identifiers x that can be accessed at the point of the method call without a prefix and that denote an implicit definition or an implicit parameter. -* Second, eligible are also all members of companion modules of the implicit parameter's type that are labeled implicit. - -In the following example we define a method `sum` which computes the sum of a list of elements using the monoid's `add` and `unit` operations. Please note that implicit values can not be top-level, they have to be members of a template. - -```tut -/** This example uses a structure from abstract algebra to show how implicit parameters work. A semigroup is an algebraic structure on a set A with an (associative) operation, called add here, that combines a pair of A's and returns another A. */ -abstract class SemiGroup[A] { - def add(x: A, y: A): A -} -/** A monoid is a semigroup with a distinguished element of A, called unit, that when combined with any other element of A returns that other element again. */ -abstract class Monoid[A] extends SemiGroup[A] { - def unit: A -} -object ImplicitTest extends App { - /** To show how implicit parameters work, we first define monoids for strings and integers. The implicit keyword indicates that the corresponding object can be used implicitly, within this scope, as a parameter of a function marked implicit. */ - 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 - } - /** This method takes a List[A] returns an A which represent the combined value of applying the monoid operation successively across the whole list. Making the parameter m implicit here means we only have to provide the xs parameter at the call site, since if we have a List[A] we know what type A actually is and therefore what type Monoid[A] is needed. We can then implicitly find whichever val or object in the current scope also has that type and use that without needing to specify it explicitly. */ - def sum[A](xs: List[A])(implicit m: Monoid[A]): A = - if (xs.isEmpty) m.unit - else m.add(xs.head, sum(xs.tail)) - - /** Here we call sum twice, with only one parameter each time. Since the second parameter of sum, m, is implicit its value is looked up in the current scope, based on the type of monoid required in each case, meaning both expressions can be fully evaluated. */ - println(sum(List(1, 2, 3))) // uses IntMonoid implicitly - println(sum(List("a", "b", "c"))) // uses StringMonoid implicitly -} -``` - -Here is the output of the Scala program: - -``` -6 -abc -``` diff --git a/tutorials/tour/_posts/2017-02-13-inner-classes.md b/tutorials/tour/_posts/2017-02-13-inner-classes.md deleted file mode 100644 index fbdfef3b0a..0000000000 --- a/tutorials/tour/_posts/2017-02-13-inner-classes.md +++ /dev/null @@ -1,81 +0,0 @@ ---- -layout: tutorial -title: Inner Classes - -discourse: true - -tutorial: scala-tour -categories: tour -num: 22 -next-page: abstract-types -previous-page: lower-type-bounds ---- - -In Scala it is possible to let classes have other classes as members. As opposed to Java-like languages where such inner classes are members of the enclosing class, in Scala such inner classes are bound to the outer object. Suppose we want the compiler to prevent us, at compile time, from mixing up which nodes belong to what graph. Path-dependent types provide a solution. - -To illustrate the difference, we quickly sketch the implementation of a graph datatype: - -```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 - } -} -``` -This program represents a graph as a list of nodes (`List[Node]`). Each node has a list of other nodes it's connected to (`connectedNodes`). The `class Node` is a _path-dependent type_ because it is nested in the `class Graph`. Therefore, all nodes in the `connectedNodes` must be created using the `newNode` from the same instance of `Graph`. - -```tut -val graph1: Graph = new Graph -val node1: graph1.Node = graph1.newNode -val node2: graph1.Node = graph1.newNode -val node3: graph1.Node = graph1.newNode -node1.connectTo(node2) -node3.connectTo(node1) -``` -We have explicitly declared the type of `node1`, `node2`, and `node3` as `graph1.Node` for clarity but the compiler could have inferred it. This is because when we call `graph1.newNode` which calls `new Node`, the method is using the instance of `Node` specific to the instance `graph1`. - -If we now have two graphs, the type system of Scala does not allow us to mix nodes defined within one graph with the nodes of another graph, since the nodes of the other graph have a different type. -Here is an illegal program: - -``` -val graph1: Graph = new Graph -val node1: graph1.Node = graph1.newNode -val node2: graph1.Node = graph1.newNode -node1.connectTo(node2) // legal -val graph2: Graph = new Graph -val node3: graph2.Node = graph2.newNode -node1.connectTo(node3) // illegal! -``` -The type `graph1.Node` is distinct from the type `graph2.Node`. In Java, the last line in the previous example program would have been correct. For nodes of both graphs, Java would assign the same type `Graph.Node`; i.e. `Node` is prefixed with class `Graph`. In Scala such a type can be expressed as well, it is written `Graph#Node`. If we want to be able to connect nodes of different graphs, we have to change the definition of our initial graph implementation in the following way: - -```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 - } -} -``` - -> Note that this program doesn't allow us to attach a node to two different graphs. If we want to remove this restriction as well, we have to change the type of variable nodes to `Graph#Node`. diff --git a/tutorials/tour/_posts/2017-02-13-local-type-inference.md b/tutorials/tour/_posts/2017-02-13-local-type-inference.md deleted file mode 100644 index 62746c5a58..0000000000 --- a/tutorials/tour/_posts/2017-02-13-local-type-inference.md +++ /dev/null @@ -1,63 +0,0 @@ ---- -layout: tutorial -title: Local Type Inference - -discourse: true - -tutorial: scala-tour -categories: tour -num: 29 -next-page: operators -previous-page: polymorphic-methods ---- -Scala has a built-in type inference mechanism which allows the programmer to omit certain type annotations. It is, for instance, often not necessary in Scala to specify the type of a variable, since the compiler can deduce the type from the initialization expression of the variable. Also return types of methods can often be omitted since they correspond to the type of the body, which gets inferred by the compiler. - -Here is an example: - -```tut -object InferenceTest1 extends App { - val x = 1 + 2 * 3 // the type of x is Int - val y = x.toString() // the type of y is String - def succ(x: Int) = x + 1 // method succ returns Int values -} -``` - -For recursive methods, the compiler is not able to infer a result type. Here is a program which will fail the compiler for this reason: - -```tut:fail -object InferenceTest2 { - def fac(n: Int) = if (n == 0) 1 else n * fac(n - 1) -} -``` - -It is also not compulsory to specify type parameters when [polymorphic methods](polymorphic-methods.html) are called or [generic classes](generic-classes.html) are instantiated. The Scala compiler will infer such missing type parameters from the context and from the types of the actual method/constructor parameters. - -Here is an example which illustrates this: - -``` -case class MyPair[A, B](x: A, y: B); -object InferenceTest3 extends App { - def id[T](x: T) = x - val p = MyPair(1, "scala") // type: MyPair[Int, String] - val q = id(1) // type: Int -} -``` - -The last two lines of this program are equivalent to the following code where all inferred types are made explicit: - -``` -val x: MyPair[Int, String] = MyPair[Int, String](1, "scala") -val y: Int = id[Int](1) -``` - -In some situations it can be quite dangerous to rely on Scala's type inference mechanism as the following program shows: - -```tut:fail -object InferenceTest4 { - var obj = null - obj = new Object() -} -``` - -This program does not compile because the type inferred for variable `obj` is `Null`. Since the only value of that type is `null`, it is impossible to make this variable refer to another value. - diff --git a/tutorials/tour/_posts/2017-02-13-lower-type-bounds.md b/tutorials/tour/_posts/2017-02-13-lower-type-bounds.md deleted file mode 100644 index 350615a22e..0000000000 --- a/tutorials/tour/_posts/2017-02-13-lower-type-bounds.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -layout: tutorial -title: Lower Type Bounds - -discourse: true - -tutorial: scala-tour -categories: tour -num: 21 -next-page: inner-classes -previous-page: upper-type-bounds -prerequisite-knowledge: upper-type-bounds, generics, variance ---- - -While [upper type bounds](upper-type-bounds.html) limit a type to a subtype of another type, *lower type bounds* declare a type to be a supertype of another type. The term `B >: A` expresses that the type parameter `B` or the abstract type `B` refer to a supertype of type `A`. In most cases, `A` will be the type parameter of the class and `B` will be the type parameter of a method. - -Here is an example where this is useful: - -```tut:fail -trait Node[+B] { - def prepend(elem: B): Unit -} - -case class ListNode[+B](h: B, t: Node[B]) extends Node[B] { - def prepend(elem: B) = ListNode[B](elem, this) - def head: B = h - def tail = t -} - -case class Nil[+B]() extends Node[B] { - def prepend(elem: B) = ListNode[B](elem, this) -} -``` -This program implements a singly-linked list. `Nil` represents an empty element (i.e. an empty list). `class ListNode` is a node which contains an element of type `B` (`head`) and a reference to the rest of the list (`tail`). The `class Node` and its subtypes are covariant because we have `+B`. - -However, this program does _not_ compile because the parameter `elem` in `prepend` is of type `B`, which we declared *co*variant. This doesn't work because functions are *contra*variant in their parameter types and *co*variant in their result types. - -To fix this, we need to flip the variance of the type of the parameter `elem` in `prepend`. We do this by introducing a new type parameter `U` that has `B` as a lower type bound. - -```tut -trait Node[+B] { - def prepend[U >: B](elem: U) -} - -case class ListNode[+B](h: B, t: Node[B]) extends Node[B] { - def prepend[U >: B](elem: U) = ListNode[U](elem, this) - def head: B = h - def tail = t -} - -case class Nil[+B]() extends Node[B] { - def prepend[U >: B](elem: U) = ListNode[U](elem, this) -} -``` - -Now we can do the following: -```tut -trait Mammal -case class AfricanSwallow() extends Mammal -case class EuropeanSwallow() extends Mammal - - -val africanSwallowList= ListNode[AfricanSwallow](AfricanSwallow(), Nil()) -val mammalList: Node[Mammal] = africanSwallowList -mammalList.prepend(new EuropeanSwallow) -``` -The `Node[Mammal]` can be assigned the `africanSwallowList` but then accept `EuropeanSwallow`s. diff --git a/tutorials/tour/_posts/2017-02-13-mixin-class-composition.md b/tutorials/tour/_posts/2017-02-13-mixin-class-composition.md deleted file mode 100644 index d56763f566..0000000000 --- a/tutorials/tour/_posts/2017-02-13-mixin-class-composition.md +++ /dev/null @@ -1,81 +0,0 @@ ---- -layout: tutorial -title: Class Composition with Mixins - -discourse: true - -tutorial: scala-tour -categories: tour -num: 6 -next-page: higher-order-functions -previous-page: traits -prerequisite-knowledge: inheritance, traits, abstract-classes, unified-types ---- -Mixins are traits which are used to compose a class. - -```tut -abstract class A { - val message: String -} -class B extends A { - val message = "I'm an instance of class B" -} -trait C extends A { - def loudMessage = message.toUpperCase() -} -class D extends B with C - -val d = new D -d.message // I'm an instance of class B -d.loudMessage // I'M AN INSTANCE OF CLASS B -``` -Class `D` has a superclass `B` and a mixin `C`. Classes can only have one superclass but many mixins (using the keywords `extends` and `with` respectively). The mixins and the superclass may have the same supertype. - -Now let's look at a more interesting example starting with an abstract class: - -```tut -abstract class AbsIterator { - type T - def hasNext: Boolean - def next: T -} -``` -The class has an abstract type `T` and the standard iterator methods. - -Next, we'll implement a concrete class (all abstract members `T`, `hasNext`, and `next` have implementations): - -```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 - } -} -``` -`StringIterator` takes a `String` and can be used to iterate over the String (e.g. to see if a String contains a certain character). - -Now let's create a trait which also extends `AbsIterator`. - -```tut -trait RichIterator extends AbsIterator { - def foreach(f: T => Unit): Unit = while (hasNext) f(next) -} -``` -Because `RichIterator` is a trait, it doesn't need to implement the abstract members of AbsIterator. - -We would like to combine the functionality of `StringIterator` and `RichIterator` into a single class. - -```tut -object StringIteratorTest extends App { - class RichStringIter extends StringIterator(args(0)) with RichIterator - val iter = new RichStringIter - iter foreach println -} -``` -The new class `RichStringIter` has `StringIterator` as a superclass and `RichIterator` as a mixin. - -With single inheritance we would not be able to achieve this level of flexibility. diff --git a/tutorials/tour/_posts/2017-02-13-named-arguments.md b/tutorials/tour/_posts/2017-02-13-named-arguments.md deleted file mode 100644 index fcb22241f5..0000000000 --- a/tutorials/tour/_posts/2017-02-13-named-arguments.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -layout: tutorial -title: Named Arguments - -discourse: true - -tutorial: scala-tour -categories: tour -num: 34 -previous-page: default-parameter-values -prerequisite-knowledge: function-syntax ---- - -When calling methods, you can label the arguments with their parameter names like so: - -```tut - def printName(first: String, last: String): Unit = { - println(first + " " + last) - } - - printName("John", "Smith") // Prints "John Smith" - printName(first = "John", last = "Smith") // Prints "John Smith" - printName(last = "Smith", first = "John") // Prints "John Smith" -``` -Notice how the order of named arguments can be rearranged. However, if some arguments are named and others are not, the unnamed arguments must come first and in the order of their parameters in the method signature. - -``` -def printName(first: String, last: String): Unit = { - println(first + " " + last) -} - -printName(last = "Smith", "john") // Does not compile -``` - -Note that named arguments do not work with calls to Java methods. diff --git a/tutorials/tour/_posts/2017-02-13-nested-functions.md b/tutorials/tour/_posts/2017-02-13-nested-functions.md deleted file mode 100644 index b1fe699612..0000000000 --- a/tutorials/tour/_posts/2017-02-13-nested-functions.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -layout: tutorial -title: Nested Methods - -discourse: true - -tutorial: scala-tour -categories: tour -num: 9 -next-page: currying -previous-page: higher-order-functions ---- - -In Scala it is possible to nest function definitions. The following object provides a `factorial` function for computing the factorial of a given number: - -```tut - def factorial(x: Int): Int = { - def fact(x: Int, accumulator: Int): Int = { - if (x <= 1) accumulator - else fact(x - 1, x * accumulator) - } - fact(x, 1) - } - - println("Factorial of 2: " + factorial(2)) - println("Factorial of 3: " + factorial(3)) -``` - -The output of this program is: - -``` -Factorial of 2: 2 -Factorial of 3: 6 -``` diff --git a/tutorials/tour/_posts/2017-02-13-operators.md b/tutorials/tour/_posts/2017-02-13-operators.md deleted file mode 100644 index b1b401267a..0000000000 --- a/tutorials/tour/_posts/2017-02-13-operators.md +++ /dev/null @@ -1,79 +0,0 @@ ---- -layout: tutorial -title: Operators - -discourse: true - -tutorial: scala-tour -categories: tour -num: 30 -next-page: by-name-parameters -previous-page: local-type-inference -prerequisite-knowledge: case-classes ---- -In Scala, operators are methods. Any method with a single parameter can be used as an _infix operator_. For example, `+` can be called with dot-notation: -``` -10.+(1) -``` - -However, it's easier to read as an infix operator: -``` -10 + 1 -``` - -## Defining and using operators -You can use any legal identifier as an operator. This includes a name like `add` or a symbol(s) like `+`. -```tut -case class Vec(val x: Double, val y: Double) { - def +(that: Vec) = new Vec(this.x + that.x, this.y + that.y) -} - -val vector1 = Vec(1.0, 1.0) -val vector2 = Vec(2.0, 2.0) - -val vector3 = vector1 + vector2 -vector3.x // 3.0 -vector3.y // 3.0 -``` -The class Vec has a method `+` which we used to add `vector1` and `vector2`. Using parentheses, you can build up complex expressions with readable syntax. Here is the definition of class `MyBool` which includes methods `and` and `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) -} -``` - -It is now possible to use `and` and `or` as infix operators: - -```tut -def not(x: MyBool) = x.negate -def xor(x: MyBool, y: MyBool) = (x or y) and not(x and y) -``` - -This helps to make the definition of `xor` more readable. - -## Precedence -When an expression uses multiple operators, the operators are evaluated based on the priority of the first character: -``` -(characters not shown below) -* / % -+ - -: -= ! -< > -& -^ -| -(all letters) -``` -This applies to functions you define. For example, the following expression: -``` -a + b ^? c ?^ d less a ==> b | c -``` -Is equivalent to -``` -((a + b) ^? (c ?^ d)) less ((a ==> b) | c) -``` -`?^` has the highest precedence because it starts with the character `?`. `+` has the second highest precedence, followed by `^?`, `==>`, `|`, and `less`. diff --git a/tutorials/tour/_posts/2017-02-13-pattern-matching.md b/tutorials/tour/_posts/2017-02-13-pattern-matching.md deleted file mode 100644 index 2ee4f705ef..0000000000 --- a/tutorials/tour/_posts/2017-02-13-pattern-matching.md +++ /dev/null @@ -1,149 +0,0 @@ ---- -layout: tutorial -title: Pattern Matching - -discourse: true - -tutorial: scala-tour -categories: tour -num: 12 - -next-page: singleton-objects -previous-page: case-classes -prerequisite-knowledge: case-classes, string-interpolation, subtyping ---- - -Pattern matching is a mechanism for checking a value against a pattern. A successful match can also deconstruct a value into its constituent parts. It is a more powerful version of the `switch` statement in Java and it can likewise be used in place of a series of if/else statements. - -## Syntax -A match expression has a value, the `match` keyword, and at least one `case` clause. -```tut -import scala.util.Random - -val x: Int = Random.nextInt(10) - -x match { - case 0 => "zero" - case 1 => "one" - case 2 => "two" - case _ => "many" -} -``` -The `val x` above is a random integer between 0 and 10. `x` becomes the left operand of the `match` operator and on the right is an expression with four cases. The last case `_` is a "catch all" case for any number greater than 2. Cases are also called _alternatives_. - -Match expressions have a value. -```tut -def matchTest(x: Int): String = x match { - case 1 => "one" - case 2 => "two" - case _ => "many" -} -matchTest(3) // many -matchTest(1) // one -``` -This match expression has a type String because all of the cases return String. Therefore, the function `matchTest` returns a String. - -## Matching on case classes - -Case classes are especially useful for pattern matching. - -```tut -abstract class Notification - -case class Email(sender: String, title: String, body: String) extends Notification - -case class SMS(caller: String, message: String) extends Notification - -case class VoiceRecording(contactName: String, link: String) extends Notification - - -``` -`Notification` is an abstract super class which has three concrete Notification types implemented with case classes `Email`, `SMS`, and `VoiceRecording`. Now we can do pattern matching on these case classes: - -``` -def showNotification(notification: Notification): String = { - notification match { - case Email(email, title, _) => - s"You got an email from $email with title: $title" - case SMS(number, message) => - s"You got an SMS from $number! Message: $message" - case VoiceRecording(name, link) => - s"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)) // prints You got an SMS from 12345! Message: Are you there? - -println(showNotification(someVoiceRecording)) // you received a Voice Recording from Tom! Click the link to hear it: voicerecording.org/id/123 -``` -The function `showNotification` takes as a parameter the abstract type `Notification` and matches on the type of `Notification` (i.e. it figures out whether it's an `Email`, `SMS`, or `VoiceRecording`). In the `case Email(email, title, _)` the fields `email` and `title` are used in the return value but the `body` field is ignored with `_`. - -## Pattern guards -Pattern guards are simply boolean expressions which are used to make cases more specific. Just add `if ` after the pattern. -``` - -def showImportantNotification(notification: Notification, importantPeopleInfo: Seq[String]): String = { - notification match { - case Email(email, _, _) if importantPeopleInfo.contains(email) => - "You got an email from special someone!" - case SMS(number, _) if importantPeopleInfo.contains(number) => - "You got an SMS from special someone!" - case other => - showNotification(other) // nothing special, delegate to our original showNotification function - } -} - -val importantPeopleInfo = Seq("867-5309", "jenny@gmail.com") - -val someSms = SMS("867-5309", "Are you there?") -val someVoiceRecording = VoiceRecording("Tom", "voicerecording.org/id/123") -val importantEmail = Email("jenny@gmail.com", "Drinks tonight?", "I'm free after 5!") -val importantSms = SMS("867-5309", "I'm here! Where are you?") - -println(showImportantNotification(someSms, importantPeopleInfo)) -println(showImportantNotification(someVoiceRecording, importantPeopleInfo)) -println(showImportantNotification(importantEmail, importantPeopleInfo)) -println(showImportantNotification(importantSms, importantPeopleInfo)) -``` - -In the `case Email(email, _, _) if importantPeopleInfo.contains(email)`, the pattern is matched only if the `email` is in the list of important people. - -## Matching on type only -You can match on the type like so: -```tut -abstract class Device -case class Phone(model: String) extends Device { - def screenOff = "Turning screen off" -} -case class Computer(model: String) extends Device { - def screenSaverOn = "Turning screen saver on..." -} - -def goIdle(device: Device) = device match { - case p: Phone => p.screenOff - case c: Computer => c.screenSaverOn -} -``` -`def goIdle` has a different behavior depending on the type of `Device`. This is useful when the case needs to call a method on the pattern. It is a convention to use the first letter of the type as the case identifier (`p` and `c` in this case). - -## Sealed classes -Traits and classes can be marked `sealed` which means all subtypes must be declared in the same file. The assures that all subtypes are known. - -```tut -sealed abstract class Furniture -case class Couch() extends Furniture -case class Chair() extends Furniture - -def findPlaceToSit(piece: Furniture): String = piece match { - case a: Couch => "Lie on the couch" - case b: Chair => "Sit on the chair" -} -``` -This is useful for pattern matching because we don't need a "catch all" case. - -## Notes - -Scala's pattern matching statement is most useful for matching on algebraic types expressed via [case classes](case-classes.html). -Scala also allows the definition of patterns independently of case classes, using `unapply` methods in [extractor objects](extractor-objects.html). diff --git a/tutorials/tour/_posts/2017-02-13-polymorphic-methods.md b/tutorials/tour/_posts/2017-02-13-polymorphic-methods.md deleted file mode 100644 index 4aecf5e43d..0000000000 --- a/tutorials/tour/_posts/2017-02-13-polymorphic-methods.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -layout: tutorial -title: Polymorphic Methods - -discourse: true - -tutorial: scala-tour -categories: tour -section: -num: 28 - -next-page: local-type-inference -previous-page: implicit-conversions -prerequisite-knowledge: unified-types, ---- - -Methods in Scala can be parameterized by type as well as value. The syntax is similar to that of generic classes. Type parameters are declared within a pair of brackets while value parameters are enclosed in a pair of parentheses. - -Here is an example: - -```tut -def listOfDuplicates[A](x: A, length: Int): List[A] = { - if (length < 1) - Nil - else - x :: listOfDuplicates(x, length - 1) -} -println(listOfDuplicates[Int](3, 4)) // List(3, 3, 3, 3) -println(listOfDuplicates("La", 8)) // List(La, La, La, La, La, La, La, La) -``` - -The method `listOfDuplicates` takes a type parameter `A` and values parameters `x` and `length`. In this case, value `x` is of type `A`. If `length < 1` we return an empty list. Otherwise we prepend `x` to the the list of duplicates returned by the recursive call to `listOfDuplicates`. (note: `::` means prepend an element on the left to a sequence on the right). - -When we call `listOfDuplicates` with `[Int]` as the type parameter, the first argument must be an int and the return type will be List[Int]. However, you don't always need to explicitly provide the the type parameter because the compiler can often figure it out based on the type of value argument (`"La"` is a String). In fact, if calling this method from Java it is impossible to provide the type parameter. diff --git a/tutorials/tour/_posts/2017-02-13-regular-expression-patterns.md b/tutorials/tour/_posts/2017-02-13-regular-expression-patterns.md deleted file mode 100644 index d2b94ec845..0000000000 --- a/tutorials/tour/_posts/2017-02-13-regular-expression-patterns.md +++ /dev/null @@ -1,61 +0,0 @@ ---- -layout: tutorial -title: Regular Expression Patterns - -discourse: true - -tutorial: scala-tour -categories: tour -num: 15 - -next-page: extractor-objects -previous-page: singleton-objects ---- - -Regular expressions are strings which can be used to find patterns (or lack thereof) in data. Any string can be converted to a regular expression using the `.r` method. - -```tut -import scala.util.matching.Regex - -val numberPattern: Regex = "[0-9]".r - -numberPattern.findFirstMatchIn("awesomepassword") match { - case Some(_) => println("Password OK") - case None => println("Password must contain a number") -} -``` - -In the above example, the `numberPattern` is a `Regex` -(regular expression) which we use to make sure a password contains a number. - -You can also search for groups of regular expressions using parentheses. - -```tut -import scala.util.matching.Regex - -val keyValPattern: Regex = "([0-9a-zA-Z-#() ]+): ([0-9a-zA-Z-#() ]+)".r - -val input: String = - """background-color: #A03300; - |background-image: url(img/header100.png); - |background-position: top center; - |background-repeat: repeat-x; - |background-size: 2160px 108px; - |margin: 0; - |height: 108px; - |width: 100%;""".stripMargin - -for (patternMatch <- keyValPattern.findAllMatchIn(input)) - println(s"key: ${patternMatch.group(1)} value: ${patternMatch.group(2)}") -``` -Here we parse out the keys and values of a String. Each match has a group of sub-matches. Here is the output: -``` -key: background-color value: #A03300 -key: background-image value: url(img -key: background-position value: top center -key: background-repeat value: repeat-x -key: background-size value: 2160px 108px -key: margin value: 0 -key: height value: 108px -key: width value: 100 -``` diff --git a/tutorials/tour/_posts/2017-02-13-self-types.md b/tutorials/tour/_posts/2017-02-13-self-types.md deleted file mode 100644 index 549fbdc396..0000000000 --- a/tutorials/tour/_posts/2017-02-13-self-types.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -layout: tutorial -title: Self-type - -discourse: true - -tutorial: scala-tour -categories: tour -num: 25 -next-page: implicit-parameters -previous-page: compound-types -prerequisite-knowledge: nested-classes, mixin-class-composition ---- -Self-types are a way to declare that a trait must be mixed into another trait, even though it doesn't directly extend it. That makes the members of the dependency available without imports. - -A self-type is a way to narrow the type of `this` or another identifier that aliases `this`. The syntax looks like normal function syntax but means something entirely different. - -To use a self-type in a trait, write an identifier, the type of another trait to mix in, and a `=>` (e.g. `someIdentifier: SomeOtherTrait =>`). -```tut -trait User { - def username: String -} - -trait Tweeter { - this: User => // reassign this - def tweet(tweetText: String) = println(s"$username: $tweetText") -} - -class VerifiedTweeter(val username_ : String) extends Tweeter with User { // We mixin User because Tweeter required it - def username = s"real $username_" -} - -val realBeyoncé = new VerifiedTweeter("Beyoncé") -realBeyoncé.tweet("Just spilled my glass of lemonade") // prints "real Beyoncé: Just spilled my glass of lemonade" -``` - -Because we said `this: User =>` in `trait Tweeter`, now the variable `username` is in scope for the `tweet` method. This also means that since `VerifiedTweeter` extends `Tweeter`, it must also mix-in `User` (using `with User`). diff --git a/tutorials/tour/_posts/2017-02-13-singleton-objects.md b/tutorials/tour/_posts/2017-02-13-singleton-objects.md deleted file mode 100644 index 11c0326063..0000000000 --- a/tutorials/tour/_posts/2017-02-13-singleton-objects.md +++ /dev/null @@ -1,74 +0,0 @@ ---- -layout: tutorial -title: Singleton Objects - -discourse: true - -tutorial: scala-tour -categories: tour -num: 13 - -next-page: regular-expression-patterns -previous-page: pattern-matching ---- - -Methods and values that aren't associated with individual instances of a [class](classes.html) belong in *singleton objects*, denoted by using the keyword `object` instead of `class`. - -``` -package test - -object Blah { - def sum(l: List[Int]): Int = l.sum -} -``` - -This `sum` method is available globally, and can be referred to, or imported, as `test.Blah.sum`. - -Singleton objects are sort of a shorthand for defining a single-use class, which can't directly be instantiated, and a `val` member at the point of definition of the `object`, with the same name. Indeed, like `val`s, singleton objects can be defined as members of a [trait](traits.html) or class, though this is atypical. - -A singleton object can extend classes and traits. In fact, a [case class](case-classes.html) with no [type parameters](generic-classes.html) will by default create a singleton object of the same name, with a [`Function*`](http://www.scala-lang.org/api/current/scala/Function1.html) trait implemented. - -## Companions ## - -Most singleton objects do not stand alone, but instead are associated with a class of the same name. The “singleton object of the same name” of a case class, mentioned above, is an example of this. When this happens, the singleton object is called the *companion object* of the class, and the class is called the *companion class* of the object. - -[Scaladoc](https://wiki.scala-lang.org/display/SW/Introduction) has special support for jumping between a class and its companion: if the big “C” or “O” circle has its edge folded up at the bottom, you can click the circle to jump to the companion. - -A class and its companion object, if any, must be defined in the same source file. Like this: - -```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)) -} -``` - -It's common to see typeclass instances as [implicit values](implicit-parameters.html), such as `ipord` above, defined in the companion, when following the typeclass pattern. This is because the companion's members are included in the default implicit search for related values. - -## Notes for Java programmers ## - -`static` is not a keyword in Scala. Instead, all members that would be static, including classes, should go in a singleton object. They can be referred to with the same syntax, imported piecemeal or as a group, and so on. - -Frequently, Java programmers define static members, perhaps `private`, as implementation aids for their instance members. These move to the companion, too; a common pattern is to import the companion object's members in the class, like so: - -``` -class X { - import X._ - - def blah = foo -} - -object X { - private def foo = 42 -} -``` - -This illustrates another feature: in the context of `private`, a class and its companion are friends. `object X` can access private members of `class X`, and vice versa. To make a member *really* private to one or the other, use `private[this]`. - -For Java convenience, methods, including `var`s and `val`s, defined directly in a singleton object also have a static method defined in the companion class, called a *static forwarder*. Other members are accessible via the `X$.MODULE$` static field for `object X`. - -If you move everything to a companion object and find that all you have left is a class you don't want to be able to instantiate, simply delete the class. Static forwarders will still be created. diff --git a/tutorials/tour/_posts/2017-02-13-tour-of-scala.md b/tutorials/tour/_posts/2017-02-13-tour-of-scala.md deleted file mode 100644 index 44c94b6cfa..0000000000 --- a/tutorials/tour/_posts/2017-02-13-tour-of-scala.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -layout: tutorial -title: Introduction - -discourse: true - -tutorial: scala-tour -categories: tour -num: 1 -languages: [ba, es, ko, pt-br, pl] -outof: 34 -next-page: basics ---- - -Scala is a modern multi-paradigm programming language designed to express common programming patterns in a concise, elegant, and type-safe way. It smoothly integrates features of object-oriented and functional languages. - -## Scala is object-oriented ## -Scala is a pure object-oriented language in the sense that [every value is an object](unified-types.html). Types and behavior of objects are described by [classes](classes.html) and [traits](traits.html). Classes are extended by subclassing and a flexible [mixin-based composition](mixin-class-composition.html) mechanism as a clean replacement for multiple inheritance. - -## Scala is functional ## -Scala is also a functional language in the sense that [every function is a value](unified-types.html). Scala provides a [lightweight syntax](anonymous-function-syntax.html) for defining anonymous functions, it supports [higher-order functions](higher-order-functions.html), it allows functions to be [nested](nested-functions.html), and supports [currying](currying.html). Scala's [case classes](case-classes.html) and its built-in support for [pattern matching](pattern-matching.html) model algebraic types used in many functional programming languages. [Singleton objects](singleton-objects.html) provide a convenient way to group functions that aren't members of a class. - -Furthermore, Scala's notion of pattern matching naturally extends to the [processing of XML data](xml-processing.html) with the help of [right-ignoring sequence patterns](regular-expression-patterns.html), by way of general extension via [extractor objects](extractor-objects.html). In this context, [for comprehensions](for-comprehensions.html) are useful for formulating queries. These features make Scala ideal for developing applications like web services. - -## Scala is statically typed ## -Scala is equipped with an expressive type system that enforces statically that abstractions are used in a safe and coherent manner. In particular, the type system supports: - -* [generic classes](generic-classes.html) -* [variance annotations](variances.html) -* [upper](upper-type-bounds.html) and [lower](lower-type-bounds.html) type bounds, -* [inner classes](inner-classes.html) and [abstract types](abstract-types.html) as object members -* [compound types](compound-types.html) -* [explicitly typed self references](explicitly-typed-self-references.html) -* [implicit parameters](implicit-parameters.html) and [conversions](implicit-conversions.html) -* [polymorphic methods](polymorphic-methods.html) - -A [local type inference mechanism](local-type-inference.html) takes care that the user is not required to annotate the program with redundant type information. In combination, these features provide a powerful basis for the safe reuse of programming abstractions and for the type-safe extension of software. - -## Scala is extensible ## - -In practice, the development of domain-specific applications often requires domain-specific language extensions. Scala provides a unique combination of language mechanisms that make it easy to smoothly add new language constructs in the form of libraries. - -A joint use of both features facilitates the definition of new statements without using meta-programming facilities such as macros. - -Scala is designed to interoperate well with the popular Java 2 Runtime Environment (JRE). In particular, the interaction with the mainstream object-oriented Java programming language is as smooth as possible. Newer Java features like [annotations](annotations.html) and Java generics have direct analogues in Scala. Those Scala features without Java analogues, such as [default](default-parameter-values.html) and [named parameters](named-parameters.html), compile as close to Java as they can reasonably come. Scala has the same compilation model (separate compilation, dynamic class loading) like Java and allows access to thousands of existing high-quality libraries. - -Please continue to the next page to read more. diff --git a/tutorials/tour/_posts/2017-02-13-traits.md b/tutorials/tour/_posts/2017-02-13-traits.md deleted file mode 100644 index feff4651a6..0000000000 --- a/tutorials/tour/_posts/2017-02-13-traits.md +++ /dev/null @@ -1,82 +0,0 @@ ---- -layout: tutorial -title: Traits - -discourse: true - -tutorial: scala-tour -categories: tour -num: 5 -next-page: mixin-class-composition -previous-page: classes -assumed-knowledge: expressions, classes, generics, objects, companion-objects ---- - -Traits are used to share interfaces and fields between classes. They are similar to Java 8's interfaces. Classes and objects can extend traits but traits cannot be instantiated and therefore have no parameters. - -## Defining a trait -A minimal trait is simply the keyword `trait` and an identifier: - -```tut -trait HairColor -``` - -Traits become especially useful as generic types and with abstract methods. -```tut -trait Iterator[A] { - def hasNext: Boolean - def next(): A -} -``` - -Extending the `trait Iterator[A]` requires a type `A` and implementations of the methods `hasNext` and `next`. - -## Using traits -Use the `extends` keyword to extend a trait. Then implement any abstract members of the trait using the `override` keyword: -```tut -trait Iterator[A] { - def hasNext: Boolean - def next(): A -} - - -class IntIterator(to: Int) extends Iterator[Int] { - private var current = 0 - override def hasNext: Boolean = current < to - override def next(): Int = { - if (hasNext) { - val t = current - current += 1 - t - } else 0 - } -} - - -val iterator = new IntIterator(10) -iterator.next() // prints 0 -iterator.next() // prints 1 -``` -This `IntIterator` class takes a parameter `to` as an upper bound. It `extends Iterator[Int]` which means that the `next` method must return an Int. - -## Subtyping -Where a given trait is required, a subtype of the trait can be used instead. -```tut -import scala.collection.mutable.ArrayBuffer - -trait Pet { - val name: String -} - -class Cat(val name: String) extends Pet -class Dog(val name: String) extends Pet - -val dog = new Dog("Harry") -val cat = new Cat("Sally") - -val animals = ArrayBuffer.empty[Pet] -animals.append(dog) -animals.append(cat) -animals.foreach(pet => println(pet.name)) // Prints Harry Sally -``` -The `trait Pet` has an abstract field `name` which gets implemented by Cat and Dog in their constructors. On the last line, we call `pet.name` which must be implemented in any subtype of the trait `Pet`. diff --git a/tutorials/tour/_posts/2017-02-13-unified-types.md b/tutorials/tour/_posts/2017-02-13-unified-types.md deleted file mode 100644 index e62af3f045..0000000000 --- a/tutorials/tour/_posts/2017-02-13-unified-types.md +++ /dev/null @@ -1,80 +0,0 @@ ---- -layout: tutorial -title: Unified Types - -discourse: true - -tutorial: scala-tour -categories: tour -num: 3 -next-page: classes -previous-page: basics -prerequisite-knowledge: classes, basics ---- - -In Scala, all values have a type, including numerical values and functions. The diagram below illustrates a subset of the type hierarchy. - -Scala Type Hierarchy - -## Scala Type Hierarchy ## - -[`Any`](http://www.scala-lang.org/api/2.12.1/scala/Any.html) is the supertype of all types, also called the top type. It defines certain universal methods such as `equals`, `hashCode`, and `toString`. `Any` has two direct subclasses: `AnyVal` and `AnyRef`. - -`AnyVal` represents value types. There are nine predefined value types and they are non-nullable: `Double`, `Float`, `Long`, `Int`, `Short`, `Byte`, `Char`, `Unit`, and `Boolean`. `Unit` is a value type which carries no meaningful information. There is exactly one instance of `Unit` which can be declared literally like so: `()`. All functions must return something so sometimes `Unit` is a useful return type. - -`AnyRef` represents reference types. All non-value types are defined as reference types. Every user-defined type in Scala is a subtype of `AnyRef`. If Scala is used in the context of a Java runtime environment, `AnyRef` corresponds to `java.lang.Object`. - -Here is an example that demonstrates that strings, integers, characters, boolean values, and functions are all objects just like every other object: - -```tut -val list: List[Any] = List( - "a string", - 732, // an integer - 'c', // a character - true, // a boolean value - () => "an anonymous function returning a string" -) - -list.foreach(element => println(element)) -``` - -It defines a variable `list` of type `List[Any]`. The list is initialized with elements of various types, but they all are instance of `scala.Any`, so you can add them to the list. - -Here is the output of the program: - -``` -a string -732 -c -true - -``` - -## Type Casting -Value types can be cast in the following way: -Scala Type Hierarchy - -For example: - -```tut -val x: Long = 987654321 -val y: Float = x // 9.8765434E8 (note that some precision is lost in this case) - -val face: Char = '☺' -val number: Int = face // 9786 -``` - -Casting is unidirectional. This will not compile: - -``` -val x: Long = 987654321 -val y: Float = x // 9.8765434E8 -val z: Long = y // Does not conform -``` - -You can also cast a reference type to a subtype. This will be covered later in the tour. - -## Nothing and Null -`Nothing` is a subtype of all types, also called the bottom type. There is no value that has type `Nothing`. A common use is to signal non-termination such as a thrown exception, program exit, or an infinite loop (i.e., it is the type of an expression which does not evaluate to a value, or a method that does not return normally). - -`Null` is a subtype of all reference types (i.e. any subtype of AnyRef). It has a single value identified by the keyword literal `null`. `Null` is provided mostly for interoperability with other JVM languages and should almost never be used in Scala code. We'll cover alternatives to `null` later in the tour. diff --git a/tutorials/tour/_posts/2017-02-13-upper-type-bounds.md b/tutorials/tour/_posts/2017-02-13-upper-type-bounds.md deleted file mode 100644 index 2043da09b5..0000000000 --- a/tutorials/tour/_posts/2017-02-13-upper-type-bounds.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -layout: tutorial -title: Upper Type Bounds - -discourse: true - -tutorial: scala-tour -categories: tour -num: 20 -next-page: lower-type-bounds -previous-page: variances ---- - -In Scala, [type parameters](generic-classes.html) and [abstract types](abstract-types.html) may be constrained by a type bound. Such type bounds limit the concrete values of the type variables and possibly reveal more information about the members of such types. An _upper type bound_ `T <: A` declares that type variable `T` refers to a subtype of type `A`. -Here is an example that demonstrates upper type bound for a type parameter of class `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 PetContainer[P <: Pet](p: P) { - def pet: P = p -} - -val dogContainer = new PetContainer[Dog](new Dog) -val catContainer = new PetContainer[Cat](new Cat) -// val lionContainer = new PetContainer[Lion](new Lion) -// ^this would not compile -``` -The `class PetContainer` take a type parameter `P` which must be a subtype of `Pet`. `Dog` and `Cat` are subtypes of `Pet` so we can create a new `PetContainer[Dog]` and `PetContainer[Cat]`. However, if we tried to create a `PetContainer[Lion]`, we would get the following Error: - -`type arguments [Lion] do not conform to class PetContainer's type parameter bounds [P <: Pet]` - -This is because `Lion` is not a subtype of `Pet`. diff --git a/tutorials/tour/_posts/2017-02-13-variances.md b/tutorials/tour/_posts/2017-02-13-variances.md deleted file mode 100644 index b7d434cf00..0000000000 --- a/tutorials/tour/_posts/2017-02-13-variances.md +++ /dev/null @@ -1,151 +0,0 @@ ---- -layout: tutorial -title: Variances - -discourse: true - -tutorial: scala-tour -num: 19 -next-page: upper-type-bounds -previous-page: generic-classes ---- - -Variance is the correlation of subtyping relationships of complex types and the subtyping relationships of their component types. Scala supports variance annotations of type parameters of [generic classes](generic-classes.html), to allow them to be covariant, contravariant, or invariant if no annotations are used. The use of variance in the type system allows us to make intuitive connections between complex types, whereas the lack of variance can restrict the reuse of a class abstraction. - -```tut -class Foo[+A] // A covariant class -class Bar[-A] // A contravariant class -class Baz[A] // An invariant class -``` - -### Covariance - -A type parameter `A` of a generic class can be made covariant by using the annotation `+A`. For some `class List[+A]`, making `A` covariant implies that for two types `A` and `B` where `A` is a subtype of `B`, then `List[A]` is a subtype of `List[B]`. This allows us to make very useful and intuitive subtyping relationships using generics. - -Consider this simple class structure: - -```tut -abstract class Animal { - def name: String -} -case class Cat(name: String) extends Animal -case class Dog(name: String) extends Animal -``` - -Both `Cat` and `Dog` are subtypes of `Animal`. The Scala standard library has a generic immutable `sealed abstract class List[+A]` class, where the type parameter `A` is covariant. This means that a `List[Cat]` is a `List[Animal]` and a `List[Dog]` is also a `List[Animal]`. Intuitively, it makes sense that a list of cats and a list of dogs are each lists of animals, and you should be able to substitute either of them for a `List[Animal]`. - -In the following example, the method `printAnimalNames` will accept a list of animals as an argument and print their names each on a new line. If `List[A]` were not covariant, the last two method calls would not compile, which would severely limit the usefulness of the `printAnimalNames` method. - -```tut -object CovarianceTest extends App { - def printAnimalNames(animals: List[Animal]): Unit = { - animals.foreach { animal => - println(animal.name) - } - } - - val cats: List[Cat] = List(Cat("Whiskers"), Cat("Tom")) - val dogs: List[Dog] = List(Dog("Fido"), Dog("Rex")) - - printAnimalNames(cats) - // Whiskers - // Tom - - printAnimalNames(dogs) - // Fido - // Rex -} -``` - -### Contravariance - -A type parameter `A` of a generic class can be made contravariant by using the annotation `-A`. This creates a subtyping relationship between the class and its type parameter that is similar, but opposite to what we get with covariance. That is, for some `class Writer[-A]`, making `A` contravariant implies that for two types `A` and `B` where `A` is a subtype of `B`, `Writer[B]` is a subtype of `Writer[A]`. - -Consider the `Cat`, `Dog`, and `Animal` classes defined above for the following example: - -```tut -abstract class Printer[-A] { - def print(value: A): Unit -} -``` - -A `Printer[A]` is a simple class that knows how to print out some type `A`. Let's define some subclasses for specific types: - -```tut -class AnimalPrinter extends Printer[Animal] { - def print(animal: Animal): Unit = - println("The animal's name is: " + animal.name) -} - -class CatPrinter extends Printer[Cat] { - def print(cat: Cat): Unit = - println("The cat's name is: " + cat.name) -} -``` - -If a `Printer[Cat]` knows how to print any `Cat` to the console, and a `Printer[Animal]` knows how to print any `Animal` to the console, it makes sense that a `Printer[Animal]` would also know how to print any `Cat`. The inverse relationship does not apply, because a `Printer[Cat]` does not know how to print any `Animal` to the console. Therefore, we should be able to substitute a `Printer[Animal]` for a `Printer[Cat]`, if we wish, and making `Printer[A]` contravariant allows us to do exactly that. - -```tut -object ContravarianceTest extends App { - val myCat: Cat = Cat("Boots") - - def printMyCat(printer: Printer[Cat]): Unit = { - printer.print(myCat) - } - - val catPrinter: Printer[Cat] = new CatPrinter - val animalPrinter: Printer[Animal] = new AnimalPrinter - - printMyCat(catPrinter) - printMyCat(animalPrinter) -} -``` - -The output of this program will be: - -``` -The cat's name is: Boots -The animal's name is: Boots -``` - -### Invariance - -Generic classes in Scala are invariant by default. This means that they are neither covariant nor contravariant. In the context of the following example, `Container` class is invariant. A `Container[Cat]` is _not_ a `Container[Animal]`, nor is the reverse true. - -```tut -class Container[A](value: A) { - private var _value: A = value - def getValue: A = _value - def setValue(value: A): Unit = { - _value = value - } -} -``` - -It may seem like a `Container[Cat]` should naturally also be a `Container[Animal]`, but allowing a mutable generic class to be covariant would not be safe. In this example, it is very important that `Container` is invariant. Supposing `Container` was actually covariant, something like this could happen: - -``` -val catContainer: Container[Cat] = new Container(Cat("Felix")) -val animalContainer: Container[Animal] = catContainer -animalContainer.setValue(Dog("Spot")) -val cat: Cat = catContainer.getValue // Oops, we'd end up with a Dog assigned to a Cat -``` - -Fortunately, the compiler stops us long before we could get this far. - -### Other Examples - -Another example that can help one understand variance is `trait Function1[-T, +R]` from the Scala standard library. `Function1` represents a function with one argument, where the first type parameter `T` represents the argument type, and the second type parameter `R` represents the return type. A `Function1` is contravariant over its argument type, and covariant over its return type. For this example we'll use the literal notation `A => B` to represent a `Function1[A, B]`. - -Assume the similar `Cat`, `Dog`, `Animal` inheritance tree used earlier, plus the following: - -```tut -class SmallAnimal -class Mouse extends SmallAnimal -``` - -Suppose we're working with functions that accept types of animals, and return the types of food they eat. If we would like a `Cat => SmallAnimal` (because cats eat small animals), but are given a `Animal => Mouse` instead, our program will still work. Intuitively an `Animal => Mouse` will still accept a `Cat` as an argument, because a `Cat` is an `Animal`, and it returns a `Mouse`, which is also an `SmallAnimal`. Since we can safely and invisibly substitute the former for the latter, we can say `Animal => Mouse` is a subtype of `Cat => SmallAnimal`. - -### Comparison With Other Languages - -Variance is supported in different ways by some languages that are similar to Scala. For example, variance annotations in Scala closely resemble those in C#, where the annotations are added when a class abstraction is defined (declaration-site variance). In Java, however, variance annotations are given by clients when a class abstraction is used (use-site variance). diff --git a/zh-cn/cheatsheets/index.md b/zh-cn/cheatsheets/index.md deleted file mode 100644 index 156103e494..0000000000 --- a/zh-cn/cheatsheets/index.md +++ /dev/null @@ -1,88 +0,0 @@ ---- -layout: cheatsheet -istranslation: true -title: Scalacheat -by: Brendan O'Connor -about: 感谢 Brendan O'Connor, 本速查表可以用于快速地查找Scala语法结构。Licensed by Brendan O'Connor under a CC-BY-SA 3.0 license. -languages: [zh-cn] ---- - -###### Contributed by {{ page.by }} - -| | | -| ------ | ------ | -| 变量 | | -|  `var x = 5`                                                                                             | 可变量       | -| Good `val x = 5`
                  Bad `x=6` | 常量 | -|  `var x: Double = 5`                                                                                     | 显式类型 | -| 函数 | | -|  Good `def f(x: Int) = { x*x }`
                  Bad `def f(x: Int)   { x*x }` | 定义函数
                  隐藏出错: 不加“=”号将会是一段返回Unit类型的过程,这将会导致意想不到的错误。 | -| Good `def f(x: Any) = println(x)`
                  Bad `def f(x) = println(x)` | 定义函数
                  语法错误: 每个参数都需要指定类型。 | -| `type R = Double` | 类型别名 | -|  `def f(x: R)` vs.
                  `def f(x: => R)`                                                                 | 传值调用
                  传名调用 (惰性参数) | -| `(x:R) => x*x` | 匿名函数 | -| `(1 to 5).map(_*2)` vs.
                  `(1 to 5).reduceLeft( _+_ )` | 匿名函数: 下划线是arg参数的占位符。 | -| `(1 to 5).map( x => x*x )` | 匿名函数: 必须命名以后才可以使用一个arg参数两次。 | -|  Good `(1 to 5).map(2*)`
                  Bad `(1 to 5).map(*2)` | 匿名函数: 绑定前缀方法,理智的方法是`2*_`。 | -|  `(1 to 5).map { x => val y=x*2; println(y); y }`                                                             | 匿名函数: 程序块风格,最后一个表达式作为返回值。 | -|  `(1 to 5) filter {_%2 == 0} map {_*2}`                                                                 | 匿名函数: 管道风格(或者用圆括号)。 | -|  `def compose(g:R=>R, h:R=>R) = (x:R) => g(h(x))`
                  `val f = compose({_*2}, {_-1})`                   | 匿名函数: 要传入多个程序块的话,需要外围括号。 | -| `val zscore = (mean:R, sd:R) => (x:R) => (x-mean)/sd` | 柯里化, 显然的语法。 | -| `def zscore(mean:R, sd:R) = (x:R) => (x-mean)/sd` | 柯里化, 显然的语法。 | -|  `def zscore(mean:R, sd:R)(x:R) = (x-mean)/sd`                                                           | 柯里化,语法糖。然后:) | -|  `val normer = zscore(7, 0.4) _`                                                                         | 需要在尾部加下划线来变成偏函数(只对语法糖版本适用)。 | -| `def mapmake[T](g:T=>T)(seq: List[T]) = seq.map(g)` | 泛型 | -|  `5.+(3); 5 + 3`
                  `(1 to 5) map (_*2)`                                                               | 中缀语法糖 | -| `def sum(args: Int*) = args.reduceLeft(_+_)` | 可变参数 | -| | | -| `import scala.collection._` | 通配符导入 | -| `import scala.collection.Vector`
                  `import scala.collection.{Vector, Sequence}` | 选择性导入 | -| `import scala.collection.{Vector => Vec28}` | 重命名导入 | -| `import java.util.{Date => _, _}` | 导入java.util包里除Date之外的所有文件. | -|  `package pkg` _at start of file_
                  `package pkg { ... }`                                             | 声明这是一个包 | -| 数据结构 | | -|  `(1,2,3)`                                                                                               | 元组字面量 (`Tuple3`) | -| `var (x,y,z) = (1,2,3)` | 解构绑定:通过模式匹配来解构元组。 | -|  Bad`var x,y,z = (1,2,3)`                                           | 隐藏出错:每一个变量都被赋值了整个元组。 | -| `var xs = List(1,2,3)` | 列表 (不可变). | -| `xs(2)` | 用括号索引 ([slides](http://www.slideshare.net/Odersky/fosdem-2009-1013261/27)) | -|  `1 :: List(2,3)`                                                                                       | Cons(构成) | -|  `1 to 5` _等价于_ `1 until 6`
                  `1 to 10 by 2`                                                     | Range类型(语法糖) | -|  `()` _(空括号)_                                                                                   | Unit类型的唯一成员 (相当于 C/Java 里的void). | -| 控制结构 | | -| `if (check) happy else sad` | 条件 | -| `if (check) happy` _same as_
                  `if (check) happy else ()` | 条件(语法糖) | -| `while (x < 5) { println(x); x += 1}` | while循环 | -| `do { println(x); x += 1} while (x < 5)` | do while循环 | -| `import scala.util.control.Breaks._`
                  `breakable {`
                  ` for (x <- xs) {`
                  ` if (Math.random < 0.1) break`
                  ` }`
                  `}`| break. ([slides](http://www.slideshare.net/Odersky/fosdem-2009-1013261/21)) | -|  `for (x <- xs if x%2 == 0) yield x*10` _等价于_
                  `xs.filter(_%2 == 0).map(_*10)`                   | for comprehension (for 导出): filter/map | -|  `for ((x,y) <- xs zip ys) yield x*y` _等价于_
                  `(xs zip ys) map { case (x,y) => x*y }`             | for comprehension (for 导出): 解构绑定 | -| `for (x <- xs; y <- ys) yield x*y` _等价于_
                  `xs flatMap {x => ys map {y => x*y}}` | for comprehension (for 导出): 叉乘 | -|  `for (x <- xs; y <- ys) {`
                     `println("%d/%d = %.1f".format(x, y, x/y.toFloat))`
                  `}`                     | for comprehension (for 导出): 不可避免的格式
                  [sprintf-style](http://java.sun.com/javase/6/docs/api/java/util/Formatter.html#syntax) | -| `for (i <- 1 to 5) {`
                  `println(i)`
                  `}` | for comprehension (for 导出): 包括上边界的遍历 | -| `for (i <- 1 until 5) {`
                  `println(i)`
                  `}` | for comprehension (for 导出): 忽略上边界的遍历 | -| 模式匹配 | | -|  Good `(xs zip ys) map { case (x,y) => x*y }`
                  Bad `(xs zip ys) map( (x,y) => x*y )` | 在函数的参数中使用模式匹配的例子。 | -| Bad
                  `val v42 = 42`
                  `Some(3) match {`
                  ` case Some(v42) => println("42")`
                  ` case _ => println("Not 42")`
                  `}` | "v42" 被解释为可以匹配任何Int类型值的名称,打印输出"42"。 | -|  Good
                  `val v42 = 42`
                  `Some(3) match {`
                  ``   case Some(`v42`) => println("42")``
                  `case _ => println("Not 42")`
                  `}` | 有反引号的 "\`v42\`" 被解释为已经存在的val `v42`,所以输出的是 "Not 42". | -|  Good
                  `val UppercaseVal = 42`
                  `Some(3) match {`
                  ` case Some(UppercaseVal) => println("42")`
                  `   case _ => println("Not 42")`
                  `}` |  `UppercaseVal` 被视作已经存在的 val,而不是一个新的模式变量,因为它是以大写字母开头的,所以`UppercaseVal` 所包含的值(42)和检查的值(3)不匹配,输出"Not 42"。| -| 面向对象 | | -| `class C(x: R)` _same as_
                  `class C(private val x: R)`
                  `var c = new C(4)` | 构造器参数 - 私有 | -| `class C(val x: R)`
                  `var c = new C(4)`
                  `c.x` | 构造器参数 - 公有 | -|  `class C(var x: R) {`
                  `assert(x > 0, "positive please")`
                  `var y = x`
                  `val readonly = 5`
                  `private var secret = 1`
                  `def this = this(42)`
                  `}`|
                  构造函数就是类的主体
                  声明一个公有成员变量
                  声明一个可get但不可set的成员变量
                  声明一个私有变量
                  可选构造器| -| `new{ ... }` | 匿名类 | -| `abstract class D { ... }` | 定义一个抽象类。(不可创建) | -| `class C extends D { ... }` | 定义一个继承子类。 | -| `class D(var x: R)`
                  `class C(x: R) extends D(x)` | 继承与构造器参数(愿望清单: 默认自动传参) -|  `object O extends D { ... }`                                                                           | 定义一个单例(和模块一样) | -|  `trait T { ... }`
                  `class C extends T { ... }`
                  `class C extends D with T { ... }`                 | 特质
                  带有实现的接口,没有构造参数。 [mixin-able]({{ site.baseurl }}/tutorials/tour/mixin-class-composition.html). -|  `trait T1; trait T2`
                  `class C extends T1 with T2`
                  `class C extends D with T1 with T2`             | (混入)多个特质 | -|  `class C extends D { override def f = ...}`                                                           | 必须声明覆盖该方法。 | -|  `new java.io.File("f")`                                                                             | 创建对象。 | -|  Bad `new List[Int]`
                  Good `List(1,2,3)` | 类型错误: 抽象类型
                  相反,习惯上:调用工厂(方法)会自动推测类型 | -| `classOf[String]` | 类字面量 | -| `x.isInstanceOf[String]` | 类型检查 (运行时) | -| `x.asInstanceOf[String]` | 类型强制转换 (运行时) | -| `x: String` | 归属 (编译时) | - - diff --git a/zh-cn/overviews/collections/arrays.md b/zh-cn/overviews/collections/arrays.md deleted file mode 100644 index e9db72f0b6..0000000000 --- a/zh-cn/overviews/collections/arrays.md +++ /dev/null @@ -1,120 +0,0 @@ ---- -layout: overview-large -title: 数组 - -discourse: false - -partof: collections -num: 10 -language: zh-cn ---- - -在Scala中,[数组](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/Array.html)是一种特殊的collection。一方面,Scala数组与Java数组是一一对应的。即Scala数组Array[Int]可看作Java的Int[],Array[Double]可看作Java的double[],以及Array[String]可看作Java的String[]。但Scala数组比Java数组提供了更多内容。首先,Scala数组是一种泛型。即可以定义一个Array[T],T可以是一种类型参数或抽象类型。其次,Scala数组与Scala序列是兼容的 - 在需要Seq[T]的地方可由Array[T]代替。最后,Scala数组支持所有的序列操作。这里有个实际的例子: - - scala> val a1 = Array(1, 2, 3) - a1: Array[Int] = Array(1, 2, 3) - scala> val a2 = a1 map (_ * 3) - a2: Array[Int] = Array(3, 6, 9) - scala> val a3 = a2 filter (_ % 2 != 0) - a3: Array[Int] = Array(3, 9) - scala> a3.reverse - res0: Array[Int] = Array(9, 3) - -既然Scala数组表现的如同Java的数组,那么Scala数组这些额外的特性是如何运作的呢?实际上,Scala 2.8与早期版本在这个问题的处理上有所不同。早期版本中执行打包/解包过程时,Scala编译器做了一些“神奇”的包装/解包的操作,进行数组与序列对象之间互转。其中涉及到的细节相当复杂,尤其是创建一个新的泛型类型数组Array[T]时。一些让人迷惑的罕见实例以及数组操作的性能都是不可预测的。 - -Scala 2.8设计要简单得多,其数组实现系统地使用隐式转换,从而基本去除了编译器的特殊处理。Scala 2.8中数组不再看作序列,因为本地数组的类型不是Seq的子类型。而是在数组和 `scala.collection.mutable.WrappedArray`这个类的实例之间隐式转换,后者则是Seq的子类。这里有个例子: - - scala> val seq: Seq[Int] = a1 - seq: Seq[Int] = WrappedArray(1, 2, 3) - scala> val a4: Array[Int] = s.toArray - a4: Array[Int] = Array(1, 2, 3) - scala> a1 eq a4 - res1: Boolean = true - -上面的例子说明数组与序列是兼容的,因为数组可以隐式转换为WrappedArray。反之可以使用Traversable提供的toArray方法将WrappedArray转换为数组。REPL最后一行表明,隐式转换与toArray方法作用相互抵消。 - -数组还有另外一种隐式转换,不需要将数组转换成序列,而是简单地把所有序列的方法“添加”给数组。“添加”其实是将数组封装到一个ArrayOps类型的对象中,后者支持所有序列的方法。ArrayOps对象的生命周期通常很短暂,不调用序列方法的时候基本不会用到,其内存也可以回收。现代虚拟机一般不会创建这个对象。 - -在接下来REPL中展示数组的这两种隐式转换的区别: - - scala> val seq: Seq[Int] = a1 - seq: Seq[Int] = WrappedArray(1, 2, 3) - scala> seq.reverse - res2: Seq[Int] = WrappedArray(3, 2, 1) - scala> val ops: collection.mutable.ArrayOps[Int] = a1 - ops: scala.collection.mutable.ArrayOps[Int] = [I(1, 2, 3) - scala> ops.reverse - res3: Array[Int] = Array(3, 2, 1) - -注意seq是一个WrappedArray,seq调用reverse方法也会得到一个WrappedArray。这是没问题的,因为封装的数组就是Seq,在任意Seq上调用reverse方法都会得到Seq。反之,变量ops属于ArrayOps这个类,对其调用reverse方法得到一个数组,而不是Seq。 - -上例直接使用ArrayOps仅为了展示其与WrappedArray的区别,这种用法非常不自然。一般情况下永远不要实例化一个ArrayOps,而是在数组上调用Seq的方法: - - scala> a1.reverse - res4: Array[Int] = Array(3, 2, 1) - -ArrayOps的对象会通过隐式转换自动的插入,因此上述的代码等价于 - - scala> intArrayOps(a1).reverse - res5: Array[Int] = Array(3, 2, 1) - -这里的intArrayOps就是之前例子中插入的隐式转换。这里引出一个疑问,上面代码中,编译器为何选择了intArrayOps而不是WrappedArray做隐式转换?毕竟,两种转换都是将数组映射到支持reverse方法的类型,并且指定输入。答案是两种转换是有优先级次序的,ArrayOps转换比WrappedArray有更高的优先级。前者定义在Predef对象中,而后者定义在Predef的基类 `scala.LowPriorityImplicits` 中。子类、子对象中隐式转换的优先级高于基类。所以如果两种转换都可用,Predef中的会优先选取。字符串的情况也是如此。 - -数组与序列兼容,并支持所有序列操作的方法,你现在应该已经了然于胸。那泛型呢?在Java中你不可以定义一个以T为类型参数的`T[]`。那么Scala的`Array[T]`是如何做的呢?事实上一个像`Array[T] `的泛型数组在运行时态可任意为Java的八个原始数组类型像`byte[]`, `short[]`, `char[]`, `int[]`, `long[]`, `float[]`, `double[]`, `boolean[]`,甚至它可以是一个对象数组。最常见的运行时态类型是AnyRef ,它包括了所有的这些类型(相当于java.lang.Object),因此这样的类型可以通过Scala编译器映射到`Array[T]`.在运行时,当`Array[T]`类型的数组元素被访问或更新时,就会有一个序列的类型测试用于确定真正的数组类型,随后就是java中的正确的数组操作。这些类型测试会影响数组操作的效率。这意味着如果你需要更大的性能,你应该更喜欢具体而明确的泛型数组。代表通用的泛型数组是不够的,因此,也必然有一种方式去创造泛型数组。这是一个更难的问题,需要一点点的帮助你。为了说明这个问题,考虑下面用一个通用的方法去创造数组的尝试。 - - //这是错的! - def evenElems[T](xs: Vector[T]): Array[T] = { - val arr = new Array[T]((xs.length + 1) / 2) - for (i <- 0 until xs.length by 2) - arr(i / 2) = xs(i) - arr - } - -evenElems方法返回一个新数组,该数组包含了参数向量xs的所有元素甚至在向量中的位置。evenElems 主体的第一行构建了结果数组,将相同元素类型作为参数。所以根据T的实际类型参数,这可能是一个`Array[Int]`,或者是一个`Array[Boolean]`,或者是一个在java中有一些其他基本类型的数组,或者是一个有引用类型的数组。但是这些类型有不同的运行时表达,那么Scala如何在运行时选择正确的呢?事实上,它不是基于信息传递做的,因为与类型参数T相对应的实际类型在运行时已被抹去。这就是为什么你在编译上面的代码时会出现如下的错误信息: - - error: cannot find class manifest for element type T - val arr = new Array[T]((arr.length + 1) / 2) - ^ - -这里需要你做的就是通过提供一些运行时的实际元素类型参数的线索来帮助编译器处理。这个运行时的提示采取的形式是一个`scala.reflect.ClassTag`类型的类声明。一个类声明就是一个类型描述对象,给对象描述了一个类型的顶层类。另外,类声明也有`scala.reflect.Manifest`类型的所有声明,它描述了类型的各个方面。但对于数组创建而言,只需要提供类声明。 - -如果你指示编译器那么做它就会自动的构建类声明。“指示”意味着你决定一个类声明作为隐式参数,像这样: - - def evenElems[T](xs: Vector[T])(implicit m: ClassTag[T]): Array[T] = ... - -使用一个替换和较短的语法。通过用一个上下文绑定你也可以要求类型与一个类声明一起。这种方式是跟在一个冒号类型和类名为ClassManifest的后面,想这样: - - import scala.reflect.ClassTag - // this works - def evenElems[T: ClassTag](xs: Vector[T]): Array[T] = { - val arr = new Array[T]((xs.length + 1) / 2) - for (i <- 0 until xs.length by 2) - arr(i / 2) = xs(i) - arr - } - -这两个evenElems的修订版本意思是完全相同的。当Array[T] 构造时,在任何情况下会发生的是,编译器会寻找类型参数T的一个类声明,这就是说,它会寻找ClassManifest[T]一个隐式类型的值。如果如此的一个值被发现,声明会用来构造正确的数组类型。否则,你就会看到一个错误信息像上面一样。 - -下面是一些使用evenElems 方法的REPL 交互。 - - scala> evenElems(Vector(1, 2, 3, 4, 5)) - res6: Array[Int] = Array(1, 3, 5) - scala> evenElems(Vector("this", "is", "a", "test", "run")) - res7: Array[java.lang.String] = Array(this, a, run) - -在这两种情况下,Scala编译器自动的为元素类型构建一个类声明(首先,Int,然后String)并且通过它传递evenElems 方法的隐式参数。编译器可以对所有的具体类型构造,但如果论点本身是另一个没有类声明的类型参数就不可以。例如,下面的错误: - - scala> def wrap[U](xs: Vector[U]) = evenElems(xs) - :6: error: No ClassTag available for U. - def wrap[U](xs: Vector[U]) = evenElems(xs) - ^ - -这里所发生的是,evenElems 需要一个类型参数U的类声明,但是没有发现。这种情况下的解决方案是,当然,是为了U的另一个隐式类声明。所以下面起作用了: - - scala> def wrap[U: ClassTag](xs: Vector[U]) = evenElems(xs) - wrap: [U](xs: Vector[U])(implicit evidence$1: scala.reflect.ClassTag[U])Array[U] - -这个实例还显示在定义U的上下文绑定里这仅是一个简短的隐式参数命名为`ClassTag[U]`类型的`evidence$1`。 - -总结,泛型数组创建需要类声明。所以每当创建一个类型参数T的数组,你还需要提供一个T的隐式类声明。最简单的方法是声明类型参数与ClassManifest的上下文绑定,如 `[T: ClassTag]`。 - diff --git a/zh-cn/overviews/collections/concrete-immutable-collection-classes.md b/zh-cn/overviews/collections/concrete-immutable-collection-classes.md deleted file mode 100644 index a7827a5d80..0000000000 --- a/zh-cn/overviews/collections/concrete-immutable-collection-classes.md +++ /dev/null @@ -1,189 +0,0 @@ ---- -layout: overview-large -title: 具体的不可变集实体类 - -discourse: false - -partof: collections -num: 8 -language: zh-cn ---- - - -Scala中提供了多种具体的不可变集类供你选择,这些类(maps, sets, sequences)实现的接口(traits)不同,比如是否能够是无限(infinite)的,各种操作的速度也不一样。下面的篇幅介绍几种Scala中最常用的不可变集类型。 - -## List(列表) - -列表[List](http://www.scala-lang.org/api/2.10.0/scala/collection/immutable/List.html)是一种有限的不可变序列式。提供了常数时间的访问列表头元素和列表尾的操作,并且提供了常数时间的构造新链表的操作,该操作将一个新的元素插入到列表的头部。其他许多操作则和列表的长度成线性关系。 - -List通常被认为是Scala中最重要的数据结构,所以我们在此不必过于赘述。版本2.8中主要的变化是,List类和其子类::以及其子对象Nil都被定义在了其逻辑上所属的scala.collection.immutable包里。scala包中仍然保留了List,Nil和::的别名,所以对于用户来说可以像原来一样访问List。 - -另一个主要的变化是,List现在更加紧密的融入了Collections Framework中,而不是像过去那样更像一个特例。比如说,大量原本存在于与List相关的对象的方法基本上全部都过时(deprecated)了,取而代之的是被每种Collection所继承的统一的构造方法。 - -## Stream(流) - -流[Stream](http://www.scala-lang.org/api/2.10.0/scala/collection/immutable/Stream.html)与List很相似,只不过其中的每一个元素都经过了一些简单的计算处理。也正是因为如此,stream结构可以无限长。只有那些被要求的元素才会经过计算处理,除此以外stream结构的性能特性与List基本相同。 - -鉴于List通常使用 `:: `运算符来进行构造,stream使用外观上很相像的`#::`。这里用一个包含整数1,2和3的stream来做一个简单的例子: - - scala> val str = 1 #:: 2 #:: 3 #:: Stream.empty - str: scala.collection.immutable.Stream[Int] = Stream(1, ?) - -该stream的头结点是1,尾是2和3.尾部并没有被打印出来,因为还没有被计算。stream被特别定义为懒惰计算,并且stream的toString方法很谨慎的设计为不去做任何额外的计算。 - -下面给出一个稍复杂些的例子。这里讲一个以两个给定的数字为起始的斐波那契数列转换成stream。斐波那契数列的定义是,序列中的每个元素等于序列中在它之前的两个元素之和。 - - scala> def fibFrom(a: Int, b: Int): Stream[Int] = a #:: fibFrom(b, a + b) - fibFrom: (a: Int,b: Int)Stream[Int] - -这个函数看起来比较简单。序列中的第一个元素显然是a,其余部分是以b和位于其后的a+b为开始斐波那契数列。这段程序最大的亮点是在对序列进行计算的时候避免了无限递归。如果函数中使用`::`来替换`#::`,那么之后的每次调用都会产生另一次新的调用,从而导致无限递归。在此例中,由于使用了`#::`,等式右值中的调用在需要求值之前都不会被展开。这里尝试着打印出以1,1开头的斐波那契数列的前几个元素: - - scala> val fibs = fibFrom(1, 1).take(7) - fibs: scala.collection.immutable.Stream[Int] = Stream(1, ?) - scala> fibs.toList - res9: List[Int] = List(1, 1, 2, 3, 5, 8, 13) - -## Vector(向量) - -对于只需要处理数据结构头结点的算法来说,List非常高效。可是相对于访问、添加和删除List头结点只需要固定时间,访问和修改头结点之后元素所需要的时间则是与List深度线性相关的。 - -向量[Vector](http://www.scala-lang.org/api/2.10.0/scala/collection/immutable/Vector.html)是用来解决列表(list)不能高效的随机访问的一种结构。Vector结构能够在“更高效”的固定时间内访问到列表中的任意元素。虽然这个时间会比访问头结点或者访问某数组元素所需的时间长一些,但至少这个时间也是个常量。因此,使用Vector的算法不必仅是小心的处理数据结构的头结点。由于可以快速修改和访问任意位置的元素,所以对Vector结构做写操作很方便。 - -Vector类型的构建和修改与其他的序列结构基本一样。 - - scala> val vec = scala.collection.immutable.Vector.empty - vec: scala.collection.immutable.Vector[Nothing] = Vector() - scala> val vec2 = vec :+ 1 :+ 2 - vec2: scala.collection.immutable.Vector[Int] = Vector(1, 2) - scala> val vec3 = 100 +: vec2 - vec3: scala.collection.immutable.Vector[Int] = Vector(100, 1, 2) - scala> vec3(0) - res1: Int = 100 - -Vector结构通常被表示成具有高分支因子的树(树或者图的分支因子是指数据结构中每个节点的子节点数目)。每一个树节点包含最多32个vector元素或者至多32个子树节点。包含最多32个元素的vector可以表示为一个单一节点,而一个间接引用则可以用来表示一个包含至多`32*32=1024`个元素的vector。从树的根节点经过两跳到达叶节点足够存下有2的15次方个元素的vector结构,经过3跳可以存2的20次方个,4跳2的25次方个,5跳2的30次方个。所以对于一般大小的vector数据结构,一般经过至多5次数组访问就可以访问到指定的元素。这也就是我们之前所提及的随机数据访问时“运行时间的相对高效”。 - -由于Vectors结构是不可变的,所以您不能通过修改vector中元素的方法来返回一个新的vector。尽管如此,您仍可以通过update方法从一个单独的元素中创建出区别于给定数据结构的新vector结构: - - scala> val vec = Vector(1, 2, 3) - vec: scala.collection.immutable.Vector[Int] = Vector(1, 2, 3) - scala> vec updated (2, 4) - res0: scala.collection.immutable.Vector[Int] = Vector(1, 2, 4) - scala> vec - res1: scala.collection.immutable.Vector[Int] = Vector(1, 2, 3) - -从上面例子的最后一行我们可以看出,update方法的调用并不会改变vec的原始值。与元素访问类似,vector的update方法的运行时间也是“相对高效的固定时间”。对vector中的某一元素进行update操作可以通过从树的根节点开始拷贝该节点以及每一个指向该节点的节点中的元素来实现。这就意味着一次update操作能够创建1到5个包含至多32个元素或者子树的树节点。当然,这样做会比就地更新一个可变数组败家很多,但比起拷贝整个vector结构还是绿色环保了不少。 - -由于vector在快速随机选择和快速随机更新的性能方面做到很好的平衡,所以它目前正被用作不可变索引序列的默认实现方式。 - - scala> collection.immutable.IndexedSeq(1, 2, 3) - res2: scala.collection.immutable.IndexedSeq[Int] = Vector(1, 2, 3) - -## Immutable stacks(不可变栈) - -如果您想要实现一个后入先出的序列,那您可以使用[Stack](http://www.scala-lang.org/api/2.10.0/scala/collection/immutable/Stack.html)。您可以使用push向栈中压入一个元素,用pop从栈中弹出一个元素,用top查看栈顶元素而不用删除它。所有的这些操作都仅仅耗费固定的运行时间。 - -这里提供几个简单的stack操作的例子: - - scala> val stack = scala.collection.immutable.Stack.empty - stack: scala.collection.immutable.Stack[Nothing] = Stack() - scala> val hasOne = stack.push(1) - hasOne: scala.collection.immutable.Stack[Int] = Stack(1) - scala> stack - stack: scala.collection.immutable.Stack[Nothing] = Stack() - scala> hasOne.top - res20: Int = 1 - scala> hasOne.pop - res19: scala.collection.immutable.Stack[Int] = Stack() - -不可变stack一般很少用在Scala编程中,因为List结构已经能够覆盖到它的功能:push操作同List中的::基本相同,pop则对应着tail。 - -## Immutable Queues(不可变队列) - -[Queue](http://www.scala-lang.org/api/2.10.0/scala/collection/immutable/Queue.html)是一种与stack很相似的数据结构,除了与stack的后入先出不同,Queue结构的是先入先出的。 - -这里给出一个创建空不可变queue的例子: - - scala> val empty = scala.collection.immutable.Queue[Int]() - empty: scala.collection.immutable.Queue[Int] = Queue() - -您可以使用enqueue方法在不可变Queue中加入一个元素: - - scala> val has1 = empty.enqueue(1) - has1: scala.collection.immutable.Queue[Int] = Queue(1) - -如果想要在queue中添加多个元素需要在调用enqueue方法时用一个collection对象作为参数: - - scala> val has123 = has1.enqueue(List(2, 3)) - has123: scala.collection.immutable.Queue[Int] - = Queue(1, 2, 3) - -如果想要从queue的头部删除一个元素,您可以使用dequeue方法: - - scala> val (element, has23) = has123.dequeue - element: Int = 1 - has23: scala.collection.immutable.Queue[Int] = Queue(2, 3) - -请注意,dequeue方法将会返回两个值,包括被删除掉的元素和queue中剩下的部分。 - -## Ranges (等差数列) - -[Range]表示的是一个有序的等差整数数列。比如说,“1,2,3,”就是一个Range,“5,8,11,14,”也是。在Scala中创建一个Range类,需要用到两个预定义的方法to和by。 - - scala> 1 to 3 - res2: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3) - scala> 5 to 14 by 3 - res3: scala.collection.immutable.Range = Range(5, 8, 11, 14) - -如果您想创建一个不包含范围上限的Range类,那么用until方法代替to更为方便: - - scala> 1 until 3 - res2: scala.collection.immutable.Range = Range(1, 2) - -Range类的空间复杂度是恒定的,因为只需要三个数字就可以定义一个Range类:起始、结束和步长值。也正是因为有这样的特性,对Range类多数操作都非常非常的快。 - -## Hash Tries - -Hash try是高效实现不可变集合和关联数组(maps)的标准方法,[immutable.HashMap](http://www.scala-lang.org/api/2.10.0/scala/collection/immutable/HashMap.html)类提供了对Hash Try的支持。从表现形式上看,Hash Try和Vector比较相似,都是树结构,且每个节点包含32个元素或32个子树,差别只是用不同的hash code替换了指向各个节点的向量值。举个例子吧:当您要在一个映射表里找一个关键字,首先需要用hash code值替换掉之前的向量值;然后用hash code的最后5个bit找到第一层子树,然后每5个bit找到下一层子树。当存储在一个节点中所有元素的代表他们当前所在层的hash code位都不相同时,查找结束。 - -Hash Try对于快速查找和函数式的高效添加和删除操作上取得了很好的平衡,这也是Scala中不可变映射和集合采用Hash Try作为默认实现方式的原因。事实上,Scala对于大小小于5的不可变集合和映射做了更进一步的优化。只有1到4个元素的集合和映射被在现场会被存储在一个单独仅仅包含这些元素(对于映射则只是包含键值对)的对象中。空集合和空映射则视情况不同作为一个单独的对象,空的一般情况下就会一直空下去,所以也没有必要为他们复制一份拷贝。 - -## Red-Black Trees(红黑树) - -红黑树是一种平衡二叉树,树中一些节点被设计成红节点,其余的作为黑节点。同任何平衡二叉树一样,对红黑树的最长运行时间随树的节点数成对数(logarithmic)增长。 - -Scala隐含的提供了不可变集合和映射的红黑树实现,您可以在[TreeSet](http://www.scala-lang.org/api/2.10.0/scala/collection/immutable/TreeSet.html)和[TreeMap](http://www.scala-lang.org/api/2.10.0/scala/collection/immutable/TreeMap.html)下使用这些方法。 - - ## scala> scala.collection.immutable.TreeSet.empty[Int] - res11: scala.collection.immutable.TreeSet[Int] = TreeSet() - scala> res11 + 1 + 3 + 3 - res12: scala.collection.immutable.TreeSet[Int] = TreeSet(1, 3) - -红黑树在Scala中被作为SortedSet的标准实现,因为它提供了一个高效的迭代器,可以用来按照拍好的序列返回所有的元素。 - -## Immutable BitSets(不可变位集合) - -[BitSet](http://www.scala-lang.org/api/2.10.0/scala/collection/immutable/BitSet.html)代表一个由小整数构成的容器,这些小整数的值表示了一个大整数被置1的各个位。比如说,一个包含3、2和0的bit集合可以用来表示二进制数1101和十进制数13. - -BitSet内部的使用了一个64位long型的数组。数组中的第一个long表示整数0到63,第二个表示64到27,以此类推。所以只要集合中最大的整数在千以内BitSet的压缩率都是相当高的。 - -BitSet操作的运行时间是非常快的。查找测试仅仅需要固定时间。向集合内增加一个项所需时间同BitSet数组中long型的个数成正比,但这也通常是个非常小的值。这里有几个关于BitSet用法的例子: - - scala> val bits = scala.collection.immutable.BitSet.empty - bits: scala.collection.immutable.BitSet = BitSet() - scala> val moreBits = bits + 3 + 4 + 4 - moreBits: scala.collection.immutable.BitSet = BitSet(3, 4) - scala> moreBits(3) - res26: Boolean = true - scala> moreBits(0) - res27: Boolean = false - -## List Maps - -[ListMap](http://www.scala-lang.org/api/2.10.0/scala/collection/immutable/ListMap.html)被用来表示一个保存键-值映射的链表。一般情况下,ListMap操作都需要遍历整个列表,所以操作的运行时间也同列表长度成线性关系。实际上ListMap在Scala中很少使用,因为标准的不可变映射通常速度会更快。唯一的例外是,在构造映射时由于某种原因,链表中靠前的元素被访问的频率大大高于其他的元素。 - - scala> val map = scala.collection.immutable.ListMap(1->"one", 2->"two") - map: scala.collection.immutable.ListMap[Int,java.lang.String] = - Map(1 -> one, 2 -> two) - scala> map(2) - res30: String = "two" - diff --git a/zh-cn/overviews/collections/concrete-mutable-collection-classes.md b/zh-cn/overviews/collections/concrete-mutable-collection-classes.md deleted file mode 100644 index 0ad5342708..0000000000 --- a/zh-cn/overviews/collections/concrete-mutable-collection-classes.md +++ /dev/null @@ -1,170 +0,0 @@ ---- -layout: overview-large -title: 具体的可变容器类 - -discourse: false - -partof: collections -num: 9 -language: zh-cn ---- - - -目前你已经看过了Scala的不可变容器类,这些是标准库中最常用的。现在来看一下可变容器类。 - -## Array Buffers - -一个[ArrayBuffer](http://www.scala-lang.org/api/2.10.0/scala/collection/mutable/ArrayBuffer.html)缓冲包含数组和数组的大小。对数组缓冲的大多数操作,其速度与数组本身无异。因为这些操作直接访问、修改底层数组。另外,数组缓冲可以进行高效的尾插数据。追加操作均摊下来只需常量时间。因此,数组缓冲可以高效的建立一个有大量数据的容器,无论是否总有数据追加到尾部。 - - scala> val buf = scala.collection.mutable.ArrayBuffer.empty[Int] - buf: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer() - scala> buf += 1 - res32: buf.type = ArrayBuffer(1) - scala> buf += 10 - res33: buf.type = ArrayBuffer(1, 10) - scala> buf.toArray - res34: Array[Int] = Array(1, 10) - -## List Buffers - -[ListBuffer](http://www.scala-lang.org/api/2.10.0/scala/collection/mutable/ListBuffer.html) 类似于数组缓冲。区别在于前者内部实现是链表, 而非数组。如果你想把构造完的缓冲转换为列表,那就用列表缓冲,别用数组缓冲。 - - scala> val buf = scala.collection.mutable.ListBuffer.empty[Int] - buf: scala.collection.mutable.ListBuffer[Int] = ListBuffer() - scala> buf += 1 - res35: buf.type = ListBuffer(1) - scala> buf += 10 - res36: buf.type = ListBuffer(1, 10) - scala> buf.toList - res37: List[Int] = List(1, 10) - -## StringBuilders - -数组缓冲用来构建数组,列表缓冲用来创建列表。类似地,[StringBuilder](http://www.scala-lang.org/api/2.10.0/scala/collection/mutable/StringBuilder.html) 用来构造字符串。作为常用的类,字符串构造器已导入到默认的命名空间。直接用 new StringBuilder就可创建字符串构造器 ,像这样: - - scala> val buf = new StringBuilder - buf: StringBuilder = - scala> buf += 'a' - res38: buf.type = a - scala> buf ++= "bcdef" - res39: buf.type = abcdef - scala> buf.toString - res41: String = abcdef - -## 链表 - -链表是可变序列,它由一个个使用next指针进行链接的节点构成。它们的支持类是[LinkedList](http://www.scala-lang.org/api/2.10.0/scala/collection/mutable/LinkedList.html)。在大多数的编程语言中,null可以表示一个空链表,但是在Scalable集合中不是这样。因为就算是空的序列,也必须支持所有的序列方法。尤其是 `LinkedList.empty.isEmpty` 必须返回`true`,而不是抛出一个 `NullPointerException` 。空链表用一种特殊的方式编译: - -它们的 next 字段指向它自身。链表像他们的不可变对象一样,是最佳的顺序遍历序列。此外,链表可以很容易去插入一个元素或链接到另一个链表。 - -## 双向链表 - -双向链表和单向链表相似,只不过它们除了具有 next字段外,还有一个可变字段 prev用来指向当前节点的上一个元素 。这个多出的链接的好处主要在于可以快速的移除元素。双向链表的支持类是[DoubleLinkedList](http://www.scala-lang.org/api/2.10.0/scala/collection/mutable/DoubleLinkedList.html). - -## 可变列表 - -[MutableList](http://www.scala-lang.org/api/2.10.0/scala/collection/mutable/MutableList.html) 由一个单向链表和一个指向该链表终端空节点的指针构成。因为避免了贯穿整个列表去遍历搜索它的终端节点,这就使得列表压缩了操作所用的时间。MutableList 目前是Scala中[mutable.LinearSeq](http://www.scala-lang.org/api/2.10.0/scala/collection/LinearSeq.html) 的标准实现。 - -## 队列 - -Scala除了提供了不可变队列之外,还提供了可变队列。你可以像使用一个不可变队列一样地使用一个可变队列,但你需要使用+= 和++=操作符进行添加的方式来替代排队方法。 -当然,在一个可变队列中,出队方法将只移除头元素并返回该队列。这里是一个例子: - - scala> val queue = new scala.collection.mutable.Queue[String] - queue: scala.collection.mutable.Queue[String] = Queue() - scala> queue += "a" - res10: queue.type = Queue(a) - scala> queue ++= List("b", "c") - res11: queue.type = Queue(a, b, c) - scala> queue - res12: scala.collection.mutable.Queue[String] = Queue(a, b, c) - scala> queue.dequeue - res13: String = a - scala> queue - res14: scala.collection.mutable.Queue[String] = Queue(b, c) - -## 数组序列 - -Array Sequences 是具有固定大小的可变序列。在它的内部,用一个 `Array[Object]`来存储元素。在Scala 中,[ArraySeq](http://www.scala-lang.org/api/2.10.0/scala/collection/mutable/ArraySeq.html) 是它的实现类。 - -如果你想拥有 Array 的性能特点,又想建立一个泛型序列实例,但是你又不知道其元素的类型,在运行阶段也无法提供一个`ClassManifest` ,那么你通常可以使用 `ArraySeq` 。这些问题在[arrays](http://docs.scala-lang.org/overviews/collections/arrays.html)一节中有详细的说明。 - -## 堆栈 - -你已经在前面看过了不可变栈。还有一个可变栈,支持类是[mutable.Stack](http://www.scala-lang.org/api/2.10.0/scala/collection/mutable/Stack.html)。它的工作方式与不可变栈相同,只是适当的做了修改。 - - scala> val stack = new scala.collection.mutable.Stack[Int] - stack: scala.collection.mutable.Stack[Int] = Stack() - scala> stack.push(1) - res0: stack.type = Stack(1) - scala> stack - res1: scala.collection.mutable.Stack[Int] = Stack(1) - scala> stack.push(2) - res0: stack.type = Stack(1, 2) - scala> stack - res3: scala.collection.mutable.Stack[Int] = Stack(1, 2) - scala> stack.top - res8: Int = 2 - scala> stack - res9: scala.collection.mutable.Stack[Int] = Stack(1, 2) - scala> stack.pop - res10: Int = 2 - scala> stack - res11: scala.collection.mutable.Stack[Int] = Stack(1) - -## 数组堆栈 - -[ArrayStack](http://www.scala-lang.org/api/2.10.0/scala/collection/mutable/ArrayStack.html) 是另一种可变栈的实现,用一个可根据需要改变大小的数组做为支持。它提供了快速索引,使其通常在大多数的操作中会比普通的可变堆栈更高效一点。 - -## 哈希表 - -Hash Table 用一个底层数组来存储元素,每个数据项在数组中的存储位置由这个数据项的Hash Code 来决定。添加一个元素到Hash Table不用花费多少时间,只要数组中不存在与其含有相同Hash Code的另一个元素。因此,只要Hash Table能够根据一种良好的hash codes分配机制来存放对象,Hash Table的速度会非常快。所以在Scala中默认的可变map和set都是基于Hash Table的。你也可以直接用[mutable.HashSet](http://www.scala-lang.org/api/2.10.0/scala/collection/mutable/HashSet.html) 和 [mutable.HashMap](http://www.scala-lang.org/api/2.10.0/scala/collection/mutable/HashMap.html) 来访问它们。 - -Hash Set 和 Map 的使用和其他的Set和Map是一样的。这里有一些简单的例子: - - scala> val map = scala.collection.mutable.HashMap.empty[Int,String] - map: scala.collection.mutable.HashMap[Int,String] = Map() - scala> map += (1 -> "make a web site") - res42: map.type = Map(1 -> make a web site) - scala> map += (3 -> "profit!") - res43: map.type = Map(1 -> make a web site, 3 -> profit!) - scala> map(1) - res44: String = make a web site - scala> map contains 2 - res46: Boolean = false - -Hash Table的迭代并不是按特定的顺序进行的。它是按任何可能的顺序,依次处理底层数组的数据。为了保证迭代的次序,可以使用一个Linked Hash Map 或 Set 来做为替代。Linked Hash Map 或 Set 像标准的Hash Map 或 Set一样,只不过它包含了一个Linked List,其中的元素按添加的顺序排列。在这种容器中的迭代都是具有相同的顺序,就是按照元素最初被添加的顺序进行迭代。 - -## Weak Hash Maps - -Weak Hash Map 是一种特殊的Hash Map,垃圾回收器会忽略从Map到存储在其内部的Key值的链接。这也就是说,当一个key不再被引用的时候,这个键和对应的值会从map中消失。Weak Hash Map 可以用来处理缓存,比如当一个方法被同一个键值重新调用时,你想重用这个大开销的方法返回值。如果Key值和方法返回值存储在一个常规的Hash Map里,Map会无限制的扩展,Key值也永远不会被垃圾回收器回收。用Weak Hash Map会避免这个问题。一旦有一个Key对象不再被引用,那它的实体会从Weak Hash Map中删除。在Scala中,[WeakHashMap](http://www.scala-lang.org/api/2.10.0/scala/collection/mutable/WeakHashMap.html)类是Weak Hash Map的实现类,封装了底层的Java实现类`java.util.WeakHashMap`。 - -## Concurrent Maps - -Concurrent Map可以同时被多个线程访问。除了[Map](http://www.scala-lang.org/api/2.10.0/scala/collection/Map.html)的通用方法,它提供了下列的原子方法: - -### Concurrent Map类中的方法: - -|WHAT IT IS | WHAT IT DOES | -|-----------------------|----------------------| -|m putIfAbsent(k, v) | 添加 键/值 绑定 k -> m ,如果k在m中没有被定义过 | -|m remove (k, v) | 如果当前 k 映射于 v,删除k对应的实体。 | -|m replace (k, old, new) | 如果k先前绑定的是old,则把键k 关联的值替换为new。 | -|m replace (k, v) | 如果k先前绑定的是其他值,则把键k对应的值替换为v | - - -`ConcurrentMap`体现了Scala容器库的特性。目前,它的实现类只有Java的`java.util.concurrent.ConcurrentMap`, 它可以用[standard Java/Scala collection conversions](http://docs.scala-lang.org/overviews/collections/conversions-between-java-and-scala-collections.html)(标准的java/Scala容器转换器)来自动转换成一个Scala map。 - -## Mutable Bitsets - -一个类型为[mutable.BitSet](http://www.scala-lang.org/api/2.10.0/scala/collection/mutable/BitSet.html)的可变bit集合和不可变的bit集合很相似,它只是做了适当的修改。Mutable bit sets在更新的操作上比不可变bit set 效率稍高,因为它不必复制没有发生变化的 Long值。 - - scala> val bits = scala.collection.mutable.BitSet.empty - bits: scala.collection.mutable.BitSet = BitSet() - scala> bits += 1 - res49: bits.type = BitSet(1) - scala> bits += 3 - res50: bits.type = BitSet(1, 3) - scala> bits - res51: scala.collection.mutable.BitSet = BitSet(1, 3) - diff --git a/zh-cn/overviews/collections/conversions-between-java-and-scala-collections.md b/zh-cn/overviews/collections/conversions-between-java-and-scala-collections.md deleted file mode 100644 index 039cec90eb..0000000000 --- a/zh-cn/overviews/collections/conversions-between-java-and-scala-collections.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -layout: overview-large -title: Java和Scala容器的转换 - -discourse: false - -partof: collections -num: 17 -language: zh-cn ---- - - -和Scala一样,Java同样提供了丰富的容器库,Scala和Java容器库有很多相似点,例如,他们都包含迭代器、可迭代结构、集合、 映射和序列。但是他们有一个重要的区别。Scala的容器库特别强调不可变性,因此提供了大量的新方法将一个容器变换成一个新的容器。 - -某些时候,你需要将一种容器类型转换成另外一种类型。例如,你可能想要像访问Scala容器一样访问某个Java容器,或者你可能想将一个Scala容器像Java容器一样传递给某个Java方法。在Scala中,这是很容易的,因为Scala提供了大量的方法来隐式转换所有主要的Java和Scala容器类型。其中提供了如下的双向类型转换: - - Iterator <=> java.util.Iterator - Iterator <=> java.util.Enumeration - Iterable <=> java.lang.Iterable - Iterable <=> java.util.Collection - mutable.Buffer <=> java.util.List - mutable.Set <=> java.util.Set - mutable.Map <=> java.util.Map - mutable.ConcurrentMap <=> java.util.concurrent.ConcurrentMap - -使用这些转换很简单,只需从JavaConversions对象中import它们即可。 - - scala> import collection.JavaConversions._ - import collection.Java.Conversions._ - -import之后,就可以在Scala容器和与之对应的Java容器之间进行隐式转换了 - - scala> import collection.mutable._ - import collection.mutable._ - scala> val jul: java.util.List[Int] = ArrayBuffer(1, 2, 3) - jul: java.util.List[Int] = [1, 2, 3] - scala> val buf: Seq[Int] = jul - buf: scala.collection.mutable.Seq[Int] = ArrayBuffer(1, 2, 3) - scala> val m: java.util.Map[String, Int] = HashMap("abc" -> 1, "hello" -> 2) - m: java.util.Map[String, Int] = {hello=2, abc=1} - -在Scala内部,这些转换是通过一系列“包装”对象完成的,这些对象会将相应的方法调用转发至底层的容器对象。所以容器不会在Java和Scala之间拷贝来拷贝去。一个值得注意的特性是,如果你将一个Java容器转换成其对应的Scala容器,然后再将其转换回同样的Java容器,最终得到的是一个和一开始完全相同的容器对象(译注:这里的相同意味着这两个对象实际上是指向同一片内存区域的引用,容器转换过程中没有任何的拷贝发生)。 - -还有一些Scala容器类型可以转换成对应的Java类型,但是并没有将相应的Java类型转换成Scala类型的能力,它们是: - - Seq => java.util.List - mutable.Seq => java.util.List - Set => java.util.Set - Map => java.util.Map - -因为Java并未区分可变容器不可变容器类型,所以,虽然能将`scala.immutable.List`转换成`java.util.List`,但所有的修改操作都会抛出“UnsupportedOperationException”。参见下例: - - scala> jul = List(1, 2, 3) - jul: java.util.List[Int] = [1, 2, 3] - scala> jul.add(7) - java.lang.UnsupportedOperationException - at java.util.AbstractList.add(AbstractList.java:131) - diff --git a/zh-cn/overviews/collections/creating-collections-from-scratch.md b/zh-cn/overviews/collections/creating-collections-from-scratch.md deleted file mode 100644 index d0b2993a0d..0000000000 --- a/zh-cn/overviews/collections/creating-collections-from-scratch.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -layout: overview-large -title: 从头定义新容器 - -discourse: false - -partof: collections -num: 16 -language: zh-cn ---- - - -我们已经知道`List(1, 2, 3)`可以创建出含有三个元素的列表,用`Map('A' -> 1, 'C' -> 2)`可以创建含有两对绑定的映射。实际上各种Scala容器都支持这一功能。任意容器的名字后面都可以加上一对带参数列表的括号,进而生成一个以这些参数为元素的新容器。不妨再看一些例子: - - Traversable() // 一个空的Traversable对象 - List() // 空列表 - List(1.0, 2.0) // 一个以1.0、2.0为元素的列表 - Vector(1.0, 2.0) // 一个以1.0、2.0为元素的Vector - Iterator(1, 2, 3) // 一个迭代器,可返回三个整数 - Set(dog, cat, bird) // 一个包含三个动物的集合 - HashSet(dog, cat, bird) // 一个包含三个同样动物的HashSet - Map('a' -> 7, 'b' -> 0) // 一个将字符映射到整数的Map - -实际上,上述每个例子都被“暗地里”转换成了对某个对象的apply方法的调用。例如,上述第三行会展开成如下形式: - - List.apply(1.0, 2.0) - -可见,这里调用的是List类的伴生对象的apply方法。该方法可以接受任意多个参数,并将这些参数作为元素,生成一个新的列表。在Scala标准库中,无论是List、Stream、Vector等具体的实现类还是Seq、Set、Traversable等抽象基类,每个容器类都伴一个带apply方法的伴生对象。针对后者,调用apply方法将得到对应抽象基类的某个默认实现,例如: - - scala > List(1,2,3) - res17: List[Int] = List(1, 2, 3) - scala> Traversable(1, 2, 3) - res18: Traversable[Int] = List(1, 2, 3) - scala> mutable.Traversable(1, 2, 3) - res19: scala.collection.mutable.Traversable[Int] = ArrayBuffer(1, 2, 3) - -除了apply方法,每个容器类的伴生对象还定义了一个名为empty的成员方法,该方法返回一个空容器。也就是说,`List.empty`可以代替`List()`,`Map.empty`可以代替`Map()`,等等。 - -Seq的子类同样在它们伴生对象中提供了工厂方法,总结如下表。简而言之,有这么一些: - -concat,将任意多个Traversable容器串联起来 -fill 和 tabulate,用于生成一维或者多维序列,并用给定的初值或打表函数来初始化。 -range,用于生成步长为step的整型序列,并且iterate,将某个函数反复应用于某个初始元素,从而产生一个序列。 - -## 序列的工厂方法 - -| WHAT IT IS | WHAT IT DOES | -|-------------------|---------------------| -| S.empty | 空序列 | -| S(x, y, z) | 一个包含x、y、z的序列 | -| S.concat(xs, ys, zs) | 将xs、ys、zs串街起来形成一个新序列。 | -| S.fill(n) {e} | 以表达式e的结果为初值生成一个长度为n的序列。 | -| S.fill(m, n){e} | 以表达式e的结果为初值生成一个维度为m x n的序列(还有更高维度的版本) | -| S.tabulate(n) {f} | 生成一个厂素为n、第i个元素为f(i)的序列。 | -| S.tabulate(m, n){f} | 生成一个维度为m x n,第(i, j)个元素为f(i, j)的序列(还有更高维度的版本)。 | -| S.range(start, end) | start, start + 1, ... end-1的序列。(译注:注意始左闭右开区间) | -| S.range(start, end, step) | 生成以start为起始元素、step为步长、最大值不超过end的递增序列(左闭右开)。 | -| S.iterate(x, n)(f) | 生成一个长度为n的序列,其元素值分别为x、f(x)、f(f(x))、…… | - diff --git a/zh-cn/overviews/collections/equality.md b/zh-cn/overviews/collections/equality.md deleted file mode 100644 index 63ebeaec8c..0000000000 --- a/zh-cn/overviews/collections/equality.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -layout: overview-large -title: 等价性 - -discourse: false - -partof: collections -num: 13 -language: zh-cn ---- - - -容器库有标准的等价性和散列法。这个想法的第一步是将容器划分为集合,映射和序列。不同范畴的容器总是不相等的。例如,即使包含相同的元素,`Set(1, 2, 3)` 与 `List(1, 2, 3)` 不等价。另一方面,在同一范畴下的容器是相等的,当且仅当它们具有相同的元素(对于序列:元素要相同,顺序要相同)。例如`List(1, 2, 3) == Vector(1, 2, 3)`, `HashSet(1, 2) == TreeSet(2, 1)`。 - -一个容器可变与否对等价性校验没有任何影响。对于一个可变容器,在执行等价性测试的同时,你可以简单地思考下它的当前元素。意思是,一个可变容器可能在不同时间等价于不同容器,这是由增加或移除了哪些元素所决定的。当你使用可变容器作为一个hashmap的键时,这将是一个潜在的陷阱。例如: - - scala> import collection.mutable.{HashMap, ArrayBuffer} - import collection.mutable.{HashMap, ArrayBuffer} - scala> val buf = ArrayBuffer(1, 2, 3) - buf: scala.collection.mutable.ArrayBuffer[Int] = - ArrayBuffer(1, 2, 3) - scala> val map = HashMap(buf -> 3) - map: scala.collection.mutable.HashMap[scala.collection。 - mutable.ArrayBuffer[Int],Int] = Map((ArrayBuffer(1, 2, 3),3)) - scala> map(buf) - res13: Int = 3 - scala> buf(0) += 1 - scala> map(buf) - java.util.NoSuchElementException: key not found: - ArrayBuffer(2, 2, 3) - -在这个例子中,由于数组xs的散列码已经在倒数第二行发生了改变,最后一行的选择操作将很有可能失败。因此,基于散列码的查找函数将会查找另一个位置,而不是xs所存储的位置。 diff --git a/zh-cn/overviews/collections/introduction.md b/zh-cn/overviews/collections/introduction.md deleted file mode 100644 index ff6b492c46..0000000000 --- a/zh-cn/overviews/collections/introduction.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -layout: overview-large -title: 简介 - -discourse: false - -partof: collections -num: 1 -language: zh-cn ---- - -**Martin Odersky 和 Lex Spoon** - -在许多人看来,新的集合框架是Scala 2.8中最显著的改进。此前Scala也有集合(实际上新框架大部分地兼容了旧框架),但2.8中的集合类在通用性、一致性和功能的丰富性上更胜一筹。 - -即使粗看上去集合新增的内容比较微妙,但这些改动却足以对开发者的编程风格造成深远的影响。实际上,就好像你从事一个高层次的程序,而此程序的基本的构建块的元素被整个集合代替。适应这种新的编程风格需要一个过程。幸运的是,新的Scala集合得益于几个新的几个漂亮的属性,从而它们易于使用、简洁、安全、快速、通用。 - -- **易用性**:由20-50个方法的小词汇量,足以在几个操作内解决大部分的集合问题。没有必要被复杂的循环或递归所困扰。持久化的集合类和无副作用的操作意味着你不必担心新数据会意外的破坏已经存在的集合。迭代器和集合更新之间的干扰会被消除! - -- **简洁**:你可以通过单独的一个词来执行一个或多个循环。你也可以用轻量级的语法和组合轻松地快速表达功能性的操作,以致结果看起来像一个自定义的代数。 - -- **安全**:这一问题必须被熟练的理解,Scala集合的静态类型和函数性质意味着你在编译的时候就可以捕获绝大多数错误。原因是(1)、集合操作本身被大量的使用,是测试良好的。(2)、集合的用法要求输入和输出要显式作为函数参数和结果。(3)这些显式的输入输出会受到静态类型检查。最终,绝大部分的误用将会显示为类型错误。这是很少见的的有数百行的程序的首次运行。 - -- **快速**:集合操作已经在类库里是调整和优化过。因此,使用集合通常是比较高效的。你也许能够通过手动调整数据结构和操作来做的好一点,但是你也可能会由于一些次优的实现而做的更糟。不仅如此,集合类最近已经能支持在多核处理器上并行运算。并行集合类支持有序集合的相同操作,因此没有新的操作需要学习也没有代码需要重写。你可以简单地通过调用标准方法来把有序集合优化为一个并行集合。 - -- **通用**:集合类提供了相同的操作在一些类型上,确实如此。所以,你可以用相当少的词汇完成大量的工作。例如,一个字符串从概念上讲就是一个字符序列。因此,在Scala集合类中,字符串支持所有的序列操作。同样的,数组也是支持的。 - -例子:这有一行代码演示了Scala集合类的先进性。 - - val (minors, adults) = people partition (_.age < 18) - -这个操作是清晰的:通过他们的age(年龄)把这个集合people拆分到到miors(未成年人)和adults(成年人)中。由于这个拆分方法是被定义在根集合类型TraversableLike类中,这部分代码服务于任何类型的集合,包括数组。例子运行的结果就是miors和adults集合与people集合的类型相同。 - -这个代码比使用传统的类运行一到三个循环更加简明(三个循环处理一个数组,是由于中间结果需要有其它地方做缓存)。一旦你已经学习了基本的集合词汇,你将也发现写这种代码显式的循环更简单和更安全。而且,这个拆分操作是非常快速,并且在多核处理器上采用并行集合类达到更快的速度(并行集合类已经Scala 2.9的一部分发布)。 - -本文档从一个用户的角度出发,提供了一个关于Scala集合类的 API的深入讨论。它将带你体验它定义的所有的基础类和方法。 - diff --git a/zh-cn/overviews/collections/iterators.md b/zh-cn/overviews/collections/iterators.md deleted file mode 100644 index 1b92b48cd9..0000000000 --- a/zh-cn/overviews/collections/iterators.md +++ /dev/null @@ -1,175 +0,0 @@ ---- -layout: overview-large -title: Iterators - -discourse: false - -partof: collections -num: 15 -language: zh-cn ---- - -迭代器不是一个容器,更确切的说是逐一访问容器内元素的方法。迭代器it的两个基本操作是next和hasNext。调用it.next()会返回迭代器的下一个元素,并且更新迭代器的状态。在同一个迭代器上再次调用next,会产生一个新元素来覆盖之前返回的元素。如果没有元素可返回,调用next方法会抛出一个NoSuchElementException异常。你可以调用[迭代器]的hasNext方法来查询容器中是否有下一个元素可供返回。 - -让迭代器it逐个返回所有元素最简单的方法是使用while循环: - - while (it.hasNext) - println(it.next()) - -Scala为Traversable, Iterable和Seq类中的迭代器提供了许多类似的方法。比如:这些类提供了foreach方法以便在迭代器返回的每个元素上执行指定的程序。使用foreach方法可以将上面的循环缩写为: - - it foreach println - -与往常一样,for表达式可以作为foreach、map、withFilter和flatMap表达式的替代语法,所以另一种打印出迭代器返回的所有元素的方式会是这样: - - for (elem <- it) println(elem) - -在迭代器或traversable容器中调用foreach方法的最大区别是:当在迭代器中完成调用foreach方法后会将迭代器保留在最后一个元素的位置。所以在这个迭代器上再次调用next方法时会抛出NoSuchElementException异常。与此不同的是,当在容器中调用foreach方法后,容器中的元素数量不会变化(除非被传递进来的函数删除了元素,但不赞成这样做,因为这会导致意想不到的结果)。 - -迭代器的其他操作跟Traversable一样具有相同的特性。例如:迭代器提供了map方法,该方法会返回一个新的迭代器: - - scala> val it = Iterator("a", "number", "of", "words") - it: Iterator[java.lang.String] = non-empty iterator - scala> it.map(_.length) - res1: Iterator[Int] = non-empty iterator - scala> res1 foreach println - 1 - 6 - 2 - 5 - scala> it.next() - java.util.NoSuchElementException: next on empty iterator - -如你所见,在调用了it.map方法后,迭代器it移动到了最后一个元素的位置。 - -另一个例子是关于dropWhile方法,它用来在迭代器中找到第一个具有某些属性的元素。比如:在上文所说的迭代器中找到第一个具有两个以上字符的单词,你可以这样写: - - scala> val it = Iterator("a", "number", "of", "words") - it: Iterator[java.lang.String] = non-empty iterator - scala> it dropWhile (_.length < 2) - res4: Iterator[java.lang.String] = non-empty iterator - scala> it.next() - res5: java.lang.String = number - -再次注意it在调用dropWhile方法后发生的变化:现在it指向了list中的第二个单词"number"。实际上,it和dropWhile返回的结果res4将会返回相同的元素序列。 - -只有一个标准操作允许重用同一个迭代器: - - val (it1, it2) = it.duplicate - -这个操作返回两个迭代器,每个都相当于迭代器it的完全拷贝。这两个iterator相互独立;一个发生变化不会影响到另外一个。相比之下,原来的迭代器it则被指定到元素的末端而无法再次使用。 - -总的来说,如果调用完迭代器的方法后就不再访问它,那么迭代器的行为方式与容器是比较相像的。Scala容器库中的抽象类TraversableOnce使这一特质更加明显,它是 Traversable 和 Iterator 的公共父类。顾名思义,TraversableOnce 对象可以用foreach来遍历,但是没有指定该对象遍历之后的状态。如果TraversableOnce对象是一个迭代器,它遍历之后会位于最后一个元素,但如果是Traversable则不会发生变化。TraversableOnce的一个通常用法是作为一个方法的参数类型,传递的参数既可以是迭代器,也可以是traversable。Traversable类中的追加方法++就是一个例子。它有一个TraversableOnce 类型的参数,所以你要追加的元素既可以来自于迭代器也可以来自于traversable容器。 - -下面汇总了迭代器的所有操作。 - -## Iterator类的操作 - -| WHAT IT IS | WHAT IT DOES | -|--------------|---------------| -| 抽象方法: | | -| it.next() | 返回迭代器中的下一个元素,并将位置移动至该元素之后。 | -| it.hasNext | 如果还有可返回的元素,返回true。 | -| 变量: | | -| it.buffered | 被缓存的迭代器返回it的所有元素。 | -| it grouped size | 迭代器会生成由it返回元素组成的定长序列块。 | -| xs sliding size | 迭代器会生成由it返回元素组成的定长滑动窗口序列。 | -| 复制: | | -| it.duplicate | 会生成两个能分别返回it所有元素的迭代器。 | -| 加法: | | -| it ++ jt | 迭代器会返回迭代器it的所有元素,并且后面会附加迭代器jt的所有元素。 | -| it padTo (len, x) | 首先返回it的所有元素,追加拷贝x直到长度达到len。 | -| Maps: | | -| it map f | 将it中的每个元素传入函数f后的结果生成新的迭代器。 | -| it flatMap f | 针对it指向的序列中的每个元素应用函数f,并返回指向结果序列的迭代器。 | -| it collect f | 针对it所指向的序列中的每一个在偏函数f上有定义的元素应用f,并返回指向结果序列的迭代器。 | -| 转换(Conversions): | | -| it.toArray | 将it指向的所有元素归入数组并返回。 | -| it.toList | 把it指向的所有元素归入列表并返回 | -| it.toIterable | 把it指向的所有元素归入一个Iterable容器并返回。 | -| it.toSeq | 将it指向的所有元素归入一个Seq容器并返回。 | -| it.toIndexedSeq | 将it指向的所有元素归入一个IndexedSeq容器并返回。 | -| it.toStream | 将it指向的所有元素归入一个Stream容器并返回。 | -| it.toSet | 将it指向的所有元素归入一个Set并返回。 | -| it.toMap | 将it指向的所有键值对归入一个Map并返回。 | -| 拷贝: | | -| it copyToBuffer buf | 将it指向的所有元素拷贝至缓冲区buf。| -| it copyToArray(arr, s, n) | 将it指向的从第s个元素开始的n个元素拷贝到数组arr,其中后两个参数是可选的。 | -| 尺寸信息: | | -| it.isEmpty | 检查it是否为空(与hasNext相反)。 | -| it.nonEmpty | 检查容器中是否包含元素(相当于 hasNext)。 | -| it.size | it可返回的元素数量。注意:这个操作会将it置于终点! | -| it.length | 与it.size相同。 | -| it.hasDefiniteSize | 如果it指向的元素个数有限则返回true(缺省等同于isEmpty) | -| 按下标检索元素: | | -| it find p | 返回第一个满足p的元素或None。注意:如果找到满足条件的元素,迭代器会被置于该元素之后;如果没有找到,会被置于终点。 | -| it indexOf x | 返回it指向的元素中index等于x的第一个元素。注意:迭代器会越过这个元素。 | -| it indexWhere p | 返回it指向的元素中下标满足条件p的元素。注意:迭代器会越过这个元素。 | -| 子迭代器: | | -| it take n | 返回一个包含it指向的前n个元素的新迭代器。注意:it的位置会步进至第n个元素之后,如果it指向的元素数不足n个,迭代器将指向终点。 | -| it drop n | 返回一个指向it所指位置之后第n+1个元素的新迭代器。注意:it将步进至相同位置。 | -| it slice (m,n) | 返回一个新的迭代器,指向it所指向的序列中从开始于第m个元素、结束于第n个元素的片段。 | -| it takeWhile p | 返回一个迭代器,指代从it开始到第一个不满足条件p的元素为止的片段。 | -| it dropWhile p | 返回一个新的迭代器,指向it所指元素中第一个不满足条件p的元素开始直至终点的所有元素。 | -| it filter p | 返回一个新迭代器 ,指向it所指元素中所有满足条件p的元素。 | -| it withFilter p | 同it filter p 一样,用于for表达式。 | -| it filterNot p | 返回一个迭代器,指向it所指元素中不满足条件p的元素。 | -| 拆分(Subdivision): | | -| it partition p | 将it分为两个迭代器;一个指向it所指元素中满足条件谓词p的元素,另一个指向不满足条件谓词p的元素。 | -| 条件元素(Element Conditions): | | -| it forall p | 返回一个布尔值,指明it所指元素是否都满足p。 | -| it exists p | 返回一个布尔值,指明it所指元素中是否存在满足p的元素。 | -| it count p | 返回it所指元素中满足条件谓词p的元素总数。 | -| 折叠(Fold): | | -| (z /: it)(op) | 自左向右在it所指元素的相邻元素间应用二元操作op,初始值为z。| -| (it :\ z)(op) | 自右向左在it所指元素的相邻元素间应用二元操作op,初始值为z。 | -| it.foldLeft(z)(op) | 与(z /: it)(op)相同。 | -| it.foldRight(z)(op) | 与(it :\ z)(op)相同。 | -| it reduceLeft op | 自左向右对非空迭代器it所指元素的相邻元素间应用二元操作op。 | -| it reduceRight op | 自右向左对非空迭代器it所指元素的相邻元素间应用二元操作op。 | -| 特殊折叠(Specific Fold): | | -| it.sum | 返回迭代器it所指数值型元素的和。 | -| it.product | 返回迭代器it所指数值型元素的积。 | -| it.min | 返回迭代器it所指元素中最小的元素。 | -| it.max | 返回迭代器it所指元素中最大的元素。 | -| 拉链方法(Zippers): | | -| it zip jt | 返回一个新迭代器,指向分别由it和jt所指元素一一对应而成的二元组序列。 | -| it zipAll (jt, x, y) | 返回一个新迭代器,指向分别由it和jt所指元素一一对应而成的二元组序列,长度较短的迭代器会被追加元素x或y,以匹配较长的迭代器。 | -| it.zipWithIndex | 返回一个迭代器,指向由it中的元素及其下标共同构成的二元组序列。 | -| 更新: | | -| it patch (i, jt, r) | 由it返回一个新迭代器,其中自第i个元素开始的r个元素被迭代器jt所指元素替换。 | -| 比对: | | -| it sameElements jt | 判断迭代器it和jt是否依次返回相同元素注意:it和jt中至少有一个会步进到终点。 | -|字符串(String): | | -| it addString (b, start, sep, end) | 添加一个字符串到StringBuilder b,该字符串以start为前缀、以end为后缀,中间是以sep分隔的it所指向的所有元素。start、end和sep都是可选项。 | -| it mkString (start, sep, end) | 将it所指所有元素转换成以start为前缀、end为后缀、按sep分隔的字符串。start、sep、end都是可选项。 | - -## 带缓冲的迭代器 - -有时候你可能需要一个支持“预览”功能的迭代器,这样我们既可以看到下一个待返回的元素,又不会令迭代器跨过这个元素。比如有这样一个任务,把迭代器所指元素中的非空元素转化成字符串。你可能会这样写: - - def skipEmptyWordsNOT(it: Iterator[String]) = - while (it.next().isEmpty) {} - -但仔细看看这段代码,就会发现明显的错误:代码确实会跳过空字符串,但同时它也跳过了第一个非空字符串! - -要解决这个问题,可以使用带缓冲能力的迭代器。[BufferedIterator]类是[Iterator]的子类,提供了一个附加的方法,head。在BufferedIterator中调用head 会返回它指向的第一个元素,但是不会令迭代器步进。使用BufferedIterator,跳过空字符串的方法可以写成下面这样: - - def skipEmptyWords(it: BufferedIterator[String]) = - while (it.head.isEmpty) { it.next() } - -通过调用buffered方法,所有迭代器都可以转换成BufferedIterator。参见下例: - - scala> val it = Iterator(1, 2, 3, 4) - it: Iterator[Int] = non-empty iterator - scala> val bit = it.buffered - bit: java.lang.Object with scala.collection. - BufferedIterator[Int] = non-empty iterator - scala> bit.head - res10: Int = 1 - scala> bit.next() - res11: Int = 1 - scala> bit.next() - res11: Int = 2 - -注意,调用`BufferedIterator bit`的head方法不会令它步进。因此接下来的`bit.next()`返回的元素跟`bit.head`相同。 diff --git a/zh-cn/overviews/collections/maps.md b/zh-cn/overviews/collections/maps.md deleted file mode 100644 index 4471f22380..0000000000 --- a/zh-cn/overviews/collections/maps.md +++ /dev/null @@ -1,168 +0,0 @@ ---- -layout: overview-large -title: 映射 - -discourse: false - -partof: collections -num: 7 -language: zh-cn ---- - - -映射(Map)是一种可迭代的键值对结构(也称映射或关联)。Scala的Predef类提供了隐式转换,允许使用另一种语法:`key -> value`,来代替`(key, value)`。如:`Map("x" -> 24, "y" -> 25, "z" -> 26)`等同于`Map(("x", 24), ("y", 25), ("z", 26))`,却更易于阅读。 - -映射(Map)的基本操作与集合(Set)类似。下面的表格分类总结了这些操作: - -- **查询类操作:**apply、get、getOrElse、contains和DefinedAt。它们都是根据主键获取对应的值映射操作。例如:def get(key): Option[Value]。“m get key” 返回m中是否用包含了key值。如果包含了,则返回对应value的Some类型值。否则,返回None。这些映射中也包括了apply方法,该方法直接返回主键对应的值。apply方法不会对值进行Option封装。如果该主键不存在,则会抛出异常。 -- **添加及更新类操作:**+、++、updated,这些映射操作允许你添加一个新的绑定或更改现有的绑定。 -- **删除类操作:**-、--,从一个映射(Map)中移除一个绑定。 -- **子集类操作:**keys、keySet、keysIterator、values、valuesIterator,可以以不同形式返回映射的键和值。 -- **filterKeys、mapValues等**变换用于对现有映射中的绑定进行过滤和变换,进而生成新的映射。 - -## Map类的操作 - -| WHAT IT IS | WHAT IT DOES | -|---------------|---------------------| -| **查询:** | | -| ms get k | 返回一个Option,其中包含和键k关联的值。若k不存在,则返回None。 | -| ms(k) | (完整写法是ms apply k)返回和键k关联的值。若k不存在,则抛出异常。 | -| ms getOrElse (k, d) | 返回和键k关联的值。若k不存在,则返回默认值d。 | -| ms contains k | 检查ms是否包含与键k相关联的映射。 | -| ms isDefinedAt k | 同contains。 | -| **添加及更新:** | | -| ms + (k -> v) | 返回一个同时包含ms中所有键值对及从k到v的键值对k -> v的新映射。 | -| ms + (k -> v, l -> w) | 返回一个同时包含ms中所有键值对及所有给定的键值对的新映射。 | -| ms ++ kvs | 返回一个同时包含ms中所有键值对及kvs中的所有键值对的新映射。 | -| ms updated (k, v) | 同ms + (k -> v)。 | -| **移除:** | | -| ms - k | 返回一个包含ms中除键k以外的所有映射关系的映射。 | -| ms - (k, 1, m) | 返回一个滤除了ms中与所有给定的键相关联的映射关系的新映射。 | -| ms -- ks | 返回一个滤除了ms中与ks中给出的键相关联的映射关系的新映射。 | -| **子容器(Subcollection):** | | -| ms.keys | 返回一个用于包含ms中所有键的iterable对象(译注:请注意iterable对象与iterator的区别) | -| ms.keySet | 返回一个包含ms中所有的键的集合。 | -| ms.keyIterator | 返回一个用于遍历ms中所有键的迭代器。 | -| ms.values | 返回一个包含ms中所有值的iterable对象。 | -| ms.valuesIterator | 返回一个用于遍历ms中所有值的迭代器。 | -| **变换:** | | -| ms filterKeys p | 一个映射视图(Map View),其包含一些ms中的映射,且这些映射的键满足条件p。用条件谓词p过滤ms中所有的键,返回一个仅包含与过滤出的键值对的映射视图(view)。| -|ms mapValues f | 用f将ms中每一个键值对的值转换成一个新的值,进而返回一个包含所有新键值对的映射视图(view)。| - - -可变映射(Map)还支持下表中列出的操作。 - -## mutable.Map类中的操作 - -| WHAT IT IS | WHAT IT DOES | -|-------------------------|-------------------------| -| **添加及更新** | | -| ms(k) = v | (完整形式为ms.update(x, v))。向映射ms中新增一个以k为键、以v为值的映射关系,ms先前包含的以k为值的映射关系将被覆盖。 | -| ms += (k -> v) | 向映射ms增加一个以k为键、以v为值的映射关系,并返回ms自身。 | -| ms += (k -> v, l -> w) | 向映射ms中增加给定的多个映射关系,并返回ms自身。 | -| ms ++= kvs | 向映射ms增加kvs中的所有映射关系,并返回ms自身。 | -| ms put (k, v) | 向映射ms增加一个以k为键、以v为值的映射,并返回一个Option,其中可能包含此前与k相关联的值。 | -| ms getOrElseUpdate (k, d) | 如果ms中存在键k,则返回键k的值。否则向ms中新增映射关系k -> v并返回d。 | -| **移除:** | | -| ms -= k | 从映射ms中删除以k为键的映射关系,并返回ms自身。 | -| ms -= (k, l, m) | 从映射ms中删除与给定的各个键相关联的映射关系,并返回ms自身。 | -| ms --= ks | 从映射ms中删除与ks给定的各个键相关联的映射关系,并返回ms自身。 | -| ms remove k | 从ms中移除以k为键的映射关系,并返回一个Option,其可能包含之前与k相关联的值。 | -| ms retain p | 仅保留ms中键满足条件谓词p的映射关系。 | -| ms.clear() | 删除ms中的所有映射关系 | -| **变换:** | | -| ms transform f | 以函数f转换ms中所有键值对(译注:原文比较含糊,transform中参数f的类型是(A, B) => B,即对ms中的所有键值对调用f,得到一个新的值,并用该值替换原键值对中的值)。 | -| **克隆:** | | -| ms.clone | 返回一个新的可变映射(Map),其中包含与ms相同的映射关系。 | - -映射(Map)的添加和删除操作与集合(Set)的相关操作相同。同集合(Set)操作一样,可变映射(mutable maps)也支持非破坏性(non-destructive)修改操作+、-、和updated。但是这些操作涉及到可变映射的复制,因此较少被使用。而利用两种变形`m(key) = value和m += (key -> value)`, 我们可以“原地”修改可变映射m。此外,存还有一种变形`m put (key, value)`,该调用返回一个Option值,其中包含此前与键相关联的值,如果不存在这样的值,则返回None。 - -getOrElseUpdate特别适合用于访问用作缓存的映射(Map)。假设调用函数f开销巨大: - - scala> def f(x: String) = { - println("taking my time."); sleep(100) - x.reverse } - f: (x: String)String - -此外,再假设f没有副作用,即反复以相同参数调用f,得到的结果都相同。那么,我们就可以将之前的调用参数和计算结果保存在一个映射(Map)内,今后仅在映射中查不到对应参数的情况下实际调用f,这样就可以节约时间。这个映射便可以认为是函数f的缓存。 - - val cache = collection.mutable.Map[String, String]() - cache: scala.collection.mutable.Map[String,String] = Map() - -现在,我们可以写出一个更高效的带缓存的函数f: - - scala> def cachedF(s: String) = cache.getOrElseUpdate(s, f(s)) - cachedF: (s: String)String - scala> cachedF("abc") - -稍等片刻。 - - res3: String = cba - scala> cachedF("abc") - res4: String = cba - -注意,getOrElseUpdate的第2个参数是“按名称(by-name)"传递的,所以,仅当在缓存映射中找不到第1个参数,而getOrElseUpdate需要其第2个参数的值时,上述的f("abc")才会被执行。当然我们也可以利用Map的基本操作直接实现cachedF,但那样写就要冗长很多了。 - - def cachedF(arg: String) = cache get arg match { - case Some(result) => result - case None => - val result = f(x) - cache(arg) = result - result - } - -## 同步集合(Set)和映射(Map) - -无论什么样的Map实现,只需混入`SychronizedMap trait`,就可以得到对应的线程安全版的Map。例如,我们可以像下述代码那样在HashMap中混入SynchronizedMap。这个示例一上来先从`scala.colletion.mutable`包中import了两个trait:Map、SynchronizedMap,和一个类:HashMap。接下来,示例中定义了一个单例对象MapMaker,其中定义了一个方法makeMap。该方法的返回值类型是一个同时以String为键值类型的可变映射。 - - import scala.collection.mutable.{Map, - SynchronizedMap, HashMap} - object MapMaker { - def makeMap: Map[String, String] = { - new HashMap[String, String] with - SynchronizedMap[String, String] { - override def default(key: String) = - "Why do you want to know?" - } - } - } - -混入SynchronizedMap trait - -makeMap方法中的第1个语句构造了一个新的混入了SynchronizedMap trait的可变映射: - - new HashMap[String, String] with - SynchronizedMap[String, String] - -针对这段代码,Scala编译器会合成HashMap的一个混入了SynchronizedMap trait的子类,同时生成(并返回)该合成子类的一个实例。处于下面这段代码的缘故,这个合成类还覆写了default方法: - - override def default(key: String) = - "Why do you want to know?" - -当向某个Map查询给定的键所对应的值,而Map中不存在与该键相关联的值时,默认情况下会触发一个NoSuchElementException异常。不过,如果自定义一个Map类并覆写default方法,便可以针对不存在的键返回一个default方法返回的值。所以,编译器根据上述代码合成的HashMap子类在碰到不存在的键时将会反过来质问你“Why do you want to know?” - -makeMap方法返回的可变映射混入了 SynchronizedMap trait,因此可以用在多线程环境下。对该映射的每次访问都是同步的。以下示例展示的是从解释器内以单个线程访问该映射: - - scala> val capital = MapMaker.makeMap - capital: scala.collection.mutable.Map[String,String] = Map() - scala> capital ++ List("US" -> "Washington", - "France" -> "Paris", "Japan" -> "Tokyo") - res0: scala.collection.mutable.Map[String,String] = - Map(France -> Paris, US -> Washington, Japan -> Tokyo) - scala> capital("Japan") - res1: String = Tokyo - scala> capital("New Zealand") - res2: String = Why do you want to know? - scala> capital += ("New Zealand" -> "Wellington") - scala> capital("New Zealand") - res3: String = Wellington - -同步集合(synchronized set)的创建方法与同步映射(synchronized map)类似。例如,我们可以通过混入SynchronizedSet trait来创建同步哈希集: - - import scala.collection.mutable //导入包scala.collection.mutable - val synchroSet = - new mutable.HashSet[Int] with - mutable.SynchronizedSet[Int] - -最后,如有使用同步容器(synchronized collection)的需求,还可以考虑使用`java.util.concurrent`中提供的并发容器(concurrent collections)。 - diff --git a/zh-cn/overviews/collections/migrating-from-scala-27.md b/zh-cn/overviews/collections/migrating-from-scala-27.md deleted file mode 100644 index 364bdbc906..0000000000 --- a/zh-cn/overviews/collections/migrating-from-scala-27.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -layout: overview-large -title: Scala 2.7迁移指南 - -discourse: false - -partof: collections -num: 18 -outof: 18 -language: zh-cn ---- - - -现有应用中新旧Scala容器类型的移植基本上是自动的。只有几种情况需要特别注意。 - -Scala 2.7中容器的旧有功能基本上全部予以保留。某些功能被标记为deprecated,这意味着今后版本可能会删除它们。如果在Scala 2.8中使用这些方法,将会得到一个deprecation警告。在2.8下编译时,这些情况被视作迁移警告(migration warnings)。要得到完整的deprecation和迁移警告以及代码修改建议,请在编译时给Scala编译器scalac加上-deprecation和-Xmigration参数(注意,-Xmigration是扩展参数,因此以X开头)。你也可以将参数传给Scala REPL,从而在交互式环境中得到警告,例如: - - >scala -deprecation -Xmigration - Welcome to Scala version 2.8.0.final - 键入表达式来运行 - 键入 :help来看更多信息 - scala> val xs = List((1, 2), (3, 4)) - xs: List[(Int, Int)] = List((1, 2), (3, 4)) - scala> List.unzip(xs) - :7: warning: method unzip in object List is deprecated: use xs.unzip instead of List.unzip(xs) - List.unzip(xs) - ^ - res0: (List[Int], List[Int]) = (List(1, 3), List(2, 4)) - scala> xs.unzip - res1: (List[Int], List[Int]) = (List(1, 3), List(2, 4)) - scala> val m = xs.toMap - m: scala.collection.immutable.Map[Int, Int] = Map((1, 2), (3, 4)) - scala> m.keys - :8: warning: method keys in trait MapLike has changed semantics: - As of 2.8 keys returns Iterable[A] rather than Iterator[A]. - m.keys - ^ - res2: Iterable[Int] = Set(1, 3) - -老版本的库中有两个部分被整个移除,所以在deprecation警告中看不到它们。 - -scala.collection.jcl包被移除了。这个包试图在Scala中模拟某些Java的容器,但是该包破坏了Scala的一些对称性。绝大多数人,当他们需要Java容器的时候,他们会直接选用java.util。 -Scala 2.8通过JavaConversions对象提供了自动的在Java和Scala容器类型间转换的机制,这一机制替代了老的jcl包。 -各种投影操作被泛化整理成了视图。从实际情况来看,投影的用处并不大,因此受影响的代码应该不多。 -所以,如果你的代码用了jcl包或者投影(projections),你将不得不进行一些小的修改。 diff --git a/zh-cn/overviews/collections/overview.md b/zh-cn/overviews/collections/overview.md deleted file mode 100644 index e8af613b75..0000000000 --- a/zh-cn/overviews/collections/overview.md +++ /dev/null @@ -1,89 +0,0 @@ ---- -layout: overview-large -title: Mutable和Immutable集合 - -discourse: false - -partof: collections -num: 2 -language: zh-cn ---- - - -Scala 集合类系统地区分了可变的和不可变的集合。可变集合可以在适当的地方被更新或扩展。这意味着你可以修改,添加,移除一个集合的元素。而不可变集合类,相比之下,永远不会改变。不过,你仍然可以模拟添加,移除或更新操作。但是这些操作将在每一种情况下都返回一个新的集合,同时使原来的集合不发生改变。 - -所有的集合类都可以在包`scala.collection` 或`scala.collection.mutable`,`scala.collection.immutable`,`scala.collection.generic`中找到。客户端代码需要的大部分集合类都独立地存在于3种变体中,它们位于`scala.collection`, `scala.collection.immutable`, `scala.collection.mutable`包。每一种变体在可变性方面都有不同的特征。 - -`scala.collection.immutable`包是的集合类确保不被任何对象改变。例如一个集合创建之后将不会改变。因此,你可以相信一个事实,在不同的点访问同一个集合的值,你将总是得到相同的元素。。 - -`scala.collection.mutable`包的集合类则有一些操作可以修改集合。所以处理可变集合意味着你需要去理解哪些代码的修改会导致集合同时改变。 - -`scala.collection`包中的集合,既可以是可变的,也可以是不可变的。例如:[collection.IndexedSeq[T]](http://www.scala-lang.org/api/current/scala/collection/IndexedSeq.html)] 就是 [collection.immutable.IndexedSeq[T]](http://www.scala-lang.org/api/current/scala/collection/immutable/IndexedSeq.html) 和[collection.mutable.IndexedSeq[T]](http://www.scala-lang.org/api/current/scala/collection/mutable/IndexedSeq.html)这两类的超类。`scala.collection`包中的根集合类中定义了相同的接口作为不可变集合类,同时,`scala.collection.mutable`包中的可变集合类代表性的添加了一些有辅助作用的修改操作到这个immutable 接口。 - -根集合类与不可变集合类之间的区别是不可变集合类的客户端可以确保没有人可以修改集合。然而,根集合类的客户端仅保证不修改集合本身。即使这个集合类没有提供修改集合的静态操作,它仍然可能在运行时作为可变集合被其它客户端所修改。 - -默认情况下,Scala 一直采用不可变集合类。例如,如果你仅写了`Set` 而没有任何加前缀也没有从其它地方导入`Set`,你会得到一个不可变的`set`,另外如果你写迭代,你也会得到一个不可变的迭代集合类,这是由于这些类在从scala中导入的时候都是默认绑定的。为了得到可变的默认版本,你需要显式的声明`collection.mutable.Set`或`collection.mutable.Iterable`. - -一个有用的约定,如果你想要同时使用可变和不可变集合类,只导入collection.mutable包即可。 - - import scala.collection.mutable //导入包scala.collection.mutable - -然而,像没有前缀的Set这样的关键字, 仍然指的是一个不可变集合,然而`mutable.Set`指的是可变的副本(可变集合)。 - -集合树的最后一个包是`collection.generic`。这个包包含了集合的构建块。集合类延迟了`collection.generic`类中的部分操作实现,另一方面集合框架的用户需要引用`collection.generic`中类在异常情况中。 - -为了方便和向后兼容性,一些导入类型在包scala中有别名,所以你能通过简单的名字使用它们而不需要import。这有一个例子是List 类型,它可以用以下两种方法使用,如下: - - scala.collection.immutable.List // 这是它的定义位置 - scala.List //通过scala 包中的别名 - List // 因为scala._ - // 总是是被自动导入。 - -其它类型的别名有: [Traversable](http://www.scala-lang.org/api/current/scala/collection/Traversable.html), [Iterable](http://www.scala-lang.org/api/current/scala/collection/Iterable.html), [Seq](http://www.scala-lang.org/api/current/scala/collection/Seq.html), [IndexedSeq](http://www.scala-lang.org/api/current/scala/collection/IndexedSeq.html), [Iterator](http://www.scala-lang.org/api/current/scala/collection/Iterator.html), [Stream](http://www.scala-lang.org/api/current/scala/collection/immutable/Stream.html), [Vector](http://www.scala-lang.org/api/current/scala/collection/immutable/Vector.html), [StringBuilder](http://www.scala-lang.org/api/current/scala/collection/mutable/StringBuilder.html), [Range](http://www.scala-lang.org/api/current/scala/collection/immutable/Range.html)。 - -下面的图表显示了`scala.collection`包中所有的集合类。这些都是高级抽象类或特性,它们通常具备和不可变实现一样的可变实现。 - -[]({{ site.baseurl }}/resources/images/collections.png) - -下面的图表显示scala.collection.immutable中的所有集合类。 - -[]({{ site.baseurl }}/resources/images/collections.immutable.png) - -下面的图表显示scala.collection.mutable中的所有集合类。 - -[]({{ site.baseurl }}/resources/images/collections.mutable.png) - -(以上三个图表由Matthias生成, 来自decodified.com)。 - -## 集合API概述 - -大多数重要的集合类都被展示在了上表。而且这些类有很多的共性。例如,每一种集合都能用相同的语法创建,写法是集合类名紧跟着元素。 - - Traversable(1, 2, 3) - Iterable("x", "y", "z") - Map("x" -> 24, "y" -> 25, "z" -> 26) - Set(Color.red, Color.green, Color.blue) - SortedSet("hello", "world") - Buffer(x, y, z) - IndexedSeq(1.0, 2.0) - LinearSeq(a, b, c) - -相同的原则也应用于特殊的集合实现,例如: - - List(1, 2, 3) - HashMap("x" -> 24, "y" -> 25, "z" -> 26) - -所有这些集合类都通过相同的途径,用toString方法展示出来。 - -Traversable类提供了所有集合支持的API,同时,对于特殊类型也是有意义的。例如,Traversable类 的map方法会返回另一个Traversable对象作为结果,但是这个结果类型在子类中被重写了。例如,在一个List上调用map会又生成一个List,在Set上调用会再生成一个Set,以此类推。 - - scala> List(1, 2, 3) map (_ + 1) - res0: List[Int] = List(2, 3, 4) - scala> Set(1, 2, 3) map (_ * 2) - res0: Set[Int] = Set(2, 4, 6) - -在集合类库中,这种在任何地方都实现了的行为,被称之为返回类型一致原则。 - -大多数类在集合树中存在这于三种变体:root, mutable 和immutable。唯一的例外是缓冲区特征,它仅在于mutable集合。 - -下面我们将一个个的回顾这些类。 diff --git a/zh-cn/overviews/collections/performance-characteristics.md b/zh-cn/overviews/collections/performance-characteristics.md deleted file mode 100644 index 8980dbfb28..0000000000 --- a/zh-cn/overviews/collections/performance-characteristics.md +++ /dev/null @@ -1,86 +0,0 @@ ---- -layout: overview-large -title: 性能特点 - -discourse: false - -partof: collections -num: 12 -language: zh-cn ---- - - -前面的解释明确说明了不同的容器类型具有不同的性能特点。这通常是选择容器类型的首要依据。以下的两张表格,总结了一些关于容器类型常用操作的性能特点。 - -## 序列类型的性能特点 - -| | head | tail | apply | update | prepend | append | insert | -|------|------|------|-------|--------|---------|--------|--------| -|**不可变序列**| | | | | | | -| List | C | C | L | L | C | L | - | -|Stream | C | C | L | L | C | L | - | -|Vector | eC | eC | eC | eC | eC | eC | - | -|Stack | C | C | L | L | C | L | L | -|Queue | aC | aC | L | L | C | C | - | -|Range | C | C | C | - | - | - | - | -|String | C | L | C | L | L | L | - | -|**可变序列**| | | | | | | -|ArrayBuffer | C | L | C | C | L | aC | L | -|ListBuffer | C | L | L | L | C | C | L | -|StringBuilder | C | L | C | C | L | aC | L | -|MutableList | C | L | L | L | C | C | L | -|Queue | C | L | L | L | C | C | L | -|ArraySeq | C | L | C | C | - | - | - | -|Stack | C | L | L | L | C | L | L | -|ArrayStack | C | L | C | C | aC | L | L | -|Array | C | L | C | C | - | - | - | - -## 集合和映射类型的性能特点 - -|lookup | add | remove | min | -|-------|-----|--------|-----| -|**不可变序列**| | | | -|HashSet/HashMap | eC | eC | eC | L | -|TreeSet/TreeMap | Log | Log | Log | Log | -|BitSet | C | L | L | eC1 | -|ListMap | L | L | L | L | -|可变序列| | | | -|HashSet/HashMap | eC | eC | eC | L | -|WeakHashMap | eC | eC | eC | L | -|BitSet | C | aC | C | eC1 | -|TreeSet | Log | Log | Log | Log | - -标注:1 假设位是密集分布的 - -这两个表中的条目: - -|解释如下| | -|--------|-----------------| -|C | 指操作的时间复杂度为常数 | -|eC | 指操作的时间复杂度实际上为常数,但可能依赖于诸如一个向量最大长度或是哈希键的分布情况等一些假设。 | -|aC | 该操作的均摊运行时间为常数。某些调用的可能耗时较长,但多次调用之下,每次调用的平均耗时是常数。 | -|Log | 操作的耗时与容器大小的对数成正比。 | -|L | 操作是线性的,耗时与容器的大小成正比。 | -|- | 操作不被支持。 | - -第一张表处理序列类型——无论可变还是不可变——: - -| 使用以下操作 | | -|--------|-----------------| -|head | 选择序列的第一个元素。 | -|tail | 生成一个包含除第一个元素以外所有其他元素的新的列表。 | -|apply | 索引。 | -|update | 功能性更新不可变序列,同步更新可变序列。 | -|prepend | 添加一个元素到序列头。对于不可变序列,操作会生成个新的序列。对于可变序列,操作会修改原有的序列。 | -|append | 在序列尾部插入一个元素。对于不可变序列,这将产生一个新的序列。对于可变序列,这将修改原有的序列。 | -|insert | 在序列的任意位置上插入一个元素。只有可变序列支持该操作。 | - -第二个表处理可变和不可变集与映射 - -| 使用以下操作:| | -|--------|-----------------| -|lookup | 测试一个元素是否被包含在集合中,或者找出一个键对应的值 | -|add | 添加一个新的元素到一个集合中或者添加一个键值对到一个映射中。 | -|remove | 移除一个集合中的一个元素或者移除一个映射中一个键。 | -|min | 集合中的最小元素,或者映射中的最小键。 | - diff --git a/zh-cn/overviews/collections/seqs.md b/zh-cn/overviews/collections/seqs.md deleted file mode 100644 index d4e07486ee..0000000000 --- a/zh-cn/overviews/collections/seqs.md +++ /dev/null @@ -1,107 +0,0 @@ ---- -layout: overview-large -title: 序列trait:Seq、IndexedSeq及LinearSeq - -discourse: false - -partof: collections -num: 5 -language: zh-cn ---- - - -[Seq](http://www.scala-lang.org/api/current/scala/collection/Seq.html) trait用于表示序列。所谓序列,指的是一类具有一定长度的可迭代访问的对象,其中每个元素均带有一个从0开始计数的固定索引位置。 - -序列的操作有以下几种,如下表所示: - -- **索引和长度的操作** apply、isDefinedAt、length、indices,及lengthCompare。序列的apply操作用于索引访问;因此,Seq[T]类型的序列也是一个以单个Int(索引下标)为参数、返回值类型为T的偏函数。换言之,Seq[T]继承自Partial Function[Int, T]。序列各元素的索引下标从0开始计数,最大索引下标为序列长度减一。序列的length方法是collection的size方法的别名。lengthCompare方法可以比较两个序列的长度,即便其中一个序列长度无限也可以处理。 -- **索引检索操作**(indexOf、lastIndexOf、indexofSlice、lastIndexOfSlice、indexWhere、lastIndexWhere、segmentLength、prefixLength)用于返回等于给定值或满足某个谓词的元素的索引。 -- **加法运算**(+:,:+,padTo)用于在序列的前面或者后面添加一个元素并作为新序列返回。 -- **更新操作**(updated,patch)用于替换原序列的某些元素并作为一个新序列返回。 -- **排序操作**(sorted, sortWith, sortBy)根据不同的条件对序列元素进行排序。 -- **反转操作**(reverse, reverseIterator, reverseMap)用于将序列中的元素以相反的顺序排列。 -- **比较**(startsWith, endsWith, contains, containsSlice, corresponds)用于对两个序列进行比较,或者在序列中查找某个元素。 -- **多集操作**(intersect, diff, union, distinct)用于对两个序列中的元素进行类似集合的操作,或者删除重复元素。 - -如果一个序列是可变的,它提供了另一种更新序列中的元素的,但有副作用的update方法,Scala中常有这样的语法,如seq(idx) = elem。它只是seq.update(idx, elem)的简写,所以update 提供了方便的赋值语法。应注意update 和updated之间的差异。update 再原来基础上更改序列中的元素,并且仅适用于可变序列。而updated 适用于所有的序列,它总是返回一个新序列,而不会修改原序列。 - -## Seq类的操作 - -| WHAT IT IS | WHAT IT DOES | -|------------------ | -------------------| -| **索引和长度** | | -| xs(i) | (或者写作xs apply i)。xs的第i个元素 | -| xs isDefinedAt i | 测试xs.indices中是否包含i。 | -| xs.length | 序列的长度(同size)。 | -| xs.lengthCompare ys | 如果xs的长度小于ys的长度,则返回-1。如果xs的长度大于ys的长度,则返回+1,如果它们长度相等,则返回0。即使其中一个序列是无限的,也可以使用此方法。 | -| xs.indices | xs的索引范围,从0到xs.length - 1。 | -| **索引搜索** | | -| xs indexOf x | 返回序列xs中等于x的第一个元素的索引(存在多种变体)。 | -| xs lastIndexOf x | 返回序列xs中等于x的最后一个元素的索引(存在多种变体)。 | -| xs indexOfSlice ys | 查找子序列ys,返回xs中匹配的第一个索引。 | -| xs indexOfSlice ys | 查找子序列ys,返回xs中匹配的倒数一个索引。 | -| xs indexWhere p | xs序列中满足p的第一个元素。(有多种形式) | -| xs segmentLength (p, i) | xs中,从xs(i)开始并满足条件p的元素的最长连续片段的长度。 | -| xs prefixLength p | xs序列中满足p条件的先头元素的最大个数。 | -| **加法:** | | -| x +: xs | 由序列xs的前方添加x所得的新序列。 | -| xs :+ x | 由序列xs的后方追加x所得的新序列。 | -| xs padTo (len, x) | 在xs后方追加x,直到长度达到len后得到的序列。 | -| **更新** | | -| xs patch (i, ys, r) | 将xs中第i个元素开始的r个元素,替换为ys所得的序列。 | -| xs updated (i, x) | 将xs中第i个元素替换为x后所得的xs的副本。 | -| xs(i) = x | (或写作 xs.update(i, x),仅适用于可变序列)将xs序列中第i个元素修改为x。 | -| **排序** | | -| xs.sorted | 通过使用xs中元素类型的标准顺序,将xs元素进行排序后得到的新序列。 | -| xs sortWith lt | 将lt作为比较操作,并以此将xs中的元素进行排序后得到的新序列。 | -| xs sortBy f | 将序列xs的元素进行排序后得到的新序列。参与比较的两个元素各自经f函数映射后得到一个结果,通过比较它们的结果来进行排序。 | -| **反转** | | -| xs.reverse | 与xs序列元素顺序相反的一个新序列。 | -| xs.reverseIterator | 产生序列xs中元素的反序迭代器。 | -| xs reverseMap f | 以xs的相反顺序,通过f映射xs序列中的元素得到的新序列。 | -| **比较** | | -| xs startsWith ys | 测试序列xs是否以序列ys开头(存在多种形式)。 | -| xs endsWith ys | 测试序列xs是否以序列ys结束(存在多种形式)。 | -| xs contains x | 测试xs序列中是否存在一个与x相等的元素。 | -| xs containsSlice ys | 测试xs序列中是否存在一个与ys相同的连续子序列。 | -| (xs corresponds ys)(p) | 测试序列xs与序列ys中对应的元素是否满足二元的判断式p。 | -| **多集操作** | | -| xs intersect ys | 序列xs和ys的交集,并保留序列xs中的顺序。 | -| xs diff ys | 序列xs和ys的差集,并保留序列xs中的顺序。 | -| xs union ys | 并集;同xs ++ ys。 | -| xs.distinct | 不含重复元素的xs的子序列。 | -| | | - - -特性(trait) [Seq](http://www.scala-lang.org/api/current/scala/collection/Seq.html) 具有两个子特征(subtrait) [LinearSeq](http://www.scala-lang.org/api/current/scala/collection/IndexedSeq.html)和[IndexedSeq](http://www.scala-lang.org/api/current/scala/collection/IndexedSeq.html)。它们不添加任何新的操作,但都提供不同的性能特点:线性序列具有高效的 head 和 tail 操作,而索引序列具有高效的apply, length, 和 (如果可变) update操作。 - -常用线性序列有 `scala.collection.immutable.List`和`scala.collection.immutable.Stream`。常用索引序列有 `scala.Array scala.collection.mutable.ArrayBuffer`。Vector 类提供一个在索引访问和线性访问之间有趣的折中。它同时具有高效的恒定时间的索引开销,和恒定时间的线性访问开销。正因为如此,对于混合访问模式,vector是一个很好的基础。后面将详细介绍vector。 - -## 缓冲器 - -Buffers是可变序列一个重要的种类。它们不仅允许更新现有的元素,而且允许元素的插入、移除和在buffer尾部高效地添加新元素。buffer 支持的主要新方法有:用于在尾部添加元素的 `+=` 和 `++=`;用于在前方添加元素的`+=: `和` ++=:` ;用于插入元素的 `insert`和`insertAll`;以及用于删除元素的` remove` 和 `-=`。如下表所示。 - -ListBuffer和ArrayBuffer是常用的buffer实现 。顾名思义,ListBuffer依赖列表(List),支持高效地将它的元素转换成列表。而ArrayBuffer依赖数组(Array),能快速地转换成数组。 - -## Buffer类的操作 - -| WHAT IT IS | WHAT IT DOES | -|--------------------- | -----------------------| -| **加法:** | | -| buf += x | 将元素x追加到buffer,并将buf自身作为结果返回。 | -| buf += (x, y, z) | 将给定的元素追加到buffer。 | -| buf ++= xs | 将xs中的所有元素追加到buffer。 | -| x +=: buf | 将元素x添加到buffer的前方。 | -| xs ++=: buf | 将xs中的所有元素都添加到buffer的前方。 | -| buf insert (i, x) | 将元素x插入到buffer中索引为i的位置。 | -| buf insertAll (i, xs) | 将xs的所有元素都插入到buffer中索引为i的位置。 | -| **移除:** | | -| buf -= x | 将元素x从buffer中移除。 | -| buf remove i | 将buffer中索引为i的元素移除。 | -| buf remove (i, n) | 将buffer中从索引i开始的n个元素移除。 | -| buf trimStart n | 移除buffer中的前n个元素。 | -| buf trimEnd n | 移除buffer中的后n个元素。 | -| buf.clear() | 移除buffer中的所有元素。 | -| **克隆:** | | -| buf.clone | 与buf具有相同元素的新buffer。 | - diff --git a/zh-cn/overviews/collections/sets.md b/zh-cn/overviews/collections/sets.md deleted file mode 100644 index e1713f2b4b..0000000000 --- a/zh-cn/overviews/collections/sets.md +++ /dev/null @@ -1,149 +0,0 @@ ---- -layout: overview-large -title: 集合 - -discourse: false - -partof: collections -num: 6 -language: zh-cn ---- - - -集合是不包含重复元素的可迭代对象。下面的通用集合表和可变集合表中概括了集合类型适用的运算。分为几类: - -* **测试型的方法:**`contains`,`apply`,`subsetOf`。`contains` 方法用于判断集合是否包含某元素。集合的 `apply` 方法和 `contains` 方法的作用相同,因此 `set(elem)` 等同于 `set contains elem`。这意味着集合对象的名字能作为其自身是否包含某元素的测试函数。 - -例如 - - val fruit = Set("apple", "orange", "peach", "banana") - fruit: scala.collection.immutable.Set[java.lang.String] = - Set(apple, orange, peach, banana) - scala> fruit("peach") - res0: Boolean = true - scala> fruit("potato") - res1: Boolean = false - -* **加法类型的方法:** `+` 和 `++`。添加一个或多个元素到集合中,产生一个新的集合。 -* **减法类型的方法:** `-` 、`--`。它们实现从一个集合中移除一个或多个元素,产生一个新的集合。 -* **Set运算包括并集、交集和差集**。每一种运算都存在两种书写形式:字母和符号形式。字母形式:`intersect`、`union` 和 `diff`,符号形式:`&`、`|` 和 `&~`。事实上,`Set` 中继承自 `Traversable` 的 `++` 也能被看做 `union` 或|的另一个别名。区别是,`++` 的参数为 `Traversable` 对象,而 `union` 和 `|` 的参数是集合。 - -## Set 类的操作 - -| WHAT IT IS | WHAT IT DOES | -|------------------------|--------------------------| -| **实验代码:** | | -| `xs contains x` | 测试 `x` 是否是 `xs` 的元素。 | -| `xs(x)` | 与 `xs contains x` 相同。 | -| `xs subsetOf ys` | 测试 `xs` 是否是 `ys` 的子集。 | -| **加法:** | | -| `xs + x` | 包含 `xs` 中所有元素以及 `x` 的集合。 | -| `xs + (x, y, z)` | 包含 `xs` 中所有元素及附加元素的集合 | -| `xs ++ ys` | 包含 `xs` 中所有元素及 `ys` 中所有元素的集合 | -| **移除:** | | -| `xs - x` | 包含 `xs` 中除x以外的所有元素的集合。 | -| `xs - x` | 包含 `xs` 中除去给定元素以外的所有元素的集合。 | -| `xs -- ys` | 集合内容为:`xs` 中所有元素,去掉 `ys` 中所有元素后剩下的部分。 | -| `xs.empty` | 与 `xs` 同类的空集合。 | -| **二值操作:** | | -| `xs & ys` | 集合 `xs` 和 `ys` 的交集。 | -| `xs intersect ys` | 等同于 `xs & ys`。 | -| xs | ys | 集合 `xs` 和 `ys` 的并集。 | -| `xs union ys` | 等同于 xs | ys。 | -| `xs &~ ys` | 集合 `xs` 和 `ys` 的差集。 | -| `xs diff ys` | 等同于 `xs &~ ys`。 | - - -可变集合提供加法类方法,可以用来添加、删除或更新元素。下面对这些方法做下总结。 - -## mutable.Set 类的操作 - -| WHAT IT IS | WHAT IT DOES | -|------------------|------------------------| -| **加法:** | | -| `xs += x` | 把元素 `x` 添加到集合 `xs` 中。该操作有副作用,它会返回左操作符,这里是 `xs` 自身。 | -| `xs += (x, y, z)` | 添加指定的元素到集合 `xs` 中,并返回 `xs` 本身。(同样有副作用) | -| `xs ++= ys` | 添加集合 `ys` 中的所有元素到集合 `xs` 中,并返回 `xs` 本身。(表达式有副作用) | -| `xs add x` | 把元素 `x` 添加到集合 `xs` 中,如集合 `xs` 之前没有包含 `x`,该操作返回 `true`,否则返回 `false`。 | -| **移除:** | | -| `xs -= x` | 从集合 `xs` 中删除元素 `x`,并返回 `xs` 本身。(表达式有副作用) | -| `xs -= (x, y, z)` | 从集合 `xs` 中删除指定的元素,并返回 `xs` 本身。(表达式有副作用) | -| `xs --= ys` | 从集合 `xs` 中删除所有属于集合 `ys` 的元素,并返回 `xs` 本身。(表达式有副作用) | -| `xs remove x` | 从集合 `xs` 中删除元素 `x` 。如之前 `xs` 中包含了 `x` 元素,返回 `true`,否则返回 `false`。 | -| `xs retain p` | 只保留集合 `xs` 中满足条件 `p` 的元素。 | -| `xs.clear()` | 删除集合 `xs` 中的所有元素。 | -| **更新:** | | -| `xs(x) = b` | ( 同 `xs.update(x, b)` )参数 `b` 为布尔类型,如果值为 `true` 就把元素x加入集合 `xs`,否则从集合 `xs` 中删除 `x`。 | -| **克隆:** | | -| `xs.clone` | 产生一个与 `xs` 具有相同元素的可变集合。 | - - -与不变集合一样,可变集合也提供了`+`和`++`操作符来添加元素,`-`和`--`用来删除元素。但是这些操作在可变集合中通常很少使用,因为这些操作都要通过集合的拷贝来实现。可变集合提供了更有效率的更新方法,`+=`和`-=`。 `s += elem`,添加元素elem到集合s中,并返回产生变化后的集合作为运算结果。同样的,`s -= elem `执行从集合s中删除元素elem的操作,并返回产生变化后的集合作为运算结果。除了`+=`和`-=`之外还有从可遍历对象集合或迭代器集合中添加和删除所有元素的批量操作符`++=`和`--=`。 - -选用`+=`和`-=`这样的方法名使得我们得以用非常近似的代码来处理可变集合和不可变集合。先看一下以下处理不可变集合 `s` 的REPL会话: - - scala> var s = Set(1, 2, 3) - s: scala.collection.immutable.Set[Int] = Set(1, 2, 3) - scala> s += 4 - scala> s -= 2 - scala> s - res2: scala.collection.immutable.Set[Int] = Set(1, 3, 4) - -我们在`immutable.Set`类型的变量中使用`+=`和`-= `。诸如 `s += 4` 的表达式是 `s = s + 4 `的缩写,它的作用是,在集合 `s` 上运用方法`+`,并把结果赋回给变量 `s`。下面我们来分析可变集合上的类似操作。 - - scala> val s = collection.mutable.Set(1, 2, 3) - s: scala.collection.mutable.Set[Int] = Set(1, 2, 3) - scala> s += 4 - res3: s.type = Set(1, 4, 2, 3) - scala> s -= 2 - res4: s.type = Set(1, 4, 3) - -最后结果看起来和之前的在非可变集合上的操作非常相似;从`Set(1, 2, 3)`开始,最后得到`Set(1, 3, 4)`。然而,尽管相似,但它们在实现上其实是不同的。 这里`s += 4 `是在可变集合值s上调用`+=`方法,它会改变 `s` 的内容。同样的,`s -= 2` 也是在s上调用 `-= `方法,也会修改 `s` 集合的内容。 - -通过比较这两种方式得出一个重要的原则。我们通常能用一个非可变集合的变量来替换可变集合的常量,反之亦然。这一原则至少在没有别名的引用添加到Collection时起作用。别名引用主要是用来观察操作在Collection上直接做的修改还是生成了一个新的Collection。 - -可变集合同样提供作为 `+=` 和 `-=` 的变型方法,`add` 和 `remove`,它们的不同之处在于 `add` 和 `remove` 会返回一个表明运算是否对集合有作用的Boolean值 - -目前可变集合默认使用哈希表来存储集合元素,非可变集合则根据元素个数的不同,使用不同的方式来实现。空集用单例对象来表示。元素个数小于等于4的集合可以使用单例对象来表达,元素作为单例对象的字段来存储。 元素超过4个,非可变集合就用哈希前缀树(hash trie)来实现。 - -采用这种表示方法,较小的不可变集合(元素数不超过4)往往会比可变集合更加紧凑和高效。所以,在处理小尺寸的集合时,不妨试试不可变集合。 - -集合的两个特质是 `SortedSet` 和 `BitSet`。 - -## 有序集(SortedSet) - - [SortedSet](http://www.scala-lang.org/api/current/scala/collection/SortedSet.html) 是指以特定的顺序(这一顺序可以在创建集合之初自由的选定)排列其元素(使用iterator或foreach)的集合。 [SortedSet](http://www.scala-lang.org/api/current/scala/collection/SortedSet.html) 的默认表示是有序二叉树,即左子树上的元素小于所有右子树上的元素。这样,一次简单的顺序遍历能按增序返回集合中的所有元素。Scala的类 `immutable.TreeSet` 使用红黑树实现,它在维护元素顺序的同时,也会保证二叉树的平衡,即叶节点的深度差最多为1。 - -创建一个空的 [TreeSet](http://www.scala-lang.org/api/current/scala/collection/immutable/TreeSet.html) ,可以先定义排序规则: - - scala> val myOrdering = Ordering.fromLessThan[String](_ > _) - myOrdering: scala.math.Ordering[String] = ... - -然后,用这一排序规则创建一个空的树集: - - scala> TreeSet.empty(myOrdering) - res1: scala.collection.immutable.TreeSet[String] = TreeSet() - -或者,你也可以不指定排序规则参数,只需要给定一个元素类型或空集合。在这种情况下,将使用此元素类型默认的排序规则。 - - scala> TreeSet.empty[String] - res2: scala.collection.immutable.TreeSet[String] = TreeSet() - -如果通过已有的TreeSet来创建新的集合(例如,通过串联或过滤操作),这些集合将和原集合保持相同的排序规则。例如, - - scala> res2 + ("one", "two", "three", "four") - res3: scala.collection.immutable.TreeSet[String] = TreeSet(four, one, three, two) - -有序集合同样支持元素的范围操作。例如,range方法返回从指定起始位置到结束位置(不含结束元素)的所有元素,from方法返回大于等于某个元素的所有元素。调用这两种方法的返回值依然是有序集合。例如: - - scala> res3 range ("one", "two") - res4: scala.collection.immutable.TreeSet[String] = TreeSet(one, three) - scala> res3 from "three" - res5: scala.collection.immutable.TreeSet[String] = TreeSet(three, two) - -## 位集合(Bitset) - -位集合是由单字或多字的紧凑位实现的非负整数的集合。其内部使用 `Long` 型数组来表示。第一个 `Long` 元素表示的范围为0到63,第二个范围为64到127,以此类推(值为0到127的非可变位集合通过直接将值存储到第一个或第两个 `Long` 字段的方式,优化掉了数组处理的消耗)。对于每个 `Long`,如果有相应的值包含于集合中则它对应的位设置为1,否则该位为0。这里遵循的规律是,位集合的大小取决于存储在该集合的最大整数的值的大小。假如N是为集合所要表示的最大整数,则集合的大小就是 `N/64` 个长整形字,或者 `N/8` 个字节,再加上少量额外的状态信息字节。 - -因此当位集合包含的元素值都比较小时,它比其他的集合类型更紧凑。位集合的另一个优点是它的 `contains` 方法(成员测试)、`+=` 运算(添加元素)、`-=` 运算(删除元素)都非常的高效。 - diff --git a/zh-cn/overviews/collections/strings.md b/zh-cn/overviews/collections/strings.md deleted file mode 100644 index f7c27c0f77..0000000000 --- a/zh-cn/overviews/collections/strings.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -layout: overview-large -title: 字符串 - -discourse: false - -partof: collections -num: 11 -language: zh-cn ---- - -像数组,字符串不是直接的序列,但是他们可以转换为序列,并且他们也支持所有的在字符串上的序列操作这里有些例子让你可以理解在字符串上操作。 - - scala> val str = "hello" - str: java.lang.String = hello - scala> str.reverse - res6: String = olleh - scala> str.map(_.toUpper) - res7: String = HELLO - scala> str drop 3 - res8: String = lo - scala> str slice (1, 4) - res9: String = ell - scala> val s: Seq[Char] = str - s: Seq[Char] = WrappedString(h, e, l, l, o) - -这些操作依赖于两种隐式转换。第一种,低优先级转换映射一个String到WrappedString,它是`immutable.IndexedSeq`的子类。在上述代码中这种转换应用在一个string转换为一个Seq。另一种,高优先级转换映射一个string到StringOps 对象,从而在immutable 序列到strings上增加了所有的方法。在上面的例子里,这种隐式转换插入在reverse,map,drop和slice的方法调用中。 diff --git a/zh-cn/overviews/collections/trait-iterable.md b/zh-cn/overviews/collections/trait-iterable.md deleted file mode 100644 index 6d6696fb53..0000000000 --- a/zh-cn/overviews/collections/trait-iterable.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -layout: overview-large -title: Trait Iterable - -discourse: false - -partof: collections -num: 4 -language: zh-cn ---- - -自下而上的容器(collection)层次结构具有可迭代的Trait。Trait的所有方法可定义为一个抽象方法,逐个生成容器(collection)元素迭代器。Traversable Trait的foreach方法实现了迭代器的Iterable。下面是具体的实现。 - - def foreach[U](f: Elem => U): Unit = { - val it = iterator - while (it.hasNext) f(it.next()) - } - -许多Iterable 的子类覆写了Iteable的foreach标准实现,因为它们提供了更多有效的实现。记住,由于性能问题,foreach是Traversable所有操作能够实现的基础。 - -Iterable有两个方法返回迭代器:grouped和sliding。然而,这些迭代器返回的不是单个元素,而是原容器(collection)元素的全部子序列。这些最大的子序列作为参数传给这些方法。grouped方法返回元素的增量分块,sliding方法生成一个滑动元素的窗口。两者之间的差异通过REPL的作用能够清楚看出。 - - scala> val xs = List(1, 2, 3, 4, 5) - xs: List[Int] = List(1, 2, 3, 4, 5) - scala> val git = xs grouped 3 - git: Iterator[List[Int]] = non-empty iterator - scala> git.next() - res3: List[Int] = List(1, 2, 3) - scala> git.next() - res4: List[Int] = List(4, 5) - scala> val sit = xs sliding 3 - sit: Iterator[List[Int]] = non-empty iterator - scala> sit.next() - res5: List[Int] = List(1, 2, 3) - scala> sit.next() - res6: List[Int] = List(2, 3, 4) - scala> sit.next() - res7: List[Int] = List(3, 4, 5) - -当只有一个迭代器可用时,Trait Iterable增加了一些其他方法,为了能被有效的实现的可遍历的情况。这些方法总结在下面的表中。 - -## Trait Iterable操作 - -| WHAT IT IS | WHAT IT DOES | -|--------------|--------------| -| **抽象方法:** | | -| xs.iterator | xs迭代器生成的每一个元素,以相同的顺序就像foreach一样遍历元素。 | -| **其他迭代器:** | | -| xs grouped size | 一个迭代器生成一个固定大小的容器(collection)块。 | -| xs sliding size | 一个迭代器生成一个固定大小的滑动窗口作为容器(collection)的元素。 | -| **子容器(Subcollection):** | | -| xs takeRight n | 一个容器(collection)由xs的最后n个元素组成(或,若定义的元素是无序,则由任意的n个元素组成)。 | -| xs dropRight n | 一个容器(collection)由除了xs 被取走的(执行过takeRight ()方法)n个元素外的其余元素组成。 | -| **拉链方法(Zippers):** | | -| xs zip ys | 把一对容器 xs和ys的包含的元素合成到一个iterabale。 | -| xs zipAll (ys, x, y) | 一对容器 xs 和ys的相应的元素合并到一个iterable ,实现方式是通过附加的元素x或y,把短的序列被延展到相对更长的一个上。 | -| xs.zip WithIndex | 把一对容器xs和它的序列,所包含的元素组成一个iterable 。 | -| **比对:** | | -| xs sameElements ys | 测试 xs 和 ys 是否以相同的顺序包含相同的元素。 | - - -在Iterable下的继承层次结构你会发现有三个traits:[Seq](https://www.scala-lang.org/api/current/scala/collection/Seq.html),[Set](https://www.scala-lang.org/api/current/scala/collection/Set.html),和 [Map](https://www.scala-lang.org/api/current/scala/collection/Map.html)。这三个Traits有一个共同的特征,它们都实现了[PartialFunction](https://www.scala-lang.org/api/current/scala/PartialFunction.html) trait以及它的应用和isDefinedAt 方法。然而,每一个trait实现的 `PartialFunction` 方法却各不相同。 - -例如序列,使用用的是位置索引,它里面的元素的总是从0开始编号。即`Seq(1, 2, 3)(1) `为2。例如sets,使用的是成员测试。例如`Set('a', 'b', 'c')('b') `算出来的是true,而`Set()('a')`为false。最后,maps使用的是选择。比如`Map('a' -> 1, 'b' -> 10, 'c' -> 100)('b')` 得到的是10。 - -接下来,我们将详细的介绍三种类型的容器(collection)。 - diff --git a/zh-cn/overviews/collections/trait-traversable.md b/zh-cn/overviews/collections/trait-traversable.md deleted file mode 100644 index 8147177c91..0000000000 --- a/zh-cn/overviews/collections/trait-traversable.md +++ /dev/null @@ -1,122 +0,0 @@ ---- -layout: overview-large -title: Trait Traversable - -discourse: false - -partof: collections -num: 3 -language: zh-cn ---- - -Traversable(遍历)是容器(collection)类的最高级别特性,它唯一的抽象操作是foreach: - -`def foreach[U](f: Elem => U) ` - -需要实现Traversable的容器(collection)类仅仅需要定义与之相关的方法,其他所有方法可都可以从Traversable中继承。 - -foreach方法用于遍历容器(collection)内的所有元素和每个元素进行指定的操作(比如说f操作)。操作类型是Elem => U,其中Elem是容器(collection)中元素的类型,U是一个任意的返回值类型。对f的调用仅仅是容器遍历的副作用,实际上所有函数f的计算结果都被foreach抛弃了。 - -Traversable同时定义的很多具体方法,如下表所示。这些方法可以划分为以下类别: - -- **相加操作++(addition)**表示把两个traversable对象附加在一起或者把一个迭代器的所有元素添加到traversable对象的尾部。 - -- **Map**操作有map,flatMap和collect,它们可以通过对容器中的元素进行某些运算来生成一个新的容器。 - -- **转换器(Conversion)**操作包括toArray,toList,toIterable,toSeq,toIndexedSeq,toStream,toSet,和toMap,它们可以按照某种特定的方法对一个Traversable 容器进行转换。等容器类型已经与所需类型相匹配的时候,所有这些转换器都会不加改变的返回该容器。例如,对一个list使用toList,返回的结果就是list本身。 - -- **拷贝(Copying)**操作有copyToBuffer和copyToArray。从字面意思就可以知道,它们分别用于把容器中的元素元素拷贝到一个缓冲区或者数组里。 - -- **Size info**操作包括有isEmpty,nonEmpty,size和hasDefiniteSize。Traversable容器有有限和无限之分。比方说,自然数流Stream.from(0)就是一个无限的traversable 容器。hasDefiniteSize方法能够判断一个容器是否可能是无限的。若hasDefiniteSize返回值为ture,容器肯定有限。若返回值为false,根据完整信息才能判断容器(collection)是无限还是有限。 - -- **元素检索(Element Retrieval)**操作有head,last,headOption,lastOption和find。这些操作可以查找容器的第一个元素或者最后一个元素,或者第一个符合某种条件的元素。注意,尽管如此,但也不是所有的容器都明确定义了什么是“第一个”或”最后一个“。例如,通过哈希值储存元素的哈希集合(hashSet),每次运行哈希值都会发生改变。在这种情况下,程序每次运行都可能会导致哈希集合的”第一个“元素发生变化。如果一个容器总是以相同的规则排列元素,那这个容器是有序的。大多数容器都是有序的,但有些不是(例如哈希集合)-- 排序会造成一些额外消耗。排序对于重复性测试和辅助调试是不可或缺的。这就是为什么Scala容器中的所有容器类型都把有序作为可选项。例如,带有序性的HashSet就是LinkedHashSet。 - -- **子容器检索(sub-collection Retrieval)**操作有tail,init,slice,take,drop,takeWhilte,dropWhile,filter,filteNot和withFilter。它们都可以通过范围索引或一些论断的判断返回某些子容器。 - -- **拆分(Subdivision)**操作有splitAt,span,partition和groupBy,它们用于把一个容器(collection)里的元素分割成多个子容器。 - -- **元素测试(Element test)**包括有exists,forall和count,它们可以用一个给定论断来对容器中的元素进行判断。 - -- **折叠(Folds)**操作有foldLeft,foldRight,/:,:\,reduceLeft和reduceRight,用于对连续性元素的二进制操作。 - -- **特殊折叠(Specific folds)**包括sum, product, min, max。它们主要用于特定类型的容器(数值或比较)。 - -- **字符串(String)**操作有mkString,addString和stringPrefix,可以将一个容器通过可选的方式转换为字符串。 - -- **视图(View)**操作包含两个view方法的重载体。一个view对象可以当作是一个容器客观地展示。接下来将会介绍更多有关视图内容。 - -## Traversable对象的操作 - -| WHAT IT IS |WHAT IT DOES | -|------------------------|------------------------------| -| **抽象方法:** | | -| xs foreach f | 对xs中的每一个元素执行函数f | -| **加运算(Addition):** | | -| xs ++ ys | 生成一个由xs和ys中的元素组成容器。ys是一个TraversableOnce容器,即Taversable类型或迭代器。 -| **Maps:** | | -| xs map f | 通过函数xs中的每一个元素调用函数f来生成一个容器。 | -| xs flatMap f | 通过对容器xs中的每一个元素调用作为容器的值函数f,在把所得的结果连接起来作为一个新的容器。 | -| xs collect f | 通过对每个xs中的符合定义的元素调用偏函数f,并把结果收集起来生成一个集合。 | -| **转换(Conversions):** | | -| xs.toArray | 把容器转换为一个数组 | -| xs.toList | 把容器转换为一个list | -| xs.toIterable | 把容器转换为一个迭代器。 | -| xs.toSeq | 把容器转换为一个序列 | -| xs.toIndexedSeq | 把容器转换为一个索引序列 | -| xs.toStream | 把容器转换为一个延迟计算的流。 | -| xs.toSet | 把容器转换为一个集合(Set)。 | -| xs.toMap | 把由键/值对组成的容器转换为一个映射表(map)。如果该容器并不是以键/值对作为元素的,那么调用这个操作将会导致一个静态类型的错误。 | -| **拷贝(Copying):** | | -| xs copyToBuffer buf | 把容器的所有元素拷贝到buf缓冲区。 | -| xs copyToArray(arr, s, n) | 拷贝最多n个元素到数组arr的坐标s处。参数s,n是可选项。 | -| **大小判断(Size info):** | | -| xs.isEmpty | 测试容器是否为空。 | -| xs.nonEmpty | 测试容器是否包含元素。 | -| xs.size | 计算容器内元素的个数。 | -| xs.hasDefiniteSize | 如果xs的大小是有限的,则为true。 | -| **元素检索(Element Retrieval):** | | -| xs.head | 返回容器内第一个元素(或其他元素,若当前的容器无序)。 | -| xs.headOption | xs选项值中的第一个元素,若xs为空则为None。 | -| xs.last | 返回容器的最后一个元素(或某个元素,如果当前的容器无序的话)。 | -| xs.lastOption | xs选项值中的最后一个元素,如果xs为空则为None。 | -| xs find p | 查找xs中满足p条件的元素,若存在则返回第一个元素;若不存在,则为空。 | -| **子容器(Subcollection):** | | -| xs.tail | 返回由除了xs.head外的其余部分。 | -| xs.init | 返回除xs.last外的其余部分。 | -| xs slice (from, to) | 返回由xs的一个片段索引中的元素组成的容器(从from到to,但不包括to)。 | -| xs take n | 由xs的第一个到第n个元素(或当xs无序时任意的n个元素)组成的容器。 | -| xs drop n | 由除了xs take n以外的元素组成的容器。 | -| xs takeWhile p | 容器xs中最长能够满足断言p的前缀。 | -| xs dropWhile p | 容器xs中除了xs takeWhile p以外的全部元素。 | -| xs filter p | 由xs中满足条件p的元素组成的容器。 | -| xs withFilter p | 这个容器是一个不太严格的过滤器。子容器调用map,flatMap,foreach和withFilter只适用于xs中那些的满足条件p的元素。 | -| xs filterNot p | 由xs中不满足条件p的元素组成的容器。 | -| **拆分(Subdivision):** | | -| xs splitAt n | 把xs从指定位置的拆分成两个容器(xs take n和xs drop n)。 | -| xs span p | 根据一个断言p将xs拆分为两个容器(xs takeWhile p, xs.dropWhile p)。 | -| xs partition p | 把xs分割为两个容器,符合断言p的元素赋给一个容器,其余的赋给另一个(xs filter p, xs.filterNot p)。 | -| xs groupBy f | 根据判别函数f把xs拆分一个到容器(collection)的map中。 | -| **条件元素(Element Conditions):** | | -| xs forall p | 返回一个布尔值表示用于表示断言p是否适用xs中的所有元素。 | -| xs exists p | 返回一个布尔值判断xs中是否有部分元素满足断言p。 | -| xs count p | 返回xs中符合断言p条件的元素个数。 | -| **折叠(Fold):** | | -| (z /: xs)(op) | 在xs中,对由z开始从左到右的连续元素应用二进制运算op。 | -| (xs :\ z)(op) | 在xs中,对由z开始从右到左的连续元素应用二进制运算op | -| xs.foldLeft(z)(op) | 与(z /: xs)(op)相同。 | -| xs.foldRight(z)(op) | 与 (xs :\ z)(op)相同。 | -| xs reduceLeft op | 非空容器xs中的连续元素从左至右调用二进制运算op。 | -| xs reduceRight op | 非空容器xs中的连续元素从右至左调用二进制运算op。 | -| **特殊折叠(Specific Fold):** | | -| xs.sum | 返回容器xs中数字元素的和。 | -| xs.product | xs返回容器xs中数字元素的积。 | -| xs.min | 容器xs中有序元素值中的最小值。 | -| xs.max | 容器xs中有序元素值中的最大值。 | -| **字符串(String):** | | -| xs addString (b, start, sep, end) | 把一个字符串加到StringBuilder对象b中,该字符串显示为将xs中所有元素用分隔符sep连接起来并封装在start和end之间。其中start,end和sep都是可选的。 | -| xs mkString (start, sep, end) | 把容器xs转换为一个字符串,该字符串显示为将xs中所有元素用分隔符sep连接起来并封装在start和end之间。其中start,end和sep都是可选的。 | -| xs.stringPrefix | 返回一个字符串,该字符串是以容器名开头的xs.toString。 | -| **视图(View):** | | -| xs.view | 通过容器xs生成一个视图。 | -| xs view (from, to) | 生成一个表示在指定索引范围内的xs元素的视图。 | - diff --git a/zh-cn/overviews/collections/views.md b/zh-cn/overviews/collections/views.md deleted file mode 100644 index e01cd42ebf..0000000000 --- a/zh-cn/overviews/collections/views.md +++ /dev/null @@ -1,125 +0,0 @@ ---- -layout: overview-large -title: 视图 - -discourse: false - -partof: collections -num: 14 -language: zh-cn ---- - -各种容器类自带一些用于开发新容器的方法。例如map、filter和++。我们将这类方法称为转换器(transformers),喂给它们一个或多个容器,它们就会输入一个新容器。 - -有两个主要途径实现转换器(transformers)。一个途径叫紧凑法,就是一个容器及其所有单元构造成这个转换器(transformers)。另一个途径叫松弛法或惰性法(lazy),就是一个容器及其所有单元仅仅是构造了结果容器的代理,并且结果容器的每个单元都是按单一需求构造的。 - -作为一个松弛法转换器的例子,分析下面的 lazy map操作: - - def lazyMap[T, U](coll: Iterable[T], f: T => U) = new Iterable[U] { - def iterator = coll.iterator map f - } - -注意lazyMap构造了一个没有遍历容器coll(collection coll)所有单元的新容器Iterable。当需要时,函数f 可作用于一个该新容器的迭代器单元。 - -除了Stream的转换器是惰性实现的外,Scala的其他容器默认都是用紧凑法实现它们的转换器。 -然而,通常基于容器视图,可将容器转换成惰性容器,反之亦可。视图是代表一些基容器但又可以惰性得构成转换器(transformers)的一种特殊容器。 - -从容器转换到其视图,可以使用容器相应的视图方法。如果xs是个容器,那么xs.view就是同一个容器,不过所有的转换器都是惰性的。若要从视图转换回紧凑型容器,可以使用强制性方法。 - -让我们看一个例子。假设你有一个带有int型数据的vector对象,你想用map函数对它进行两次连续的操作 - - scala> val v = Vector(1 to 10: _*) - v: scala.collection.immutable.Vector[Int] = - Vector(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) - scala> v map (_ + 1) map (_ * 2) - res5: scala.collection.immutable.Vector[Int] = - Vector(4, 6, 8, 10, 12, 14, 16, 18, 20, 22) - -在最后一条语句中,表达式`v map (_ + 1) ` 构建了一个新的vector对象,该对象被map第二次调用`(_ * 2)`而转换成第3个vector对象。很多情况下,从map的第一次调用构造一个中间结果有点浪费资源。上述示例中,将map的两次操作结合成一次单一的map操作执行得会更快些。如果这两次操作同时可行,则可亲自将它们结合成一次操作。但通常,数据结构的连续转换出现在不同的程序模块里。融合那些转换将会破坏其模块性。更普遍的做法是通过把vector对象首先转换成其视图,然后把所有的转换作用于该视图,最后强制将视图转换成vector对象,从而避开出现中间结果这种情况。 - - scala> (v.view map (_ + 1) map (_ * 2)).force - res12: Seq[Int] = Vector(4, 6, 8, 10, 12, 14, 16, 18, 20, 22) - -让我们按这个步骤一步一步再做一次: - - scala> val vv = v.view - vv: scala.collection.SeqView[Int,Vector[Int]] = - SeqView(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) - - v.view 给出了SeqView对象,它是一个延迟计算的Seq。SeqView有两个参数,第一个是整型(Int)表示视图单元的类型。第二个Vector[Int]数组表示当需要强制将视图转回时构造函数的类型。 - -将第一个map 转换成视图可得到: - - scala> vv map (_ + 1) - res13: scala.collection.SeqView[Int,Seq[_]] = SeqViewM(...) - -map的结果是输出`SeqViewM(...)`的值。实质是记录函数`map (_ + 1)`应用在vector v数组上的封装。除非视图被强制转换,否则map不会被执行。然而,`SeqView `后面的 `‘’M‘’`表示这个视图包含一个map操作。其他字母表示其他延迟操作。比如`‘’S‘’`表示一个延迟的slice操作,而`‘’R‘’`表示reverse操作。现在让我们将第二个map操作作用于最后的结果。 - - scala> res13 map (_ * 2) - res14: scala.collection.SeqView[Int,Seq[_]] = SeqViewMM(...) - -现在得到了包含2个map操作的`SeqView`对象,这将输出两个`‘’M‘’: SeqViewMM(...)`。最后强制转换最后结果: - - scala> res14.force res15: Seq[Int] = Vector(4, 6, 8, 10, 12, 14, 16, 18, 20, 22) - -两个存储函数应用于强制操作的执行部分并构造一个新的矢量数组。这样,没有中间数据结构是必须的。 - -需要注意的是静态类型的最终结果是Seq对象而不是Vector对象。跟踪类型后我们看到一旦第一个延迟map被应用,就会得到一个静态类型的`SeqViewM[Int, Seq[_]`。就是说,应用于特定序列类型的矢量数组的"knowledge"会被丢失。一些类的视图的实现需要大量代码,于是Scala 容器链接库仅主要为一般的容器类型而不是特殊功能(一个例外是数组:将数组操作延迟会再次给予静态类型数组的结果)的实现提供视图。 - -有2个理由使您考虑使用视图。首先是性能。你已经看到,通过转换容器为视图可以避免中间结果。这些节省是非常重要的。就像另一个例子,考虑到在一个单词列表找到第一个回文问题。回文就是顺读或倒读都一样的单词。以下是必要的定义: - - def isPalindrome(x: String) = x == x.reverse - def findPalidrome(s: Seq[String]) = s find isPalindrome - -现在,假设你有一个很长序列的单词表,你想在这个序列的第一百万个字内找到回文。你能复用findPalidrome么?当然,你可以写: - - findPalindrome(words take 1000000) - -这很好地解决了两个方面问题:提取序列的第一个百万单词,找到一个回文结构。但缺点是,它总是构建由一百万个字组成的中间序列,即使该序列的第一个单词已经是一个回文。所以可能,999 '999个单词在根本没被检查就复制到中间的结果(数据结构中)。很多程序员会在这里放弃转而编写给定参数前缀的寻找回文的自定义序列。但对于视图(views),这没必要。简单地写: - - findPalindrome(words.view take 1000000) - -这同样是一个很好的分选,但不是一个序列的一百万个元素,它只会构造一个轻量级的视图对象。这样,你无需在性能和模块化之间衡量取舍。 - -第二个案例适用于遍历可变序列的视图。许多转换器函数在那些视图提供视窗给部分元素可以非常规更新的原始序列。通过一个示例看看这种情形。让我们假定有一个数组arr: - - scala> val arr = (0 to 9).toArray - arr: Array[Int] = Array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9) - -你可以在arr数组视图的一部分里创建一个子窗体。 - - scala> val subarr = arr.view.slice(3, 6) - subarr: scala.collection.mutable.IndexedSeqView[ - Int,Array[Int]] = IndexedSeqViewS(...) - -这里给出了一个视图subarr指向从数组arr的第三个元素开始的5个元素组成的子数组。这个视图没有拷贝这些元素,而只是提供了它们的一个映射。现在,假设你有一个修改序列元素的方法。例如,下面的negate方法将对给定整数序列的所有元素取反操作: - - scala> def negate(xs: collection.mutable.Seq[Int]) = - for (i <- 0 until xs.length) xs(i) = -xs(i) - negate: (xs: scala.collection.mutable.Seq[Int])Unit - -假定现在你要对数组arr里从第3个元素开始的5个元素取反操作。你能够使用negate方法来做么?使用视图,就这么简单: - - scala> negate(subarr) - scala> arr - res4: Array[Int] = Array(0, 1, 2, -3, -4, -5, 6, 7, 8, 9) - -看看发生什么了,negate方法改变了从数组arr截取元素生成的数组subarr里面的所有元素。你再次看到视图(views)在保持模块化方面的功效。上面的代码完美地分离了使用方法时如何安排下标顺序和使用什么方法的问题。 - -看了这些漂亮的视图应用示例你可能会困惑于为什么怎么还是会有 strict型容器存在了?一个原因是 lazy型容器性能不总是优于strict型容器的。对于较小的容器在视图里创建和关闭应用附加开销通常是大于从避免中间数据结构的增益。一个更重要的原因是,如果延迟操作有副作用,可能导致视图非常混乱。 - -这里有一个使用2.8版本以前的Scala的几个用户的例子。在这些版本中, Range类型是延迟型的。所以它表现的效果就像一个视图。人们试图创造一些对象像这样: - - val actors = for (i <- 1 to 10) yield actor { ... } - -令他们吃惊的是,没有对象被执行。甚至在后面括号里的代码里无法创建和启动对象方法。对于为什么什么都没发生,记住,对上述表达式等价于map应用: - - val actors = (1 to 10) map (i => actor { ... }) - -由于先前的范围由(1~10)表现得像一个视图,map的结果又是一个视图。那就是,没有元素计算,并且,因此,没有对象的构建!对象会在整个表达的范围内被强制创建,但这并不就是对象要完成的工作。 - -为了避免这样的疑惑,Scala 2.8版容器链接库有了更严格的规定。除streams 和 views 外所有容器都是strict型的。只有一种途径将strict型容器转换成lazy型,那就是采用视图(view)方法。而唯一可逆的途径(from lazy to strict)就是采用强制。因此在Scala 2.8版里actors 对象上面的定义会像预期的那样,这将创建和启动10个actors对象。回到先前疑惑处,你可以增加一个明确的视图方法调用: - - val actors = for (i <- (1 to 10).view) yield actor { ... } - -总之,视图是协调性能和模块化的一个强大工具。但为了不被延迟利弊评估方面的纠缠,应该在2个方面对视图进行约束。要么你将在容器转换器不产生副作用的纯粹的功能代码里使用视图。要么你将它们应用在所有的修改都是明确的可变容器。最好的规避就是混合视图和操作,创建新的根接口,同时消除片面影响。 diff --git a/zh-cn/overviews/core/actors-migration-guide.md b/zh-cn/overviews/core/actors-migration-guide.md deleted file mode 100644 index 641dc5b61b..0000000000 --- a/zh-cn/overviews/core/actors-migration-guide.md +++ /dev/null @@ -1,466 +0,0 @@ ---- -layout: overview -language: zh-cn -label-color: success -label-text: New in 2.10 -overview: actors-migration-guide -title: Scala Actors迁移指南 - -discourse: false ---- - -**Vojin Jovanovic 和 Philipp Haller 著** - -## 概述 - -从Scala的2.11.0版本开始,Scala的Actors库已经过时了。早在Scala2.10.0的时候,默认的actor库即是Akka。 - -为了方便的将Scala Actors迁移到Akka,我们提供了Actor迁移工具包(AMK)。通过在一个项目的类路径中添加scala-actors-migration.jar,AMK包含了一个针对Scala Actors扩展。此外,Akka 2.1包含一些特殊功能,比如ActorDSL singleton,可以实现更简单的转换功能,使Scala Actors代码变成Akka代码。本章内容的目的是用来指导用户完成迁移过程,并解释如何使用AMK。 - -本指南包括以下内容:在“迁移工具的局限性”章节中,我们在此概述了迁移工具的主要局限性。在“迁移概述”章节中我们描述了迁移过程和谈论了Scala的变化分布,使得迁移成为一种可能。最后,在“一步一步指导迁移到Akka”章节里,我们展示了一些迁移工作的例子,以及各个步骤,如果需要从Scala Actors迁移至Akka's actors,本节是推荐阅读的。 - -免责声明:并发代码是臭名昭著的,当出现bug时很难调试和修复。由于两个actor的不同实现,这种差异导致可能出现错误。迁移过程没一步后都建议进行完全的代码测试。 - -## 迁移工具的局限性 - -由于Akka和Scala的actor模型的完整功能不尽相同导致两者之间不能平滑地迁移。下面的列表解释了很难迁移的部分行为: - -1. 依靠终止原因和双向行为链接方法 - Scala和Akka actors有不同的故障处理和actor monitoring模型。在Scala actors模型中,如果一个相关联部分异常终止,相关联的actors终止。如果终止是显式跟踪(通过self.trapExit),actor可以从失败的actor收到终止的原因。通过Akka这个功能不能迁移到AMK。AMK允许迁移的只是[Akka monitoring](http://doc.akka.io/docs/akka/2.1.0/general/supervision.html#What_Lifecycle_Monitoring_Means)机制。Monitoring不同于连接,因为它是单向(unindirectional)的并且终止的原因是现在已知的。如果仅仅是monitoring机制是无法满足需求的,迁移的链接必须推迟到最后一刻(步骤5的迁移)。然后,当迁移到Akka,用户必须创建一个[监督层次(supervision hierarchy)](http://doc.akka.io/docs/akka/2.1.0/general/supervision.html),处理故障。 - -2. 使用restart方法——Akka不提供显式的重启actors,因此上述例子我们不能提供平滑迁移。用户必须更改系统,所以没有使用重启方法(restart method)。 - -3. 使用getState方法 - Akka actors没有显式状态,此功能无法迁移。用户代码必须没有getState调用。 - -4. 实例化后没有启动actors - Akka actors模型会在实例化后自动启动actors,所以用户不需要重塑系统来显式的在实例化后启动actors。 - -5. mailboxSize方法不存在Akka中,因此不能迁移。这种方法很少使用,很容易被删除。 - -## 迁移概述 - -### 迁移工具 - -在Scal 2.10.0 actors 是在[Scala distribution](http://www.scala-lang.org/downloads)中作为一个单独包(scala-actors.jar)存在的,并且他们的接口已被弃用。这种分布也包含在Akka actors的akka-actor.jar里。AMK同时存在Scala actors 和 akka-actor.jar之中。未来的主要版本的Scala将不包含Scala actors和AMK。 - -开始迁移,用户需要添加scala-actors.jar和scala-actors-migration.jar来构建他们的项目。添加scala-actors.jar和scala-actors-migration.jar允许使用下面描述的AMK。这些jar位于[Scala Tools](https://oss.sonatype.org/content/groups/scala-tools/org/scala-lang/)库和[Scala distribution](http://www.scala-lang.org/downloads)库中。 - -### 一步一步来迁移 - -Actor迁移工具使用起来应该有5步骤。每一步都设计为引入的基于代码的最小变化。在前四个迁移步骤的代码中将使用Scala actors来实现,并在该步完成后运行所有的系统测试。然而,方法和类的签名将被转换为与Akka相似。迁移工具在Scal方面引入了一种新的actor类型(ActWithStash)和强制执行actors的ActorRef接口。 - -该结果同样强制通过一个特殊的方法在ActorDSL 对象上创建actors。在这些步骤可以每次迁移一个actor。这降低了在同一时刻引入多个bug的可能性,同样降低了bug的复杂程度。 - -在Scala方面迁移完成后,用户应该改变import语句并变成使用Akka库。在Akka方面,ActorDSL和ActWithStash允许对Scala Actors和他们的生态系的react construct进行建模。这个步骤迁移所有actors到Akka的后端,会在系统中引入bug。一旦代码迁移到Akka,用户将能够使用Akka的所有的功能的。 - -### 一步一步指导迁移到Akka - -在这一章中,我们将通过actor迁移的5个步骤。在每一步之后的代码都要为可能的错误进行检测。在前4个步骤中可以一边迁移一个actor和一边测试功能。然而,最后一步迁移所有actors到Akka后它只能作为一个整体进行测试。在这个步骤之后系统应该具有和之前一样相同的功能,不过它将使用Akka actor库。 - -### 步骤1——万物皆是Actor - -Scala actors库提供了公共访问多个类型的actors。他们被组织在类层次结构和每个子类提供了稍微更丰富的功能。为了进一步的使迁移步骤更容易,我们将首先更改Actor类型系统中的每一个actor。这种迁移步骤很简单,因为Actor类位于层次结构的底部,并提供了广泛的功能。 - -来自Scala库的Actors应根据以下规则进行迁移: - -1. class MyServ extends Reactor[T] -> class MyServ extends Actor - -注意,反应器提供了一个额外的类型参数代表了类型的消息收到。如果用户代码中使用这些信息,那么一个需要:i)应用模式匹配与显式类型,或者ii)做一个向下的消息来自任何泛型T。 - -1. class MyServ extends ReplyReactor -> class MyServ extends Actor - -2. class MyServ extends DaemonActor -> class MyServ extends Actor - -为了为DaemonActor提供配对功能,将下列代码添加到类的定义。 - - override def scheduler: IScheduler = DaemonScheduler - -### 步骤2 - 实例化 - -在Akka中,actors可以访问只有通过ActorRef接口。ActorRef的实例可以通过在ActorDSL对象上调用actor方法或者通过调用ActorRefFactory实例的actorOf方法来获得。在Scala的AMK工具包中,我们提供了Akka ActorRef和ActorDSL的一个子集,该子集实际上是Akka库的一个单例对象(singleton object)。 - -这一步的迁移使所有actors访问通过ActorRefs。首先,我们现实如何迁移普通模式的实例化Sacla Actors。然后,我们将展示如何分别克服问题的ActorRef和Actor的不同接口。 - -#### Actor实例化 - -actor实例的转换规则(以下规则需要import scala.actors.migration._): - -1. 构造器调用实例化 - - val myActor = new MyActor(arg1, arg2) - myActor.start() - -应该被替换 - - ActorDSL.actor(new MyActor(arg1, arg2)) - -2. 用于创建Actors的DSL(译注:领域专用语言(Domain Specific Language)) - - val myActor = actor { - // actor 定义 - } -应该被替换 - - val myActor = ActorDSL.actor(new Actor { - def act() { - // actor 定义 - } - }) - -3. 从Actor Trait扩展来的对象 - - object MyActor extends Actor { - // MyActor 定义 - } - MyActor.start() -应该被替换 - - class MyActor extends Actor { - // MyActor 定义 - } - - object MyActor { - val ref = ActorDSL.actor(new MyActor) - } -所有的MyActor地想都应该被替换成MyActor.ref。 - -需要注意的是Akka actors在实例化的同时开始运行。actors创建并开始在迁移的系统的情况下,actors在不同的位置以及改变这可能会影响系统的行为,用户需要更改代码,以使得actors在实例化后立即开始执行。 - -远程actors也需要被获取作为ActorRefs。为了得到一个远程actor ActorRef需使用方法selectActorRef。 - -#### 不同的方法签名(signatures) - -至此为止我们已经改变了所有的actor实例化,返回ActorRefs,然而,我们还没有完成迁移工作。有不同的接口在ActorRefs和Actors中,因此我们需要改变在每个迁移实例上触发的方法。不幸的是,Scala Actors提供的一些方法不能迁移。对下列方法的用户需要找到一个解决方案: - -1. getState()——Akka中的actors 默认情况下由其监管actors(supervising actors)负责管理和重启。在这种情况下,一个actor的状态是不相关的。 - -2. restart() - 显式的重启一个Scala actor。在Akka中没有相应的功能。 - -所有其他Actor方法需要转换为两个ActorRef中的方法。转换是通过下面描述的规则。请注意,所有的规则需要导入以下内容: - - import scala.concurrent.duration._ - import scala.actors.migration.pattern.ask - import scala.actors.migration._ - import scala.concurrent._ -额外规则1-3的作用域定义在无限的时间需要一个隐含的超时。然而,由于Akka不允许无限超时,我们会使用100年。例如: - - implicit val timeout = Timeout(36500 days) - -规则: - -1. !!(msg: Any): Future[Any] 被?替换。这条规则会改变一个返回类型到scala.concurrent.Future这可能导致类型不匹配。由于scala.concurrent.Future比过去的返回值具有更广泛的功能,这种类型的错误可以很容易地固定在与本地修改: - - actor !! message -> respActor ? message - -2. !![A] (msg: Any, handler: PartialFunction[Any, A]): Future[A] 被?取代。处理程序可以提取作为一个单独的函数,并用来生成一个future对象结果。处理的结果应给出另一个future对象结果,就像在下面的例子: - - val handler: PartialFunction[Any, T] = ... // handler - actor !! (message, handler) -> (respActor ? message) map handler - -3. !? (msg: Any):任何被?替换都将阻塞在返回的future对象上 - - actor !? message -> - Await.result(respActor ? message, Duration.Inf) - -4. !? (msec: Long, msg: Any): Option[Any]任何被?替换都将显式的阻塞在future对象 - - actor !? (dur, message) -> - val res = respActor.?(message)(Timeout(dur milliseconds)) - val optFut = res map (Some(_)) recover { case _ => None } - Await.result(optFut, Duration.Inf) - -这里没有提到的公共方法是为了actors DSL被申明为公共的。他们只能在定义actor时使用,所以他们的这一步迁移是不相关的。 - -###第3步 - 从Actor 到 ActWithStash - -到目前为止,所有的控制器都继承自Actor trait。我们通过指定的工厂方法来实例化控制器,所有的控制器都可以通过接口ActorRef 来进行访问。现在我们需要把所有的控制器迁移的AMK 的 ActWithStash 类上。这个类的行为方式和Scala的Actor几乎完全一致,它提供了另外一些方法,对应于Akka的Actor trait。这使得控制器更易于逐步的迁移到Akka。 - -为了达到这个目的,所有的从Actor继承的类,按照下列的方式,需要改为继承自ActWithStash: - - class MyActor extends Actor -> class MyActor extends ActWithStash - -经过这样修改以后,代码会无法通过编译。因为ActWithStash中的receive 方法不能在act中像原来那样使用。要使代码通过编译,需要在所有的 receive 调用中加上类型参数。例如: - - receive { case x: Int => "Number" } -> - receive[String] { case x: Int => "Number" } - -另外,要使代码通过编译,还要在act方法前加上 override关键字,并且定义一个空的receive方法。act方法需要被重写,因为它在ActWithStash 的实现中模拟了Akka的消息处理循环。需要修改的地方请看下面的例子: - - class MyActor extends ActWithStash { - - // 空的 receive 方法 (现在还没有用) - def receive = {case _ => } - - override def act() { - // 原来代码中的 receive 方法改为 react。 - } - } -ActWithStash 的实例中,变量trapExit 的缺省值是true。如果希望改变,可以在初始化方法中把它设置为false。 - -远程控制器在ActWithStash 下无法直接使用,register('name, this)方法需要被替换为: - - registerActorRef('name, self) - -在后面的步骤中, registerActorRef 和 alive 方法的调用与其它方法一样。 - -现在,用户可以测试运行,整个系统的运行会和原来一样。ActWithStash 和Actor 拥有相同的基本架构,所以系统的运行会与原来没有什么区别。 - -### 第4步 - 去掉act 方法 - -在这一节,我们讨论怎样从ActWithStash中去掉act方法,以及怎样修改其他方法,使它与Akka更加契合. 这一环节会比较繁杂,所以我们建议最好一次只修改一个控制器。在Scala中,控制器的行为主要是在act方法的中定义。逻辑上来说,控制器是一个并发执行act方法的过程,执行完成后过程终止。在Akka中,控制器用一个全局消息处理器来依次处理它的的消息队列中的消息。这个消息处理器是一个receive函数返回的偏函数(partial function),该偏函数被应用与每一条消息上。 - -因为ActWithStash中Akka方法的行为依赖于移除的act方法,所以我们首先要做的是去掉act方法。然后,我们需要按照给定的规则修改scala.actors.Actor中每个方法的。 - -#### 怎样去除act 方法 - -在下面的列表中,我们给出了通用消息处理模式的修改规则。这个列表并不包含所有的模式,它只是覆盖了其中一些通用的模式。然而用户可以通过参考这些规则,通过扩展简单规则,将act方法移植到Akka。 - -嵌套调用react/reactWithin需要注意:消息处理偏函数需要做结构扩展,使它更接近Akka模式。尽管这种修改会很复杂,但是它允许任何层次的嵌套被移植。下面有相关的例子。 - -在复杂控制流中使用receive/receiveWithin需要注意:这个移植会比较复杂,因为它要求重构act方法。在消息处理偏函数中使用react 和 andThen可以使receive的调用模型化。下面是一些简单的例子。 - -1. 如果在act方法中有一些代码在第一个包含react的loop之前被执行,那么这些代码应该被放在preStart方法中。 - - def act() { - //初始化的代码放在这里 - loop { - react { ... } - } - } -应该被替换 - - override def preStart() { - //初始化的代码放在这里 - } - - def act() { - loop { - react{ ... } - } - } -其他的模式,如果在第一个react 之前有一些代码,也可以使用这个规则。 - -2. 当act 的形式为:一个简单loop循环嵌套react,用下面的方法。 - - def act() = { - loop { - react { - // body - } - } - } -应该被替换 - - def receive = { - // body - } - -3. 当act包含一个loopWhile 结构,用下面的方法。 - - def act() = { - loopWhile(c) { - react { - case x: Int => - // do task - if (x == 42) { - c = false - } - } - } - } -应该被替换 - - def receive = { - case x: Int => - // do task - if (x == 42) { - context.stop(self) - } - } - -4. 当act包含嵌套的react,用下面的规则: - - def act() = { - var c = true - loopWhile(c) { - react { - case x: Int => - // do task - if (x == 42) { - c = false - } else { - react { - case y: String => - // do nested task - } - } - } - } - } -应该被替换 - - def receive = { - case x: Int => - // do task - if (x == 42) { - context.stop(self) - } else { - context.become(({ - case y: String => - // do nested task - }: Receive).andThen(x => { - unstashAll() - context.unbecome() - }).orElse { case x => stash(x) }) - } - } - -5. reactWithin方法使用下面的修改规则: - - loop { - reactWithin(t) { - case TIMEOUT => // timeout processing code - case msg => // message processing code - } - } -应该被替换 - - import scala.concurrent.duration._ - - context.setReceiveTimeout(t millisecond) - def receive = { - case ReceiveTimeout => // timeout processing code - case msg => // message processing code - } - -6. 在Akka中,异常处理用另一种方式完成。如果要模拟Scala控制器的方式,那就用下面的方法 - - def act() = { - loop { - react { - case msg => - // 可能会失败的代码 - } - } - } - - override def exceptionHandler = { - case x: Exception => println("got exception") - } -应该被替换 - - def receive = PFCatch({ - case msg => - // 可能会失败的代码 - }, { case x: Exception => println("got exception") }) - PFCatch 的定义 - - class PFCatch(f: PartialFunction[Any, Unit], - handler: PartialFunction[Exception, Unit]) - extends PartialFunction[Any, Unit] { - - def apply(x: Any) = { - try { - f(x) - } catch { - case e: Exception if handler.isDefinedAt(e) => - handler(e) - } - } - - def isDefinedAt(x: Any) = f.isDefinedAt(x) - } - - object PFCatch { - def apply(f: PartialFunction[Any, Unit], - handler: PartialFunction[Exception, Unit]) = - new PFCatch(f, handler) - } - -PFCatch并不包含在AMK之中,所以它可以保留在移植代码中,AMK将会在下一版本中被删除。当整个移植完成后,错误处理也可以改由Akka来监管。 - -#### 修改Actor的方法 - -当我们移除了act方法以后,我们需要替换在Akka中不存在,但是有相似功能的方法。在下面的列表中,我们给出了两者的区别和替换方法: - -1. exit()/exit(reason) - 需要由 context.stop(self) 替换 - -2. receiver - 需要由 self 替换 - -3. reply(msg) - 需要由 sender ! msg 替换 - -4. link(actor) - 在Akka中,控制器之间的链接一部分由[supervision](http://doc.akka.io/docs/akka/2.1.0/general/supervision.html#What_Supervision_Means)来完成,一部分由[actor monitoring](http://doc.akka.io/docs/akka/2.1.0/general/supervision.html#What_Lifecycle_Monitoring_Means)来完成。在AMK中,我们只支持监测方法。因此,这部分Scala功能可以被完整的移植。 - -linking 和 watching 之间的区别在于:watching actor总是接受结束通知。然而,不像Scala的Exit消息包含结束的原因,Akka的watching 返回Terminated(a: ActorRef)消息,只包含ActorRef。获取结束原因的功能无法被移植。在Akka中,这一步骤可以在第4步之后,通过组织控制器的监管层级 [supervision hierarchy](http://doc.akka.io/docs/akka/2.1.0/general/supervision.html)来完成。 - -如果watching actors收到的消息不撇陪结束消息,控制器会被终止并抛出DeathPactException异常。注意就算watching actors正常的结束,也会发生这种情况。在Scala中,linked actors只要一方不正常的终止,另一方就会以相同的原因终止。 - -如果系统不能单独的用 watch actors来 移植,用户可以像原来那样用link和exit(reason)来使用。然而,因为act()重载了Exit消息,需要做如下的修改: - - case Exit(actor, reason) => - println("sorry about your " + reason) - ... -应该被替换 - - case t @ Terminated(actorRef) => - println("sorry about your " + t.reason) - ... -注意:在Scala和Akka的actor之间有另一种细微的区别:在Scala, link/watch 到已经终止的控制器不会有任何影响。在Akka中,看管已经终止的控制器会导致发送终止消息。这会在系统移植的第5 步导致不可预料的结果。 - -### 第5步 - Akka后端的移植 - -到目前为止,用户代码已经做好了移植到Akka actors的准备工作。现在我们可以把Scala actors迁移到Akka actor上。为了完成这一目标,需要配置build,去掉scala-actors.jar 和 scala-actors-migration.jar,把 akka-actor.jar 和 typesafe-config.jar加进来。AMK只能在Akka actor 2.1下正常工作,Akka actor 2.1已经包含在分发包 [Scala distribution](http://www.scala-lang.org/downloads)中, 可以用这样的方法配置。 - -经过这一步骤以后,因为包名的不同和API之间的细微差别,编译会失败。我们必须将每一个导入的actor从scala 修改为Akka。下列是部分需要修改的包名: - - scala.actors._ -> akka.actor._ - scala.actors.migration.ActWithStash -> akka.actor.ActorDSL._ - scala.actors.migration.pattern.ask -> akka.pattern.ask - scala.actors.migration.Timeout -> akka.util.Timeout - -当然,ActWithStash 中方法的声明 def receive = 必须加上前缀override。 - -在Scala actor中,stash 方法需要一个消息做为参数。例如: - - def receive = { - ... - case x => stash(x) - } - -在Akka中,只有当前处理的消息可以被隐藏(stashed)。因此,上面的例子可以替换为: - - def receive = { - ... - case x => stash() - } - -#### 添加Actor System - -Akka actor 组织在[Actor systems](http://doc.akka.io/docs/akka/2.1.0/general/actor-systems.html)系统中。每一个被实例化的actor必须属于某一个ActorSystem。因此,要添加一个ActorSystem 实例作为每个actor 实例调用的第一个参数。下面给出了例子。 - -为了完成该转换,你需要有一个actor system 实例。例如: - - val system = ActorSystem("migration-system") - -然后,做如下转换: - - ActorDSL.actor(...) -> ActorDSL.actor(system)(...) - -如果对actor 的调用都使用同一个ActorSystem ,那么它可以作为隐式参数来传递。例如: - - ActorDSL.actor(...) -> - import project.implicitActorSystem - ActorDSL.actor(...) - -当所有的主线程和actors结束后,Scala程序会终止。迁移到Akka后,当所有的主线程结束,所有的actor systems关闭后,程序才会结束。Actor systems 需要在程序退出前明确的中止。这需要通过在Actor system中调用shutdown 方法来完成。 - -#### 远程 Actors - -当代码迁移到Akka,远程actors就不再工作了。 registerActorFor 和 alive 方法需要被移除。 在Akka中,远程控制通过配置独立的完成。更多细节请参考[Akka remoting documentation](http://doc.akka.io/docs/akka/2.1.0/scala/remoting.html)。 - -#### 样例和问题 - -这篇文档中的所有程序片段可以在[Actors Migration test suite](http://github.com/scala/actors-migration/tree/master/src/test/)中找到,这些程序做为测试文件,前缀为actmig。 - -这篇文档和Actor移植组件由 [Vojin Jovanovic](http://people.epfl.ch/vojin.jovanovic)和[Philipp Haller](http://lampwww.epfl.ch/~phaller/)编写。 - -如果你发现任何问题或不完善的地方,请把它们报告给 [Scala Bugtracker](https://github.com/scala/actors-migration/issues)。 - diff --git a/zh-cn/overviews/core/actors.md b/zh-cn/overviews/core/actors.md deleted file mode 100644 index 01809d1e1e..0000000000 --- a/zh-cn/overviews/core/actors.md +++ /dev/null @@ -1,307 +0,0 @@ ---- -layout: overview -title: The Scala Actors API -overview: actors -language: zh-cn - -discourse: false ---- - -**Philipp Haller 和 Stephen Tu 著** - -## 简介 - -本指南介绍了Scala 2.8和2.9中`scala.actors`包的API。这个包的内容因为逻辑上相通,所以放到了同一个类型的包内。这个trait在每个章节里面都会有所涉及。这章的重点在于这些traits所定义的各种方法在运行状态时的行为,由此来补充现有的Scala基础API。 - -注意:在Scala 2.10版本中这个Actors库将是过时的,并且在未来Scala发布的版本中将会被移除。开发者应该使用在`akka.actor`包中[Akka](http://akka.io/) actors来替代它。想了解如何将代码从Scala actors迁移到Akka请参考[Actors 迁移指南](http://docs.scala-lang.org/overviews/core/actors-migration-guide.html)章节。 - -## Actor trait:Reactor, ReplyReactor和Actor - -### Reactor trait - -Reactor 是所有`actor trait`的父级trait。扩展这个trait可以定义actor,其具有发送和接收消息的基本功能。 - -Reactor的行为通过实现其act方法来定义。一旦调用start方法启动Reactor,这个act方法便会执行,并返回这个Reactor对象本身。start方法是具有等幂性的,也就是说,在一个已经启动了的actor对象上调用它(start方法)是没有作用的。 - -Reactor trait 有一个Msg 的类型参数,这个参数指明这个actor所能接收的消息类型。 - -调用Reactor的!方法来向接收者发送消息。用!发送消息是异步的,这样意味着不会等待消息被接收——它在发送消息后便立刻往下执行。例如:`a ! msg`表示向`a`发送`msg`。每个actor都有各自的信箱(mailbox)作为缓冲来存放接收到的消息,直至这些消息得到处理。 - -Reactor trait中也定义了一个forward方法,这个方法继承于OutputChannel。它和!(感叹号,发送方法)有同样的作用。Reactor的SubTrait(子特性)——特别是`ReplyReactor trait`——覆写了此方法,使得它能够隐式地回复目标。(详细地看下面的介绍) - -一个Reactor用react方法来接收消息。react方法需要一个PartialFunction[Msg, Unit]类型的参数,当消息到达actor的邮箱之后,react方法根据这个参数来确定如何处理消息。在下面例子中,当前的actor等待接收一个“Hello”字符串,然后打印一句问候。 - - react { - case "Hello" => println("Hi there") - } - -调用react没有返回值。因此,在接收到一条消息后,任何要执行的代码必须被包含在传递给react方法的偏函数(partial function)中。举个例子,通过嵌套两个react方法调用可以按顺序接收到两条消息: - - react { - case Get(from) => - react { - case Put(x) => from ! x - } - } - -Reactor trait 也提供了控制结构,简化了react方法的代码。 - -### 终止和执行状态 - -当Reactor的act方法完整执行后, Reactor则随即终止执行。Reactor也可以显式地使用exit方法来终止自身。exit方法的返回值类型为Nothing,因为它总是会抛出异常。这个异常仅在内部使用,并且不应该去捕捉这个异常。 - -一个已终止的Reactor可以通过它的restart方法使它重新启动。对一个未终止的Reactor调用restart方法则会抛出`IllegalStateException`异常。重新启动一个已终止的actor则会使它的act方法重新运行。 - -Reactor定义了一个getState方法,这个方法可以将actor当前的运行状态作为Actor.State枚举的一个成员返回。一个尚未运行的actor处于`Actor.State.New`状态。一个能够运行并且不在等待消息的actor处于`Actor.State.Runnable`状态。一个已挂起,并正在等待消息的actor处于`Actor.State.Suspended`状态。一个已终止的actor处于`Actor.State.Terminated`状态。 - -### 异常处理 - -exceptionHandler成员允许定义一个异常处理程序,其在Reactor的整个生命周期均可用。 - - def exceptionHandler: PartialFunction[Exception, Unit] - -exceptionHandler返回一个偏函数,它用来处理其他没有被处理的异常。每当一个异常被传递到Reactor的act方法体之外时,这个成员函数就被应用到该异常,以允许这个actor在它结束前执行清理代码。注意:`exceptionHandler`的可见性为protected。 - -用exceptionHandler来处理异常并使用控制结构对与react的编程是非常有效的。每当exceptionHandler返回的偏函数处理完一个异常后,程序会以当前的后续闭包(continuation closure)继续执行。 - - loop { - react { - case Msg(data) => - if (cond) // 数据处理代码 - else throw new Exception("cannot process data") - } - } - -假设Reactor覆写了exceptionHandler,在处理完一个在react方法体内抛出的异常后,程序将会执行下一个循环迭代。 - -### ReplyReactor trait - -`ReplyReactor trait`扩展了`Reactor[Any]`并且增加或覆写了以下方法: - -!方法被覆写以获得一个当前actor对象(发送方)的引用,并且,这个发送方引用和实际的消息一起被传递到接收actor的信箱(mail box)中。接收方通过其sender方法访问消息的发送方(见下文)。 - -forward方法被覆写以获得一个引用,这个引用指向正在被处理的消息的发送方。引用和实际的消息一起作为当前消息的发送方传递。结果,forward方法允许代表不同于当前actor对象的actor对象转发消息。 - -增加的sender方法返回正被处理的消息的发送方。考虑到一个消息可能已经被转发,发送方可能不会返回实际发送消息的actor对象。 - -增加的reply方法向最后一个消息的发送方回复消息。reply方法也被用作回复一个同步消息发送或者一个使用future的消息发送(见下文)。 - -增加的!?方法提供同步消息发送。调用!?方法会引起发送方actor对象等待,直到收到一个响应,然后返回这个响应。重载的变量有两个。这个双参数变量需要额外的超时参数(以毫秒计),并且,它的返回类型是Option[Any]而不是Any。如果发送方在指定的超时期间没有收到一个响应,!?方法返回None,否则它会返回由Some包裹的响应。 - -增加的!!方法与同步消息发送的相似点在于,它们都允许从接收方传递一个响应。然而,它们返回Future实例,而不是阻塞发送中的actor对象直到接收响应。一旦Future对象可用,它可以被用来重新获得接收方的响应,还可以在不阻塞发送方的情况下,用于获知响应是否可用。重载的变量有两个。双参数变量需要额外的PartialFunction[Any,A]类型的参数。这个偏函数用于对接收方响应进行后处理。本质上,!!方法返回一个future对象,一旦响应被接收,这个future对象把偏函数应用于响应。future对象的结果就是后处理的结果。 - -增加的reactWithin方法允许在一段给定的时间段内接收消息。相对于react方法,这个方法需要一个额外的msec参数,用来指示在这个时间段(以毫秒计)直到匹配指定的TIMEOUT模式为止(TIMEOUT是包scala.actors中的用例对象(case object))。例如: - -reactWithin(2000) { case Answer(text) => // process text case TIMEOUT => println("no answer within 2 seconds") } - -reactWithin方法也允许以非阻塞方式访问信箱。当指定一个0毫秒的时间段时,首先会扫描信箱以找到一个匹配消息。如果在第一次扫描后没有匹配的消息,这个TIMEOUT模式将会匹配。例如,这使得接收某些消息会比其他消息有较高的优先级: - -reactWithin(0) { case HighPriorityMsg => // ... case TIMEOUT => react { case LowPriorityMsg => // ... } } - -在上述例子中,即使在信箱里有一个先到达的低优先级的消息,actor对象也会首先处理下一个高优先级的消息。actor对象只有在信箱里没有高优先级消息时才会首先处理一个低优先级的消息。 - -另外,ReplyReactor 增加了`Actor.State.TimedSuspended`执行状态。一个使用`reactWithin`方法等待接收消息而挂起的actor对象,处在` Actor.State.TimedSuspended `状态。 - -### Actor trait - -Actor trait扩展了`ReplyReactor`并增加或覆写了以下成员: - -增加的receive方法的行为类似react方法,但它可以返回一个结果。这可以在它的类型上反映——它的结果是多态的:def receive[R](f: PartialFunction[Any, R]): R。然而,因为actor对象挂起并等待消息时,receive方法会阻塞底层线程(underlying thread),使用receive方法使actor对象变得更加重量级。直到receive方法的调用返回,阻塞的线程将不能够执行其他actor对象。 - -增加的link和unlink方法允许一个actor对象将自身链接到另一个actor对象,或将自身从另一个actor对象断开链接。链接可以用来监控或对另一个actor对象的终止做出反应。特别要注意的是,正如在Actor trait的API文档中的解释,链接影响调用exit方法的行为。 - -trapExit成员允许对链接的actor对象的终止做出反应而无关其退出的原因(即,无关是否正常退出)。如果一个actor对象的trapExit成员被设置为true,则这个actor对象会因链接的actor对象而永远不会终止。相反,每当其中一个链接的actor对象个终止了,它将会收到类型为Exit的消息。这个Exit case class 有两个成员:from指终止的actor对象;reason指退出原因。 - -### 终止和执行状态 - -当终止一个actor对象的执行时,可以通过调用以下exit方法的变体,显式地设置退出原因: - - def exit(reason: AnyRef): Nothing -当一个actor对象以符号'normal以外的原因退出,会向所有链接到它的atocr对象传递其退出原因。如果一个actor对象由于一个未捕获异常终止,它的退出原因则为一个UncaughtException case class的实例。 - -Actor trait增加了两个新的执行状态。使用receive方法并正在等待接收消息的actor处在`Actor.State.Blocked`状态。使用receiveWithin方法并正在等待接收消息的actor处在`Actor.State.TimedBlocked`状态。 - -## 控制结构 - -Reactor trait定义了控制结构,它简化了无返回的react操作的编程。一般来说,一个react方法调用并不返回。如果actor对象随后应当执行代码,那么,或者显式传递actor对象的后续代码给react方法,或者可以使用下述控制结构,达到隐藏这些延续代码的目的。 - -最基础的控制结构是andThen,它允许注册一个闭包。一旦actor对象的所有其他代码执行完毕,闭包就会被执行。 - - actor { - { - react { - case "hello" => // 处理 "hello" - }: Unit - } andThen { - println("hi there") - } - } - -例如,上述actor实例在它处理了“hello”消息之后,打印一句问候。虽然调用react方法不会返回,我们仍然可以使用andThen来注册这段输出问候的代码(作为actor的延续)。 - -注意:在react方法的调用(: Unit)中存在一种类型归属。总而言之,既然表达式的结果经常可以被忽略,react方法的结果就可以合法地作为Unit类型来处理。andThen方法无法成为Nothing类型(react方法的结果类型)的一个成员,所以在这里有必要这样做。把react方法的结果类型当作Unit,允许实现一个隐式转换的应用,这个隐式转换使得andThen成员可用。 - -API还提供一些额外的控制结构: - -loop { ... }。无限循环,在每一次迭代中,执行括号中的代码。调用循环体内的react方法,actor对象同样会对消息做出反应。而后,继续执行这个循环的下次迭代。 - -loopWhile (c) { ... }。当条件c返回true,执行括号中的代码。调用循环体中的react方法和使用loop时的效果一样。 - -continue。继续执行当前的接下来的后续闭包(continuation closure)。在loop或loopWhile循环体内调用continue方法将会使actor对象结束当前的迭代并继续执行下次迭代。如果使用andThen注册了当前的后续代码,这个闭包会作为第二个参数传给andThen,并以此继续执行。 - -控制结构可以在Reactor对象的act方法中,以及在act方法(传递地)调用的方法中任意处使用。对于用actor{...}这样的缩略形式创建的actor,控制结构可以从Actor对象导入。 - -### Future - -ReplyReactor和Actor trait支持发送带有结果的消息(!!方法),其立即返回一个future实例。一个future即Future trait的一个实例,即可以用来重新获取一个send-with-future消息的响应的句柄。 - -一个send-with-future消息的发送方可以通过应用future来等待future的响应。例如,使用val fut = a !! msg 语句发送消息,允许发送方等待future的结果。如:val res = fut()。 - -另外,一个Future可以在不阻塞的情况下,通过isSet方法来查询并获知其结果是否可用。 - -send-with-future的消息并不是获得future的唯一的方法。future也可以通过future方法计算而得。下述例子中,计算体会被并行地启动运行,并返回一个future实例作为其结果: - - val fut = Future { body } - // ... - fut() // 等待future - -能够通过基于actor的标准接收操作(例如receive方法等)来取回future的结果,使得future实例在actor上下文中变得特殊。此外,也能够通过使用基于事件的操作(react方法和ractWithin方法)。这使得一个actor实例在等待一个future实例结果时不用阻塞它的底层线程。 - -通过future的inputChannel,使得基于actor的接收操作方法可用。对于一个类型为`Future[T]`的future对象而言,它的类型是`InputChannel[T]`。例如: - - val fut = a !! msg - // ... - fut.inputChannel.react { - case Response => // ... - } - -## Channel(通道) - -channnel可以用来对发送到同一actor的不同类型消息的处理进行简化。channel的层级被分为OutputChannel和InputChannel。 - -OutputChannel可用于发送消息。OutputChannel的out方法支持以下操作。 - -out ! msg。异步地向out方法发送msg。当msg直接发送给一个actor,一个发送中的actor的引用会被传递。 - -out forward msg。异步地转发msg给out方法。当msg被直接转发给一个actor,发送中的actor会被确定。 - -out.receiver。返回唯一的actor,其接收发送到out channel(通道)的消息。 - -out.send(msg, from)。异步地发送msg到out,并提供from作为消息的发送方。 - -注意:OutputChannel trait有一个类型参数,其指定了可以被发送到channel(通道)的消息类型(使用!、forward和send)。这个类型参数是逆变的: - - trait OutputChannel[-Msg] - -Actor能够从InputChannel接收消息。就像OutputChannel,InputChannel trait也有一个类型参数,用于指定可以从channel(通道)接收的消息类型。这个类型参数是协变的: - - trait InputChannel[+Msg] - -An` InputChannel[Msg] `in支持下列操作。 - -in.receive { case Pat1 => ... ; case Patn => ... }(以及类似的 in.receiveWithin)。从in接收一个消息。在一个输入channel(通道)上调用receive方法和actor的标准receive操作具有相同的语义。唯一的区别是,作为参数被传递的偏函数具有PartialFunction[Msg, R]类型,此处R是receive方法的返回类型。 - -in.react { case Pat1 => ... ; case Patn => ... }(以及类似的in.reactWithin)通过基于事件的react操作,从in方法接收一个消息。就像actor的react方法,返回类型是Nothing。这意味着此方法的调用不会返回。就像之前的receive操作,作为参数传递的偏函数有一个更具体的类型:PartialFunction[Msg, Unit] - -### 创建和共享channel - -channel通过使用具体的Channel类创建。它同时扩展了InputChannel和OutputChannel。使channel在多个actor的作用域(Scope)中可见,或者将其在消息中发送,都可以实现channel的共享。 - -下面的例子阐述了基于作用域(scope)的共享。 - - actor { - var out: OutputChannel[String] = null - val child = actor { - react { - case "go" => out ! "hello" - } - } - val channel = new Channel[String] - out = channel - child ! "go" - channel.receive { - case msg => println(msg.length) - } - } - -运行这个例子将输出字符串“5”到控制台。注意:子actor对out(一个OutputChannel[String])具有唯一的访问权。而用于接收消息的channel的引用则被隐藏了。然而,必须要注意的是,在子actor向输出channel发送消息之前,确保输出channel被初始化到一个具体的channel。通过使用“go”消息来完成消息发送。当使用channel.receive来从channel接收消息时,因为消息是String类型的,可以使用它提供的length成员。 - -另一种共享channel的可行的方法是在消息中发送它们。下面的例子对此作了阐述。 - - case class ReplyTo(out: OutputChannel[String]) - - val child = actor { - react { - case ReplyTo(out) => out ! "hello" - } - } - - actor { - val channel = new Channel[String] - child ! ReplyTo(channel) - channel.receive { - case msg => println(msg.length) - } - } - -ReplyTo case class是一个消息类型,用于分派一个引用到OutputChannel[String]。当子actor接收一个ReplyTo消息时,它向它的输出channel发送一个字符串。第二个actor则像以前一样接收那个channel上的消息。 - -## Scheduler - -scheduler用于执行一个Reactor实例(或子类型的一个实例)。Reactor trait引入了scheduler成员,其返回常用于执行Reactor实例的scheduler。 - - def scheduler: IScheduler - -运行时系统通过使用在IScheduler trait中定义的execute方法之一,向scheduler提交任务来执行actor。只有在完整实现一个新的scheduler时(但没有必要),此trait的大多数其他方法才是相关的。 - -默认的scheduler常用于执行Reactor实例,而当所有的actor完成其执行时,Actor则会检测环境。当这发生时,scheduler把它自己关闭(终止scheduler使用的任何线程)。然而,一些scheduler,比如SingleThreadedScheduler(位于scheduler包)必须要通过调用它们的shutdown方法显式地关闭。 - -创建自定义scheduler的最简单方法是通过扩展SchedulerAdapter,实现下面的抽象成员: - - def execute(fun: => Unit): Unit - -典型情况下,一个具体的实现将会使用线程池来执行它的按名参数fun。 - -## 远程Actor - -这一段描述了远程actor的API。它的主要接口是scala.actors.remote包中的RemoteActor对象。这个对象提供各种方法来创建和连接到远程actor实例。在下面的代码段中我们假设所有的RemoteActor成员都已被导入,所使用的完整导入列表如下: - - import scala.actors._ - import scala.actors.Actor._ - import scala.actors.remote._ - import scala.actors.remote.RemoteActor._ - -### 启动远程Actor - -远程actor由一个Symbol唯一标记。在这个远程Actor所执行JVM上,这个符号对于JVM实例是唯一的。由名称'myActor标记的远程actor可按如下方法创建。 - - class MyActor extends Actor { - def act() { - alive(9000) - register('myActor, self) - // ... - } - } - -记住:一个名字一次只能标记到一个单独的(存活的)actor。例如,想要标记一个actorA为'myActor,然后标记另一个actorB为'myActor。这种情况下,必须等待A终止。这个要求适用于所有的端口,因此简单地将B标记到不同的端口来作为A是不能满足要求的。 - -### 连接到远程Actor - -连接到一个远程actor也同样简单。为了获得一个远程Actor的远程引用(运行于机器名为myMachine,端口为8000,名称为'anActor),可按下述方式使用select方法: - - val myRemoteActor = select(Node("myMachine", 8000), 'anActor) - -从select函数返回的actor具有类型AbstractActor,这个类型本质上提供了和通常actor相同的接口,因此支持通常的消息发送操作: - - myRemoteActor ! "Hello!" - receive { - case response => println("Response: " + response) - } - myRemoteActor !? "What is the meaning of life?" match { - case 42 => println("Success") - case oops => println("Failed: " + oops) - } - val future = myRemoteActor !! "What is the last digit of PI?" - -记住:select方法是惰性的,它不实际初始化任何网络连接。仅当必要时(例如,调用!时),它会单纯地创建一个新的,准备好初始化新网络连接的AbstractActor实例。 - diff --git a/zh-cn/overviews/core/architecture-of-scala-collections.md b/zh-cn/overviews/core/architecture-of-scala-collections.md deleted file mode 100644 index b7b254dec9..0000000000 --- a/zh-cn/overviews/core/architecture-of-scala-collections.md +++ /dev/null @@ -1,541 +0,0 @@ ---- -layout: overview -title: Scala容器类体系结构 -overview: architecture-of-scala-collections -language: zh-cn - -discourse: false ---- - -**Martin Odersky 和 Lex Spoon 著** - -本篇详细的介绍了Scala 容器类(collections)框架。通过与 [Scala 2.8 的 Collection API](http://docs.scala-lang.org/overviews/collections/introduction.html) 的对比,你会了解到更多框架的内部运作方式,同时你也将学习到如何通过几行代码复用这个容器类框架的功能来定义自己的容器类。 - -[Scala 2.8 容器API](http://docs.scala-lang.org/overviews/collections/introduction.html) 中包含了大量的 容器(collection)操作,这些操作在不同的许多容器类上表现为一致。假设,为每种 Collection 类型都用不同的方法代码实现,那么将导致代码的异常臃肿,很多代码将会仅仅是别处代码的拷贝。随着时间的推移,这些重复的代码也会带来不一致的问题,试想,相同的代码,在某个地方被修改了,而另外的地方却被遗漏了。而新的 容器类(collections)框架的设计原则目标就是尽量的避免重复,在尽可能少的地方定义操作(理想情况下,只在一处定义,当然也会有例外的情况存在)。设计中使用的方法是,在 Collection 模板中实现大部分的操作,这样就可以灵活的从独立的基类和实现中继承。后面的部分,我们会来详细阐述框架的各组成部分:模板(templates)、类(classes)以及trait(译注:类似于java里接口的概念),也会说明他们所支持的构建原则。 - -## Builders - -Builder类概要: - - package scala.collection.mutable - - class Builder[-Elem, +To] { - def +=(elem: Elem): this.type - def result(): To - def clear(): Unit - def mapResult[NewTo](f: To => NewTo): Builder[Elem, NewTo] = ... - } - -几乎所有的 Collection 操作都由遍历器(traversals)和构建器 (builders)来完成。Traversal 用可遍历类的foreach方法来实现,而构建新的 容器(collections)是由构建器类的实例来完成。上面的代码就是对这个类的精简描述。 - -我们用 b += x 来表示为构建器 b 加上元素 x。也可以一次加上多个元素,例如: b += (x, y) 及 b ++= x ,这类似于缓存(buffers)的工作方式(实际上,缓存就是构建器的增强版)。构建器的 result() 方法会返回一个collection。在获取了结果之后,构建器的状态就变成未定义,调用它的 clear() 方法可以把状态重置成空状态。构建器是通用元素类型,它适用于元素,类型,及它所返回的Collection。 - -通常,一个builder可以使用其他的builder来组合一个容器的元素,但是如果想要把其他builder返回的结果进行转换,例如,转成另一种类型,就需要使用Builder类的mapResult方法。假设,你有一个数组buffer,名叫 buf。一个ArrayBuffer的builder 的 result() 返回它自身。如果想用它去创建一个新的ArrayBuffer的builder,就可以使用 mapResult : - - scala> val buf = new ArrayBuffer[Int] - buf: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer() - - scala> val bldr = buf mapResult (_.toArray) - bldr: scala.collection.mutable.Builder[Int,Array[Int]] - = ArrayBuffer() - -结果值 bldr,是使用 buf 来收集元素的builder。当调用 bldr 的result时,其实是调用的 buf 的result,结果是返回的buf本身。接着这个数组buffer用 _.toArray 映射成了一个数组,结果 bldr 也就成了一个数组的 builder. - -## 分解(factoring out)通用操作 - -### TraversableLike类概述 - - package scala.collection - - class TraversableLike[+Elem, +Repr] { - def newBuilder: Builder[Elem, Repr] // deferred - def foreach[U](f: Elem => U) // deferred - ... - def filter(p: Elem => Boolean): Repr = { - val b = newBuilder - foreach { elem => if (p(elem)) b += elem } - b.result - } - } - -Collection库重构的主要设计目标是在拥有自然类型的同时又尽可能的共享代码实现。Scala的Collection 遵从“结果类型相同”的原则:只要可能,容器上的转换方法最后都会生成相同类型的Collection。例如,过滤操作对各种Collection类型都应该产生相同类型的实例。在List上应用过滤器应该获得List,在Map上应用过滤器,应该获得Map,如此等等。在下面的章节中,会告诉大家该原则的实现方法。 - -Scala的 Collection 库通过在 trait 实现中使用通用的构建器(builders)和遍历器(traversals)来避免代码重复、实现“结果类型相同”的原则。这些Trait的名字都有Like后缀。例如:IndexedSeqLike 是 IndexedSeq 的 trait 实现,再如,TraversableLike 是 Traversable 的 trait 实现。和普通的 Collection 只有一个类型参数不同,trait实现有两个类型参数。他们不仅参数化了容器的成员类型,也参数化了 Collection 所代表的类型,就像下面的 Seq[I] 或 List[T]。下面是TraversableLike开头的描述: - - trait TraversableLike[+Elem, +Repr] { ... } - -类型参数Elem代表Traversable的元素类型,参数Repr代表它自身。Repr上没有限制,甚至,Repr可以是非Traversable子类的实例。这就意味这,非容器子类的类,例如String和Array也可以使用所有容器实现trait中包含的操作。 - -以过滤器为例,这个操作只在TraversableLike定义了一次,就使得它适用于所有的容器类(collections)。通过查看前面 TraversableLike类的概述中相关的代码描述,我们知道,该trait声明了两个抽象方法,newBuilder 和 foreach,这些抽象方法在具体的collection类中实现。过滤器也是用这两个方法,通过相同的方式来实现的。首先,它用 newBuiler 方法构造一个新的builder,类型为 Repr 的类型。然后,使用 foreach 来遍历当前 collection 中的所有元素。一旦某个元素 x 满足谓词 p (即,p(x)为真),那么就把x加入到builder中。最后,用 builder 的 result 方法返回类型同 Repr 的 collection,里面的元素就是上面收集到的满足条件的所有元素。 - -容器(collections)上的映射操作就更复杂。例如:如果 f 是一个以一个String类型为参数并返回一个Int类型的函数,xs 是一个 List[String],那么在xs上使用该映射函数 f 应该会返回 List[Int]。同样,如果 ys 是一个 Array[String],那么通过 f 映射,应该返回 Array[Int]。 这里的难点在于,如何实现这样的效果而又不用分别针对 list 和 array 写重复的代码。TraversableLike 类的 newBuilder/foreach 也完成不了这个任务,因为他们需要生成一个完全相同类型的容器(collection)类型,而映射需要的是生成一个相同类型但内部元素类型却不同的容器。 - -很多情况下,甚至像map的构造函数的结果类型都可能是不那么简单的,因而需要依靠于其他参数类型,比如下面的例子: - - scala> import collection.immutable.BitSet - import collection.immutable.BitSet - - scala> val bits = BitSet(1, 2, 3) - bits: scala.collection.immutable.BitSet = BitSet(1, 2, 3) - - scala> bits map (_ * 2) - res13: scala.collection.immutable.BitSet = BitSet(2, 4, 6) - - scala> bits map (_.toFloat) - res14: scala.collection.immutable.Set[Float] - = Set(1.0, 2.0, 3.0) - -在一个BitSet上使用倍乘映射 _*2,会得到另一个BitSet。然而,如果在相同的BitSet上使用映射函数 (_.toFloat) 结果会得到一个 Set[Float]。这样也很合理,因为 BitSet 中只能放整型,而不能存放浮点型。 - -因此,要提醒大家注意,映射(map)的结果类型是由传进来的方法的类型决定的。如果映射函数中的参数会得到Int类型的值,那么映射的结果就是 BitSet。但如果是其他类型,那么映射的结果就是 Set 类型。后面会让大家了解 Scala 这种灵活的类型适应是如何实现的。 - -类似 BitSet 的问题不是唯一的,这里还有在map类型上应用map函数的交互式例子: - - scala> Map("a" -> 1, "b" -> 2) map { case (x, y) => (y, x) } - res3: scala.collection.immutable.Map[Int,java.lang.String] - = Map(1 -> a, 2 -> b) - - scala> Map("a" -> 1, "b" -> 2) map { case (x, y) => y } - res4: scala.collection.immutable.Iterable[Int] - = List(1, 2) - -第一个函数用于交换两个键值对。这个函数映射的结果是一个类似的Map,键和值颠倒了。事实上,地一个表达式产生了一个键值颠倒的map类型(在原map可颠倒的情况下)。然而,第二个函数,把键值对映射成一个整型,即成员变成了具体的值。在这种情况下,我们不可能把结果转换成Map类型,因此处理成,把结果转换成Map的一个可遍历的超类,这里是List。 - -你可能会问,哪为什么不强制让映射都返回相同类型的Collection呢?例如:BitSet上的映射只能接受整型到整型的函数,而Map上的映射只能接受键值对到键值对的函数。但这种约束从面向对象的观点来看是不能接受的,它会破坏里氏替换原则(Liskov substitution principle),即:Map是可遍历类,因此所有在可遍历类上的合法的操作都必然在Map中合法。 - -Scala通过重载来解决这个问题:Scala中的重载并非简单的复制Java的实现(Java的实现不够灵活),它使用隐式参数所提供的更加系统化的重载方式。 - -TraversableLike 中映射(map)的实现: - - def map[B, That](p: Elem => B) - (implicit bf: CanBuildFrom[B, That, This]): That = { - val b = bf(this) - for (x <- this) b += f(x) - b.result - } - -上面的代码展示了TraversableLike如何实现映射的trait。看起来非常类似于TraversableLike类的过滤器的实现。主要的区别在于,过滤器使用TraversableLike类的抽象方法 newBuilder,而映射使用的是Builder工场,它作为CanBuildFrom类型的一个额外的隐式参数传入。 - -CanBuildFrom trait: - - package scala.collection.generic - - trait CanBuildFrom[-From, -Elem, +To] { - // 创建一个新的构造器(builder) - def apply(from: From): Builder[Elem, To] - } - -上面的代码是 trait CanBuildFrom 的定义,它代表着构建者工场。它有三个参数:Elem是要创建的容器(collection)的元素的类型,To是要构建的容器(collection)的类型,From是该构建器工场适用的类型。通过定义适合的隐式定义的构建器工场,你就可以构建出符合你需要的类型转换行为。以 BitSet 类为例,它的伴生对象包含一个 CanBuildFrom[BitSet, Int, BitSet] 类型的构建器工场。这就意味着,当在一个 BitSet 上执行操作的时候,你可以创建另一个元素类型为整型的 BitSet。如果你需要的类型不同,那么,你还可以使用其他的隐式构建器工场,它们在Set的伴生对象中实现。下面就是一个更通用的构建器,A是通用类型参数: - - CanBuildFrom[Set[_], A, Set[A]] - -这就意味着,当操作一个任意Set(用现有的类型 Set[] 表述),我们可以再次创建一个 Set,并且无需关心它的元素类型A是什么。给你两个 CanBuildFrom 的隐式实例,你有可以利用 Scala 的隐式解析(implicit resolution)规则去挑选出其中最契合的一个。 - -所以说,隐式解析(implicit resolution)为类似映射的比较棘手的Collection操作提供了正确的静态类型。但是动态类型又怎么办呢?特别是,假设你有一个List,作为静态类型它有遍历方法,你在它上面使用一些映射(map)方法: - - scala> val xs: Iterable[Int] = List(1, 2, 3) - xs: Iterable[Int] = List(1, 2, 3) - - scala> val ys = xs map (x => x * x) - ys: Iterable[Int] = List(1, 4, 9) - -上述ys的静态类型是可遍历的(Iterable)类型。但是它的动态类型仍然必须是List类型的!此行为是间接被实现的。在CanBuildFrom的apply方法被作为参数传递源容器中。大多数的builder工厂仿制traversables(除建造工厂意外所有的叶子类型(leaf classes))将调用转发到集合的方法genericBuilder。反过来genericBuilder方法调用属于在定义它收集的建设者。所以Scala使用静态隐式解析,以解决map类型的限制问题,以及分派挑选对应于这些约束最佳的动态类型。 - -## 集成新容器 - -如果想要集成一个新的容器(Collection)类,以便受益于在正确类型上预定义的操作,需要做些什么呢?在下面几页中,将通过两个例子来进行演示。 - -### 集成序列(Sequence) - -RNA(核糖核酸)碱基(译者注:RNA链即很多不同RNA碱基的序列,RNA参考资料:http://zh.wikipedia.org/wiki/RNA): - - abstract class Base - case object A extends Base - case object T extends Base - case object G extends Base - case object U extends Base - - object Base { - val fromInt: Int => Base = Array(A, T, G, U) - val toInt: Base => Int = Map(A -> 0, T -> 1, G -> 2, U -> 3) - } - -假设需要为RNA链建立一个新的序列类型,这些RNA链是由碱基A(腺嘌呤)、T(胸腺嘧啶)、G(鸟嘌呤)、U(尿嘧啶)组成的序列。如上述列出的RNA碱基,很容易建立碱基的定义。 - -每个碱基都定义为一个具体对象(case object),该对象继承自一个共同的抽象类Base(碱基)。这个Base类具有一个伴生对象(companion object),该伴生对象定义了描述碱基和整数(0到3)之间映射的2个函数。可以从例子中看到,有两种不同的方式来使用容器(Collection)来实现这些函数。toInt函数通过一个从Base值到整数之间的映射(map)来实现。而它的逆函数fromInt则通过数组来实现。以上这些实现方法都基于一个事实,即“映射和数组都是函数”。因为他们都继承自Function1 trait。 - -下一步任务,便是为RNA链定义一个类。从概念上来看,一个RNA链就是一个简单的Seq[Base]。然而,RNA链可以很长,所以值的去花点时间来简化RNA链的表现形式。因为只有4种碱基,所以每个碱基可以通过2个比特位来区别。因此,在一个integer中,可以保存16个由2位比特标示的碱基。即构造一个Seq[Base]的特殊子类,并使用这种压缩的表示(packed representation)方式。 - -#### RNA链类的第一个版本 - - import collection.IndexedSeqLike - import collection.mutable.{Builder, ArrayBuffer} - import collection.generic.CanBuildFrom - - final class RNA1 private (val groups: Array[Int], - val length: Int) extends IndexedSeq[Base] { - - import RNA1._ - - def apply(idx: Int): Base = { - if (idx < 0 || length <= idx) - throw new IndexOutOfBoundsException - Base.fromInt(groups(idx / N) >> (idx % N * S) & M) - } - } - - object RNA1 { - - // 表示一组所需要的比特数 - private val S = 2 - - // 一个Int能够放入的组数 - private val N = 32 / S - - // 分离组的位掩码(bitmask) - private val M = (1 << S) - 1 - - def fromSeq(buf: Seq[Base]): RNA1 = { - val groups = new Array[Int]((buf.length + N - 1) / N) - for (i <- 0 until buf.length) - groups(i / N) |= Base.toInt(buf(i)) << (i % N * S) - new RNA1(groups, buf.length) - } - - def apply(bases: Base*) = fromSeq(bases) - } - -上面的RNA链类呈现出这个类的第一个版本,它将在以后被细化。类RNA1有一个构造函数,这个构造函数将int数组作为第一个参数。而这个数组包含打包压缩后的RNA数据,每个数组元素都有16个碱基,而最后一个元素则只有一部分有数据。第二个参数是长度,指定了数组中(和序列中)碱基的总数。RNA1类扩展了IndexedSeq[Base]。而IndexedSeq来自scala.collection.immutable,IndexedSeq定义了两个抽象方法:length和apply。这方法些需要在具体的子类中实现。类RNA1通过定义一个相同名字的参数字段来自动实现length。同时,通过类RNA1中给出的代码实现了索引方法apply。实质上,apply方法首先从数组中提取出一个整数值,然后再对这个整数中使用右移位(>>)和掩码(&)提取出正确的两位比特。私有常数S、N来自RNA1的伴生对象,S指定了每个包的尺寸(也就是2),N指定每个整数的两位比特包的数量,而M则是一个比特掩码,分离出一个字(word)的低S位。 - -注意,RNA1类的构造函数是一个私有函数。这意味着用户端无法通过调用new函数来创建RNA1序列的实例。这是有意义的,因为这能对用户隐藏RNA1序列包装数组的实现。如果用户端无法看到RNA序列的具体实现,以后任何时候,就可以做到改变RNA序列具体实现的同时,不影响到用户端代码。换句话说,这种设计实现了RNA序列的接口和实现之间解藕。然而,如果无法通过new来创建一个RNA序列,那就必须存在其他方法来创建它,否则整个类就变得毫无用处。事实上,有两种建立RNA序列的替代途径,两者都由RNA1的伴生对象(companion object)提供。第一个途径是fromSeq方法,这个方法将一个给定的碱基序列(也就是一个Seq[Base]类型的值)转换成RNA1类的实例。fromSeq方法将所有其序列参数内的碱基打包进一个数组。然后,将这个数组以及原序列的长度作为参数,调用RNA1的私有构造函数。这利用了一个事实:一个类的私有构造函数对于其伴生对象(companion object)是可见的。 - -创建RNA1实例的第二种途径由RNA1对象中的apply方法提供。它使用一个可变数量的Base类参数,并简单地将其作为序列指向fromSeq方法。这里是两个创建RNA实例的实际方案。 - - scala> val xs = List(A, G, T, A) - xs: List[Product with Base] = List(A, G, T, A) - - scala> RNA1.fromSeq(xs) - res1: RNA1 = RNA1(A, G, T, A) - - scala> val rna1 = RNA1(A, U, G, G, T) - rna1: RNA1 = RNA1(A, U, G, G, T) - -## 控制RNA类型中方法的返回值 - -这里有一些和RNA1抽象之间更多的交互操作 - - scala> rna1.length - res2: Int = 5 - - scala> rna1.last - res3: Base = T - - scala> rna1.take(3) - res4: IndexedSeq[Base] = Vector(A, U, G) - -前两个返回值正如预期,但最后一个——从rna1中获得前3个元素——的返回值则未必如预期。实际上,我们知道一个IndexedSeq[Base]作为返回值的静态类型而一个Vector作为返回值的动态类型,但我们更想看到一个RNA1的值。但这是无法做到的,因为之前在RNA1类中所做的一切仅仅是让RNA1扩展IndexedSeq。换句话说,IndexedSeq类具有一个take方法,其返回一个IndexedSeq。并且,这个方法是根据 IndexedSeq 的默认是用Vector来实现的。所以,这就是上一个交互中最后一行上所能看到的。 - -#### RNA链类的第二个版本 - - final class RNA2 private ( - val groups: Array[Int], - val length: Int - ) extends IndexedSeq[Base] with IndexedSeqLike[Base, RNA2] { - - import RNA2._ - - override def newBuilder: Builder[Base, RNA2] = - new ArrayBuffer[Base] mapResult fromSeq - - def apply(idx: Int): Base = // as before - } - -现在,明白了本质之后,下一个问题便是如何去改变它们。一种途径便是覆写(override)RNA1类中的take方法,可能如下所示: - - def take(count: Int): RNA1 = RNA1.fromSeq(super.take(count)) - -这对take函数有效,但drop、filter或者init又如何呢?事实上,序列(Sequence)中有超过50个方法同样返回序列。为了保持一致,所有这些方法都必须被覆写。这看起来越来越不像一个有吸引力的选择。幸运的是,有一种更简单的途径来达到同样的效果。RNA类不仅需要继承自IndexedSeq类,同时继承自它的实现trait(特性)IndexedSeqLike。如上面的RNA2所示。新的实现在两个方面与之前不同。第一个,RNA2类现在同样扩展自IndexedSeqLike[Base, RNA2]。这个IndexedSeqLike trait(特性)以可扩展的方式实现了所有IndexedSeq的具体方法。比如,如take、drop、filer或init的返回值类型即是传给IndexedSeqLike类的第二个类型参数,也就是说,在RNA2中的是RNA2本身。 - -为了能够做,IndexedSeqLike将自身建立在newBuilder抽象上,这个抽象能够创建正确类型的builder。IndexedSeqLike trait(特性)的子类必须覆写newBuilder以返回一个它们自身类型的容器。在RNA2类中,newBuilder方法返回一个Builder[Base, RNA2]类型的builder。 - -为了构造这个builder,首先创建一个ArrayBuffer,其自身就是一个Builder[Base, ArrayBuffer]。然后通过调用其mapResult方法来将这个ArrayBuffer转换为一个RNA2 builder。mapResult方法需要一个从ArrayBuffer到RNA2的转换函数来作为其参数。转换函数仅仅提供RNA2.fromSeq,其将一个任意的碱基序列转换为RNA2值(之前提到过,数组缓冲是一种序列,所以RNA2.fromSeq可对其使用)。 - -如果忘记声明newBuilder,将会得到一个如下的错误信息: - - RNA2.scala:5: error: overriding method newBuilder in trait - TraversableLike of type => scala.collection.mutable.Builder[Base,RNA2]; - method newBuilder in trait GenericTraversableTemplate of type - => scala.collection.mutable.Builder[Base,IndexedSeq[Base]] has - incompatible type - class RNA2 private (val groups: Array[Int], val length: Int) ^ - - one error found(发现一个错误) - -错误信息非常地长,并且很复杂,体现了容器(Collection)库错综复杂的组合。所以,最好忽略有关这些方法来源的信息,因为在这种情况下,它更多得是分散人的精力。而剩下的,则说明需要声明一个具有返回类型Builder[Base, RNA2]的newBuilder方法,但无法找到一个具有返回类型Builder[Base,IndexedSeq[Base]]的newBuilder方法。后者并不覆写前者。第一个方法——返回值类型为Builder[Base, RNA2]——是一个抽象方法,其在RNA2类中通过传递RNA2的类型参数给IndexedSeqLike,来以这种类型实例化。第二个方法的返回值类型为Builder[Base,IndexedSeq[Base]]——是由继承后的IndexedSeq类提供的。换句话说,如果没有声明一个以第一个返回值类型为返回值的newBuilder,RNA2类就是非法的。 - -改善了RNA2类中的实现之后,take、drop或filter方法现在便会按照预期执行: - - scala> val rna2 = RNA2(A, U, G, G, T) - rna2: RNA2 = RNA2(A, U, G, G, T) - - scala> rna2 take 3 - res5: RNA2 = RNA2(A, U, G) - - scala> rna2 filter (U !=) - res6: RNA2 = RNA2(A, G, G, T) - -### 使用map(映射)和friends(友元) - -然而,在容器中存在没有被处理的其他类别的方法。这些方法就不总会返回容器类型。它们可能返回同一类型的容器,但包含不同类型的元素。典型的例子就是map方法。如果s是一个Int的序列(Seq[Int]),f是将Int转换为String的方法,那么,s.map(f)将返回一个String的序列(Seq[String])。这样,元素类型在接收者和结果之间发生了改变,但容器的类型还是保持一致。 - -有一些其他的方法的行为与map类似,比如说flatMap、collect等,但另一些则不同。例如:++这个追加方法,它也可能因参数返回一个不同类型的结果——向Int类型的列表拼接一个String类型的列表将会得到一个Any类型的列表。至于这些方法如何适应RNA链,理想情况下应认为,在RNA链上进行碱基到碱基的映射将产生另外一个RNA链。(译者注:碱基为RNA链的“元素”) - - scala> val rna = RNA(A, U, G, G, T) - rna: RNA = RNA(A, U, G, G, T) - - scala> rna map { case A => T case b => b } - res7: RNA = RNA(T, U, G, G, T) - -同样,用 ++ 方法来拼接两个RNA链应该再次产生另外一个RNA链。 - - scala> rna ++ rna - res8: RNA = RNA(A, U, G, G, T, A, U, G, G, T) - -另一方面,在RNA链上进行碱基(类型)到其他类型的映射无法产生另外一个RNA链,因为新的元素有错误的类型。它只能产生一个序列而非RNA链。同样,向RNA链追加非Base类型的元素可以产生一个普通序列,但无法产生另一个RNA链。 - - scala> rna map Base.toInt - res2: IndexedSeq[Int] = Vector(0, 3, 2, 2, 1) - - scala> rna ++ List("missing", "data") - res3: IndexedSeq[java.lang.Object] = - Vector(A, U, G, G, T, missing, data) - -这就是在理想情况下应认为结果。但是,RNA2类并不提供这样的处理。事实上,如果你用RNA2类的实例来运行前两个例子,结果则是: - - scala> val rna2 = RNA2(A, U, G, G, T) - rna2: RNA2 = RNA2(A, U, G, G, T) - - scala> rna2 map { case A => T case b => b } - res0: IndexedSeq[Base] = Vector(T, U, G, G, T) - - scala> rna2 ++ rna2 - res1: IndexedSeq[Base] = Vector(A, U, G, G, T, A, U, G, G, T) - -所以,即使生成的容器元素类型是Base,map和++的结果也永远不会是RNA链。如需改善,则需要仔细查看map方法的签名(或++,它也有类似的方法签名)。map的方法最初在scala.collection.TraversableLike类中定义,具有如下签名: - - def map[B, That](f: A => B) - (隐含CBF:CanBuildFrom[修订版,B]): - -这里的A是一个容器元素的类型,而Repr是容器本身的类型,即传递给实现类(例如 TraversableLike和IndexedSeqLike)的第二个参数。map方法有两个以上的参数,B和That。参数B表示映射函数的结果类型,同时也是新容器中的元素类型。That作为map的结果类型。所以,That表示所创建的新容器的类型。 - -对于That类型如何确定,事实上,它是根据隐式参数cbf(CanBuildFrom[Repr,B,That]类型)被链接到其他类型。这些隐式CanBuildFrom由独立的容器类定义。大体上,CanBuildFrom[From,Elem,To]类型的值可以描述为:“有这么一种方法,由给定的From类型的容器,使用Elem类型,建立To的容器。” - -#### RNA链类的最终版本 - - final class RNA private (val groups: Array[Int], val length: Int) - extends IndexedSeq[Base] with IndexedSeqLike[Base, RNA] { - - import RNA._ - - // 在IndexedSeq中必须重新实现newBuilder - override protected[this] def newBuilder: Builder[Base, RNA] = - RNA.newBuilder - - // 在IndexedSeq中必须实现apply - def apply(idx: Int): Base = { - if (idx < 0 || length <= idx) - throw new IndexOutOfBoundsException - Base.fromInt(groups(idx / N) >> (idx % N * S) & M) - } - - // (可选)重新实现foreach, - // 来提高效率 - override def foreach[U](f: Base => U): Unit = { - var i = 0 - var b = 0 - while (i < length) { - b = if (i % N == 0) groups(i / N) else b >>> S - f(Base.fromInt(b & M)) - i += 1 - } - } - } - -#### RNA伴生对象的最终版本 - - object RNA { - - private val S = 2 // group中的比特(bit)数 - private val M = (1 << S) - 1 // 用于隔离group的比特掩码 - private val N = 32 / S // 一个Int中的group数 - - def fromSeq(buf: Seq[Base]): RNA = { - val groups = new Array[Int]((buf.length + N - 1) / N) - for (i <- 0 until buf.length) - groups(i / N) |= Base.toInt(buf(i)) << (i % N * S) - new RNA(groups, buf.length) - } - - def apply(bases: Base*) = fromSeq(bases) - - def newBuilder: Builder[Base, RNA] = - new ArrayBuffer mapResult fromSeq - - implicit def canBuildFrom: CanBuildFrom[RNA, Base, RNA] = - new CanBuildFrom[RNA, Base, RNA] { - def apply(): Builder[Base, RNA] = newBuilder - def apply(from: RNA): Builder[Base, RNA] = newBuilder - } - } - -现在在RNA2序列链上的 map 和 ++ 的行为变得更加清晰了。由于没有能够创建RNA2序列的CanBuildFrom实例,因此在从trait InexedSeq继承的伴生对象上得到的CanBuildFrom成为了第二选择。这隐式地创建了IndexedSeqs,也是在应用map到RNA2的时候发生的情况。 - -为了解决这个缺点,你需要在RNA类的同伴对象里定义CanBuildFrom的隐式实例。该实例的类型应该是CanBuildFrom [RNA, Base, RNA] 。即,这个实例规定,给定一个RNA链和新元素类型Base,可以建立另一个RNA链容器。上述有关RNA链的两个代码以及其伴生对象展示了细节。相较于类RNA2有两个重要的区别。首先, newBuilder的实现,从RNA类移到了它的伴生对象中。RNA类中新的newBuilder方法只是转发这个定义。其次,在RNA对象现在有个隐式的CanBuildFrom值。要创建这样的对象你需要在CanBuildFrom trait中定义两个apply方法。同时,为容器RNA创建一个新的builder,但参数列表不同。在apply()方法只是简单地以正确的类型创建builder。相比之下,apply(from)方法将原来的容器作为参数。在适应动态类型的builder的返回值与接收者的动态类型一致非常有用。在RNA的情况下,这不会起作用,因为RNA是final类,所以静态类型的RNA任何接收者同时具有RNA作为其动态类型。这就是为什么apply(from)也只是简单地调用newBuilder ,忽略其参数。 - -这样,RNA类(final)以原本的类型实现了所有容器方法。它的实现需要一些协议支持。从本质上讲,你需要知道newBuilder 工厂放在哪里以及canBuildFrom的隐式实现。在有利方面,能够以相对较少的代码,得到大量自动定义的方法。另外,如果不打算在容器中扩展take,drop,map或++这样操作,可以不写额外的代码,并在类RNA1所示的实现上结束工作。 - -到目前为止,讨论集中在以定义新序列所需的最少方法来获得特定类型。但在实践中,可能需要在序列上添加新的功能,或重写现有的方法,以获得更好的效果。其中一个例子就是重写RNA类的foreach方法。foreach是RNA本身的一个重要方法,因为它实现了遍历容器。此外,容器的许多其他方法的实现依赖foreach。因此,投入一些精力来做优化方法的实现有意义。IndexedSeq的foreach方法的标准实现仅仅使用aplly来选取容器的中的第i个元素(i从0到容器长度-1)。因此,对RNA链的每一个元素,标准实现选择一个数组元素,并从中解开一个碱基(base)。而RNA类上重写的foreach要聪明得多。对于每一个选定的数组元素,它立刻对其中所包含的所有碱基应用给定的方法。因此,数组选择和位拆包的工作大大减少。 - -### 整合 sets与 map - -在第二个实例中,将介绍如何将一个新的map类型整合到容器框架中的。其方式是通过使用关键字“Patricia trie”,实现以String作为类型的可变映射(mutable map)。术语“Patricia“实际上就是"Practical Algorithm to Retrieve Information Coded in Alphanumeric."(检索字母数字编码信息的实用算法) 的缩写。思想是以树的形式存储一个set或者map,在这种树中,后续字符作为子树可以用唯一确定的关键字查找。例如,一个 Patricia trie存储了三个字符串 "abc", "abd", "al", "all", "xy" 。如下: - -patricia 树的例子: - -![patricia.png](/resources/images/patricia.png) - -为了能够在trie中查找与字符串”abc“匹配的节点,只要沿着标记为”a“的子树,查找到标记为”b“的子树,最后到达标记为”c“的子树。如果 Patricia trie作为map使用,键所对应的值保存在一个可通过键定位的节点上。如果作为set,只需保存一个标记,说明set中存在这个节点。 - -使用Patricia tries的prefix map实现方式: - - import collection._ - - class PrefixMap[T] - extends mutable.Map[String, T] - with mutable.MapLike[String, T, PrefixMap[T]] { - - var suffixes: immutable.Map[Char, PrefixMap[T]] = Map.empty - var value: Option[T] = None - - def get(s: String): Option[T] = - if (s.isEmpty) value - else suffixes get (s(0)) flatMap (_.get(s substring 1)) - - def withPrefix(s: String): PrefixMap[T] = - if (s.isEmpty) this - else { - val leading = s(0) - suffixes get leading match { - case None => - suffixes = suffixes + (leading -> empty) - case _ => - } - suffixes(leading) withPrefix (s substring 1) - } - - override def update(s: String, elem: T) = - withPrefix(s).value = Some(elem) - - override def remove(s: String): Option[T] = - if (s.isEmpty) { val prev = value; value = None; prev } - else suffixes get (s(0)) flatMap (_.remove(s substring 1)) - - def iterator: Iterator[(String, T)] = - (for (v <- value.iterator) yield ("", v)) ++ - (for ((chr, m) <- suffixes.iterator; - (s, v) <- m.iterator) yield (chr +: s, v)) - - def += (kv: (String, T)): this.type = { update(kv._1, kv._2); this } - - def -= (s: String): this.type = { remove(s); this } - - override def empty = new PrefixMap[T] - } - -Patricia tries支持非常高效的查找和更新。另一个良好的特点是,支持通过前缀查找子容器。例如,在上述的patricia tree中,你可以从树根处按照“a”链接进行查找,获得所有以”a“为开头的键所组成的子容器。 - -依据这些思想,来看一下作为Patricia trie的映射实现方式。这种map称为PrefixMap。PrefixMap提供了withPrefix方法,这个方法根据给定的前缀查找子映射(submap),其包含了所有匹配该前缀的键。首先,使用键来定义一个prefix map,执行如下。 - - scala> val m = PrefixMap("abc" -> 0, "abd" -> 1, "al" -> 2, - "all" -> 3, "xy" -> 4) - m: PrefixMap[Int] = Map((abc,0), (abd,1), (al,2), (all,3), (xy,4)) - -然后,在m中调用withPrefix方法将产生另一个prefix map: - - scala> m withPrefix "a" - res14: PrefixMap[Int] = Map((bc,0), (bd,1), (l,2), (ll,3)) - -上面展示的代码表明了PrefixMap的定义方式。这个类以关联值T参数化,并扩展mutable.Map[String, T] 与 mutable.MapLike[String, T, PrefixMap[T]]。在RNA链的例子中对序列的处理也使用了这种模式,然后,为转换(如filter)继承实现类(如MapLike),用以获得正确的结果类型。 - -一个prefix map节点中含有两个可变字段:suffixes与value。value字段包含关联此节点的任意值,其初始化为None。suffixes字段包含从字符到PrefixMap值的映射(map),其初始化为空的map。 - -为什么要选择一种不可变map作为suffixes的实现方式?既然PrefixMap在整体上也是可变的,使用可变映射(map)是否更符合标准吗?这个问题的答案是,因为仅包含少量元素的不可变map,在空间和执行时间都非常高效。例如,包含有少于5个元素的map代表一个单独的对象。相比之下,就标准的可变map中的HashMap来说,即使为空时,也至少占用80bytes的空间。因此,如果普遍使用小容器,不可变的容器就优于可变的容器。在Patricia tries的例子中,预想除了树顶端节点之外,大部分节点仅包含少量的successor。所以在不可变映射(map)中存储这些successor效率很可能会更高。 - -现在看看映射(map)中第一个方法的实现:get。算法如下:为了获取prefix map里面和空字符串相关的值,简单地选取存储在树根节点上的任意值。另外,如果键字符串非空,尝试选取字符串的首字符匹配的子映射。如果产生一个map,继续去寻找map里面首个字符的之后的剩余键字符串。如果选取失败,键没有存储在map里面,则返回None。使用flatmap可以优雅地表示任意值上的联合选择。当对任意值ov,以及闭包f(其转而会返回任的值)进行应用时,如果ov和f都返回已定义的值,那么ov flatmap f将执行成功,否则ov flatmap f将返回None。 - -可变映射(map)之后的两个方法的实现是+=和-=。在prefixmap的实现中,它们按照其它两种方法定义:update和remove。 - -remove方法和get方法非常类似,除了在返回任何关联值之前,保存这个值的字段被设置为None。update方法首先会调用 withPrefix 方法找到需要被更新的树节点,然后将给定的值赋值给该节点的value字段。withPrefix 方法遍历整个树,如果在这个树中没有发现以这些前缀字符为节点的路径,它会根据需要创建子映射(sub-map)。 - -可变map最后一个需要实现的抽象方法是iterator。这个方法需要创建一个能够遍历map中所有键值对的迭代器iterator。对于任何给出的 prefix map,iterator 由如下几部分组成:首先,如果这个map中,在树根节点的value字段包含一个已定义的值Some(x),那么("", x)应为从iterator返回的第一个元素。此外,iterator需要串联存储在suffixes字段上的所有submap的iterator,并且还需要在这些返回的iterator每个键字符串的前面加上一个字符。进一步说,如果m是通过一个字符chr链接到根节点的submap ,并且(s, v)是一个从m.iterator返回的元素,那么这个根节点的iterator 将会转而返回(chr +: s, v)。在PrefixMap的iterator方法的实现中,这个逻辑可以非常简明地用两个for表达式实现。第一个for表达式在value.iterate上迭代。这表明Option值定义一个迭代器方法,如果Option值为None,则不返回任何元素。如果Option值为Some(x),则返回一个确切的元素x。 - -prefix map的伴生对象: - - import scala.collection.mutable.{Builder, MapBuilder} - import scala.collection.generic.CanBuildFrom - - object PrefixMap extends { - def empty[T] = new PrefixMap[T] - - def apply[T](kvs: (String, T)*): PrefixMap[T] = { - val m: PrefixMap[T] = empty - for (kv <- kvs) m += kv - m - } - - def newBuilder[T]: Builder[(String, T), PrefixMap[T]] = - new MapBuilder[String, T, PrefixMap[T]](empty) - - implicit def canBuildFrom[T] - : CanBuildFrom[PrefixMap[_], (String, T), PrefixMap[T]] = - new CanBuildFrom[PrefixMap[_], (String, T), PrefixMap[T]] { - def apply(from: PrefixMap[_]) = newBuilder[T] - def apply() = newBuilder[T] - } - } - -请注意,在PrefixMap中没有newBuilder方法的定义。这是没有必要的,因为maps和sets有默认的构造器,即MapBuilder类的实例。对可变映射来说,其默认的构造器初始时是一个空映射,然后使用映射的+= 方法连续增加元素。可变集合也类似。非可变映射和非可变集合的默认构造器则不同,它们使用无损的元素添加方法+,而非+=方法。 - -然而,为了构建合适的集合或映射,你都需要从一个空的集合或映射开始。empty方法提供了这样的功能,它是PrefixMap中最后定义的方法,该方法简单的返回一个新的PrefixMap。 - -现在我们来看看PrefixMap的伴生对象。事实上,并不是非要定义这种伴生对象,类PrefixMap自己就可以很好的完成它的功能。PrefixMap 对象的主要作用,是定义一些方便的工厂方法。它也定义了一个 CanBuildFrom,该方法可以让输入工作完成的更好。 - -其中有两个方法值得一提,它们是 empty 和 apply。同样的方法,在Scala的容器框架中的其他容器中都存在,因此在PrefixMap中定义它们也很合理。用这两种方法,你可以像写其他容器一样的编写PrefixMap: - - scala> PrefixMap("hello" -> 5, "hi" -> 2) - res0: PrefixMap[Int] = Map((hello,5), (hi,2)) - - scala> PrefixMap.empty[String] - res2: PrefixMap[String] = Map() - -另一个PrefixMap对象的成员是内置CanBuildFrom实例。它和上一节定义的CanBuildFrom目的相同:使得类似map等方法能返回最合适的类型。以PrefixMap的键值对映射函数为例,只要该函数生成串型和另一种类型组成的键值对,那么结果又会是一个PrefixMap。这里有一个例子: - - scala> res0 map { case (k, v) => (k + "!", "x" * v) } - res8: PrefixMap[String] = Map((hello!,xxxxx), (hi!,xx)) - -给出的函数参数是一个PrefixMap res0的键值对绑定,最终生成串型值对。map的结果是一个PrefixMap,只是String类型替代了int类型。如果在PrefixMap中没有内置的canBuildFrom ,那么结果将是一个普通的可变映射,而不是一个PrefixMap。 - -### 小结 - -总而言之,如果你想要将一个新的collection类完全的融入到框架中,需要注意以下几点: - -1. 决定容器应该是可变的,还是非可变的。 -2. 为容器选择正确的基类trait -3. 确保容器继承自适合的trait实现,这样它就能具有大多数的容器操作。 -4. 如果你想要map及类似的操作去返回你的容器类型的实例,那么就需要在类的伴生对象中提供一个隐式CanBuildFrom。 - -你现在已经了解Scala容器如何构建和如何构建新的容器类型。由于Scala丰富的抽象支持,新容器类型无需写代码就可以拥有大量的方法实现。 - -### 致谢 - -这些页面的素材改编自,由Odersky,Spoon和Venners编写的[Scala编程](http://www.artima.com/shop/programming_in_scala)第2版 。感谢Artima 对于出版的大力支持。 diff --git a/zh-cn/overviews/core/futures.md b/zh-cn/overviews/core/futures.md deleted file mode 100644 index fe46c6be9e..0000000000 --- a/zh-cn/overviews/core/futures.md +++ /dev/null @@ -1,504 +0,0 @@ ---- -layout: overview -label-color: success -label-text: New in 2.10 -overview: futures -language: zh-cn -title: Future和Promise - -discourse: false ---- - -**Philipp Haller, Aleksandar Prokopec, Heather Miller, Viktor Klang, Roland Kuhn, and Vojin Jovanovic 著** - -## 简介 - -Future提供了一套高效便捷的非阻塞并行操作管理方案。其基本思想很简单,所谓Future,指的是一类占位符对象,用于指代某些尚未完成的计算的结果。一般来说,由Future指代的计算都是并行执行的,计算完毕后可另行获取相关计算结果。以这种方式组织并行任务,便可以写出高效、异步、非阻塞的并行代码。 - -默认情况下,future和promise并不采用一般的阻塞操作,而是依赖回调进行非阻塞操作。为了在语法和概念层面更加简明扼要地使用这些回调,Scala还提供了flatMap、foreach和filter等算子,使得我们能够以非阻塞的方式对future进行组合。当然,future仍然支持阻塞操作——必要时,可以阻塞等待future(不过并不鼓励这样做)。 - -## Future - -所谓Future,是一种用于指代某个尚未就绪的值的对象。而这个值,往往是某个计算过程的结果: - -- 若该计算过程尚未完成,我们就说该Future未就位; -- 若该计算过程正常结束,或中途抛出异常,我们就说该Future已就位。 - -Future的就位分为两种情况: - -- 当Future带着某个值就位时,我们就说该Future携带计算结果成功就位。 -- 当Future因对应计算过程抛出异常而就绪,我们就说这个Future因该异常而失败。 - -Future的一个重要属性在于它只能被赋值一次。一旦给定了某个值或某个异常,future对象就变成了不可变对象——无法再被改写。 - -创建future对象最简单的方法是调用future方法,该future方法启用异步(asynchronous)计算并返回保存有计算结果的futrue,一旦该future对象计算完成,其结果就变的可用。 - -注意_Future[T]_ 是表示future对象的类型,而future是方法,该方法创建和调度一个异步计算,并返回随着计算结果而完成的future对象。 - -这最好通过一个例子予以说明。 - -假设我们使用某些流行的社交网络的假定API获取某个用户的朋友列表,我们将打开一个新对话(session),然后发送一个请求来获取某个特定用户的好友列表。 - - import scala.concurrent._ - import ExecutionContext.Implicits.global - - val session = socialNetwork.createSessionFor("user", credentials) - val f: Future[List[Friend]] = Future { - session.getFriends() - } - -以上,首先导入scala.concurrent 包使得Future类型和future构造函数可见。我们将马上解释第二个导入。 - -然后我们初始化一个session变量来用作向服务器发送请求,用一个假想的 createSessionFor 方法来返回一个List[Friend]。为了获得朋友列表,我们必须通过网络发送一个请求,这个请求可能耗时很长。这能从调用getFriends方法得到解释。为了更好的利用CPU,响应到达前不应该阻塞(block)程序的其他部分执行,于是在计算中使用异步。future方法就是这样做的,它并行地执行指定的计算块,在这个例子中是向服务器发送请求和等待响应。 - -一旦服务器响应,future f 中的好友列表将变得可用。 - -未成功的尝试可能会导致一个异常(exception)。在下面的例子中,session的值未被正确的初始化,于是在future的计算中将抛出NullPointerException,future f 不会圆满完成,而是以此异常失败。 - - val session = null - val f: Future[List[Friend]] = Future { - session.getFriends - } - -`import ExecutionContext.Implicits.global` 上面的线条导入默认的全局执行上下文(global execution context),执行上下文执行执行提交给他们的任务,也可把执行上下文看作线程池,这对于future方法来说是必不可少的,因为这可以处理异步计算如何及何时被执行。我们可以定义自己的执行上下文,并在future上使用它,但是现在只需要知道你能够通过上面的语句导入默认执行上下文就足够了。 - -我们的例子是基于一个假定的社交网络API,此API的计算包含发送网络请求和等待响应。提供一个涉及到你能试着立即使用的异步计算的例子是公平的。假设你有一个文本文件,你想找出一个特定的关键字第一次出现的位置。当磁盘正在检索此文件内容时,这种计算可能会陷入阻塞,因此并行的执行该操作和程序的其他部分是合理的(make sense)。 - - val firstOccurrence: Future[Int] = Future { - val source = scala.io.Source.fromFile("myText.txt") - source.toSeq.indexOfSlice("myKeyword") - } - -### Callbacks(回调函数) - -现在我们知道如何开始一个异步计算来创建一个新的future值,但是我们没有展示一旦此结果变得可用后如何来使用,以便我们能够用它来做一些有用的事。我们经常对计算结果感兴趣而不仅仅是它的副作用。 - -在许多future的实现中,一旦future的client对future的结果感兴趣,它不得不阻塞它自己的计算直到future完成——然后才能使用future的值继续它自己的计算。虽然这在Scala的Future API(在后面会展示)中是允许的,但是从性能的角度来看更好的办法是一种完全非阻塞的方法,即在future中注册一个回调,future完成后这个回调称为异步回调。如果当注册回调时future已经完成,则回调可能是异步执行的,或在相同的线程中循序执行。 - -注册回调最通常的形式是使用OnComplete方法,即创建一个`Try[T] => U`类型的回调函数。如果future成功完成,回调则会应用到Success[T]类型的值中,否则应用到` Failure[T] `类型的值中。 - - `Try[T]` 和`Option[T]`或 `Either[T, S]`相似,因为它是一个可能持有某种类型值的单子。然而,它是特意设计来保持一个值或某个可抛出(throwable)对象。`Option[T]` 既可以是一个值(如:`Some[T]`)也可以是完全无值(如:`None`),如果`Try[T]`获得一个值则它为`Success[T]` ,否则为`Failure[T]`的异常。 `Failure[T]` 获得更多的关于为什么这儿没值的信息,而不仅仅是None。同时也可以把`Try[T]`看作一种特殊版本的`Either[Throwable, T]`,专门用于左值为可抛出类型(Throwable)的情形。 - -回到我们的社交网络的例子,假设我们想要获取我们最近的帖子并显示在屏幕上,我们通过调用getRecentPosts方法获得一个返回值List[String]——一个近期帖子的列表文本: - - val f: Future[List[String]] = Future { - session.getRecentPosts - } - - f onComplete { - case Success(posts) => for (post <- posts) println(post) - case Success(posts) => for (post <- posts) println(post) - } - -onComplete方法一般在某种意义上它允许客户处理future计算出的成功或失败的结果。对于仅仅处理成功的结果,onSuccess 回调使用如下(该回调以一个偏函数(partial function)为参数): - - val f: Future[List[String]] = Future { - session.getRecentPosts - } - - f onSuccess { - case posts => for (post <- posts) println(post) - } - -对于处理失败结果,onFailure回调使用如下: - - val f: Future[List[String]] = Future { - session.getRecentPosts - } - - f onFailure { - case t => println("An error has occured: " + t.getMessage) - } - - f onSuccess { - case posts => for (post <- posts) println(post) - } - -如果future失败,即future抛出异常,则执行onFailure回调。 - -因为偏函数具有 isDefinedAt方法, onFailure方法只有在特定的Throwable类型对象中被定义才会触发。下面例子中的onFailure回调永远不会被触发: - - val f = Future { - 2 / 0 - } - - f onFailure { - case npe: NullPointerException => - println("I'd be amazed if this printed out.") - } - -回到前面查找某个关键字第一次出现的例子,我们想要在屏幕上打印出此关键字的位置: - - val firstOccurrence: Future[Int] = Future { - val source = scala.io.Source.fromFile("myText.txt") - source.toSeq.indexOfSlice("myKeyword") - } - - firstOccurrence onSuccess { - case idx => println("The keyword first appears at position: " + idx) - } - - firstOccurrence onFailure { - case t => println("Could not process file: " + t.getMessage) - } - - onComplete,、onSuccess 和 onFailure 方法都具有Unit的结果类型,这意味着不能链接使用这些方法的回调。注意这种设计是为了避免暗示而刻意为之的,因为链接回调也许暗示着按照一定的顺序执行注册回调(回调注册在同一个future中是无序的)。 - -也就是说,我们现在应讨论论何时调用callback。因为callback需要future的值是可用的,所有回调只能在future完成之后被调用。然而,不能保证callback在完成future的线程或创建callback的线程中被调用。反而, 回调(callback)会在future对象完成之后的一些线程和一段时间内执行。所以我们说回调(callback)最终会被执行。 - -此外,回调(callback)执行的顺序不是预先定义的,甚至在相同的应用程序中callback的执行顺序也不尽相同。事实上,callback也许不是一个接一个连续的调用,但是可能会在同一时间同时执行。这意味着在下面的例子中,变量totalA也许不能在计算上下文中被设置为正确的大写或者小写字母。 - - @volatile var totalA = 0 - - val text = Future { - "na" * 16 + "BATMAN!!!" - } - - text onSuccess { - case txt => totalA += txt.count(_ == 'a') - } - - text onSuccess { - case txt => totalA += txt.count(_ == 'A') - } - -以上,这两个回调(callbacks)可能是一个接一个地执行的,这样变量totalA得到的预期值为18。然而,它们也可能是并发执行的,于是totalA最终可能是16或2,因为+= 是一个不可分割的操作符(即它是由一个读和一个写的步骤组成,这样就可能使其与其他的读和写任意交错执行)。 - -考虑到完整性,回调的使用情景列在这儿: - -- 在future中注册onComplete回调的时候要确保最后future执行完成之后调用相应的终止回调。 - -- 注册onSuccess或者onFailure回调时也和注册onComplete一样,不同之处在于future执行成功或失败分别调用onSuccess或onSuccess的对应的闭包。 - -- 注册一个已经完成的future的回调最后将导致此回调一直处于执行状态(1所隐含的)。 - -- 在future中注册多个回调的情况下,这些回调的执行顺序是不确定的。事实上,这些回调也许是同时执行的,然而,特定的ExecutionContext执行可能导致明确的顺序。 - -- 在一些回调抛出异常的情况下,其他的回调的执行不受影响。 - -- 在一些情况下,回调函数永远不能结束(例如,这些回调处于无限循环中),其他回调可能完全不会执行。在这种情况下,对于那些潜在的阻塞回调要使用阻塞的构造(例子如下)。 - -- 一旦执行完,回调将从future对象中移除,这样更适合JVM的垃圾回收机制(GC)。 - -### 函数组合(Functional Composition)和For解构(For-Comprehensions) - -尽管前文所展示的回调机制已经足够把future的结果和后继计算结合起来的,但是有些时候回调机制并不易于使用,且容易造成冗余的代码。我们可以通过一个例子来说明。假设我们有一个用于进行货币交易服务的API,我们想要在有盈利的时候购进一些美元。让我们先来看看怎样用回调来解决这个问题: - - val rateQuote = Future { - connection.getCurrentValue(USD) - } - - rateQuote onSuccess { case quote => - val purchase = Future { - if (isProfitable(quote)) connection.buy(amount, quote) - else throw new Exception("not profitable") - } - - purchase onSuccess { - case _ => println("Purchased " + amount + " USD") - } - } - -首先,我们创建一个名为rateQuote的future对象并获得当前的汇率。在服务器返回了汇率且该future对象成功完成了之后,计算操作才会从onSuccess回调中执行,这时我们就可以开始判断买还是不买了。所以我们创建了另一个名为purchase的future对象,用来在可盈利的情况下做出购买决定,并在稍后发送一个请求。最后,一旦purchase运行结束,我们会在标准输出中打印一条通知消息。 - -这确实是可行的,但是有两点原因使这种做法并不方便。其一,我们不得不使用onSuccess,且不得不在其中嵌套purchase future对象。试想一下,如果在purchase执行完成之后我们可能会想要卖掉一些其他的货币。这时我们将不得不在onSuccess的回调中重复这个模式,从而可能使代码过度嵌套,过于冗长,并且难以理解。 - -其二,purchase只是定义在局部范围内--它只能被来自onSuccess内部的回调响应。这也就是说,这个应用的其他部分看不到purchase,而且不能为它注册其他的onSuccess回调,比如说卖掉些别的货币。 - -为解决上述的两个问题,futures提供了组合器(combinators)来使之具有更多易用的组合形式。映射(map)是最基本的组合器之一。试想给定一个future对象和一个通过映射来获得该future值的函数,映射方法将创建一个新Future对象,一旦原来的Future成功完成了计算操作,新的Future会通过该返回值来完成自己的计算。你能够像理解容器(collections)的map一样来理解future的map。 - -让我们用map的方法来重构一下前面的例子: - - val rateQuote = Future { - connection.getCurrentValue(USD) - } - - val purchase = rateQuote map { quote => - if (isProfitable(quote)) connection.buy(amount, quote) - else throw new Exception("not profitable") - } - - purchase onSuccess { - case _ => println("Purchased " + amount + " USD") - } - -通过对rateQuote的映射我们减少了一次onSuccess的回调,更重要的是避免了嵌套。这时如果我们决定出售一些货币就可以再次使用purchase方法上的映射了。 - -可是如果isProfitable方法返回了false将会发生些什么?会引发异常?这种情况下,purchase的确会因为异常而失败。不仅仅如此,想象一下,链接的中断和getCurrentValue方法抛出异常会使rateQuote的操作失败。在这些情况下映射将不会返回任何值,而purchase也会会自动的以和rateQuote相同的异常而执行失败。 - -总之,如果原Future的计算成功完成了,那么返回的Future将会使用原Future的映射值来完成计算。如果映射函数抛出了异常则Future也会带着该异常完成计算。如果原Future由于异常而计算失败,那么返回的Future也会包含相同的异常。这种异常的传导方式也同样适用于其他的组合器(combinators)。 - -使之能够在For-comprehensions原则下使用,是设计Future的目的之一。也正是因为这个原因,Future还拥有flatMap,filter和foreach等组合器。其中flatMap方法可以构造一个函数,它可以把值映射到一个姑且称为g的新future,然后返回一个随g的完成而完成的Future对象。 - -让我们假设我们想把一些美元兑换成瑞士法郎。我们必须为这两种货币报价,然后再在这两个报价的基础上确定交易。下面是一个在for-comprehensions中使用flatMap和withFilter的例子: - - val usdQuote = Future { connection.getCurrentValue(USD) } - val chfQuote = Future { connection.getCurrentValue(CHF) } - - val purchase = for { - usd <- usdQuote - chf <- chfQuote - if isProfitable(usd, chf) - } yield connection.buy(amount, chf) - - purchase onSuccess { - case _ => println("Purchased " + amount + " CHF") - } - -purchase只有当usdQuote和chfQuote都完成计算以后才能完成-- 它以其他两个Future的计算值为前提所以它自己的计算不能更早的开始。 - -上面的for-comprhension将被转换为: - - val purchase = usdQuote flatMap { - usd => - chfQuote - .withFilter(chf => isProfitable(usd, chf)) - .map(chf => connection.buy(amount, chf)) - } - -这的确是比for-comprehension稍微难以把握一些,但是我们这样分析有助于您更容易的理解flatMap的操作。FlatMap操作会把自身的值映射到其他future对象上,并随着该对象计算完成的返回值一起完成计算。在我们的例子里,flatMap用usdQuote的值把chfQuote的值映射到第三个futrue对象里,该对象用于发送一定量瑞士法郎的购入请求。只有当通过映射返回的第三个future对象完成了计算,purchase才能完成计算。 - -这可能有些难以置信,但幸运的是faltMap操作在for-comprhensions模式以外很少使用,因为for-comprehensions本身更容易理解和使用。 - -再说说filter,它可以用于创建一个新的future对象,该对象只有在满足某些特定条件的前提下才会得到原始future的计算值,否则就会抛出一个NoSuchElementException的异常而失败。调用了filter的future,其效果与直接调用withFilter完全一样。 - -作为组合器的collect同filter之间的关系有些类似容器(collections)API里的那些方法之间的关系。 - -值得注意的是,调用foreach组合器并不会在计算值可用的时候阻塞当前的进程去获取计算值。恰恰相反,只有当future对象成功计算完成了,foreach所迭代的函数才能够被异步的执行。这意味着foreach与onSuccess回调意义完全相同。 - -由于Future trait(译注: trait有点类似java中的接口(interface)的概念)从概念上看包含两种类型的返回值(计算结果和异常),所以组合器会有一个处理异常的需求。 - -比方说我们准备在rateQuote的基础上决定购入一定量的货币,那么`connection.buy`方法需要知道购入的数量和期望的报价值,最终完成购买的数量将会被返回。假如报价值偏偏在这个节骨眼儿改变了,那buy方法将会抛出一个`QuoteChangedExecption`,并且不会做任何交易。如果我们想让我们的Future对象返回0而不是抛出那个该死的异常,那我们需要使用recover组合器: - - val purchase: Future[Int] = rateQuote map { - quote => connection.buy(amount, quote) - } recover { - case QuoteChangedException() => 0 - } - -这里用到的recover能够创建一个新future对象,该对象当计算完成时持有和原future对象一样的值。如果执行不成功则偏函数的参数会被传递给使原Future失败的那个Throwable异常。如果它把Throwable映射到了某个值,那么新的Future就会成功完成并返回该值。如果偏函数没有定义在Throwable中,那么最终产生结果的future也会失败并返回同样的Throwable。 - -组合器recoverWith能够创建一个新future对象,当原future对象成功完成计算时,新future对象包含有和原future对象相同的计算结果。若原future失败或异常,偏函数将会返回造成原future失败的相同的Throwable异常。如果此时Throwable又被映射给了别的future,那么新Future就会完成并返回这个future的结果。recoverWith同recover的关系跟flatMap和map之间的关系很像。 - -fallbackTo组合器生成的future对象可以在该原future成功完成计算时返回结果,如果原future失败或异常返回future参数对象的成功值。在原future和参数future都失败的情况下,新future对象会完成并返回原future对象抛出的异常。正如下面的例子中,本想打印美元的汇率,但是在获取美元汇率失败的情况下会打印出瑞士法郎的汇率: - - val usdQuote = Future { - connection.getCurrentValue(USD) - } map { - usd => "Value: " + usd + "$" - } - val chfQuote = Future { - connection.getCurrentValue(CHF) - } map { - chf => "Value: " + chf + "CHF" - } - - al anyQuote = usdQuote fallbackTo chfQuote - - anyQuote onSuccess { println(_) } - -组合器andThen的用法是出于纯粹的side-effecting目的。经andThen返回的新Future无论原Future成功或失败都会返回与原Future一模一样的结果。一旦原Future完成并返回结果,andThen后跟的代码块就会被调用,且新Future将返回与原Future一样的结果,这确保了多个andThen调用的顺序执行。正如下例所示,这段代码可以从社交网站上把近期发出的帖子收集到一个可变集合里,然后把它们都打印在屏幕上: - - val allposts = mutable.Set[String]() - - Future { - session.getRecentPosts - } andThen { - case Success(posts) => allposts ++= posts - } andThen { - case _ => - clearAll() - for (post <- allposts) render(post) - } - -综上所述,Future的组合器功能是纯函数式的,每种组合器都会返回一个与原Future相关的新Future对象。 - -### 投影(Projections) - -为了确保for解构(for-comprehensions)能够返回异常,futures也提供了投影(projections)。如果原future对象失败了,失败的投影(projection)会返回一个带有Throwable类型返回值的future对象。如果原Future成功了,失败的投影(projection)会抛出一个NoSuchElementException异常。下面就是一个在屏幕上打印出异常的例子: - - val f = Future { - 2 / 0 - } - for (exc <- f.failed) println(exc) - -下面的例子不会在屏幕上打印出任何东西: - - val f = Future { - 4 / 2 - } - for (exc <- f.failed) println(exc) - -### Future的扩展 - -用更多的实用方法来对Futures API进行扩展支持已经被提上了日程,这将为很多外部框架提供更多专业工具。 - -## Blocking - -正如前面所说的,在future的blocking非常有效地缓解性能和预防死锁。虽然在futures中使用这些功能方面的首选方式是Callbacks和combinators,但在某些处理中也会需要用到blocking,并且它也是被Futures and Promises API所支持的。 - -在之前的并发交易(concurrency trading)例子中,在应用的最后有一处用到block来确定是否所有的futures已经完成。这有个如何使用block来处理一个future结果的例子: - - import scala.concurrent._ - import scala.concurrent.duration._ - - def main(args: Array[String]) { - val rateQuote = Future { - connection.getCurrentValue(USD) - } - - val purchase = rateQuote map { quote => - if (isProfitable(quote)) connection.buy(amount, quote) - else throw new Exception("not profitable") - } - - Await.result(purchase, 0 nanos) - } - -在这种情况下这个future是不成功的,这个调用者转发出了该future对象不成功的异常。它包含了失败的投影(projection)-- 阻塞(blocking)该结果将会造成一个NoSuchElementException异常在原future对象被成功计算的情况下被抛出。 - -相反的,调用`Await.ready`来等待这个future直到它已完成,但获不到它的结果。同样的方式,调用那个方法时如果这个future是失败的,它将不会抛出异常。 - -The Future trait实现了Awaitable trait还有其`ready()`和`result()`方法。这些方法不能被客户端直接调用,它们只能通过执行环境上下文来进行调用。 - -为了允许程序调用可能是阻塞式的第三方代码,而又不必实现Awaitable特质,原函数可以用如下的方式来调用: - - blocking { - potentiallyBlockingCall() - } - -这段blocking代码也可以抛出一个异常。在这种情况下,这个异常会转发给调用者。 - -## 异常(Exceptions) - -当异步计算抛出未处理的异常时,与那些计算相关的futures就失败了。失败的futures存储了一个Throwable的实例,而不是返回值。Futures提供onFailure回调方法,它用一个PartialFunction去表示一个Throwable。下列特殊异常的处理方式不同: - -`scala.runtime.NonLocalReturnControl[_]` --此异常保存了一个与返回相关联的值。通常情况下,在方法体中的返回结构被调用去抛出这个异常。相关联的值将会存储到future或一个promise中,而不是一直保存在这个异常中。 - -ExecutionException-当因为一个未处理的中断异常、错误或者`scala.util.control.ControlThrowable`导致计算失败时会被存储起来。这种情况下,ExecutionException会为此具有未处理的异常。这些异常会在执行失败的异步计算线程中重新抛出。这样做的目的,是为了防止正常情况下没有被客户端代码处理过的那些关键的、与控制流相关的异常继续传播下去,同时告知客户端其中的future对象是计算失败的。 - -更精确的语义描述请参见 [NonFatal]。 - -## Promises - -到目前为止,我们仅考虑了通过异步计算的方式创建future对象来使用future的方法。尽管如此,futures也可以使用promises来创建。 - -如果说futures是为了一个还没有存在的结果,而当成一种只读占位符的对象类型去创建,那么promise就被认为是一个可写的,可以实现一个future的单一赋值容器。这就是说,promise通过这种success方法可以成功去实现一个带有值的future。相反的,因为一个失败的promise通过failure方法就会实现一个带有异常的future。 - -一个promise p通过p.future方式返回future。 这个futrue对象被指定到promise p。根据这种实现方式,可能就会出现p.future与p相同的情况。 - -考虑下面的生产者 - 消费者的例子,其中一个计算产生一个值,并把它转移到另一个使用该值的计算。这个传递中的值通过一个promise来完成。 - - import scala.concurrent.{ Future, Promise } - import scala.concurrent.ExecutionContext.Implicits.global - - val p = Promise[T]() - val f = p.future - - val producer = Future { - val r = produceSomething() - p success r - continueDoingSomethingUnrelated() - } - - val consumer = Future { - startDoingSomething() - f onSuccess { - case r => doSomethingWithResult() - } - } - -在这里,我们创建了一个promise并利用它的future方法获得由它实现的Future。然后,我们开始了两种异步计算。第一种做了某些计算,结果值存放在r中,通过执行promise p,这个值被用来完成future对象f。第二种做了某些计算,然后读取实现了future f的计算结果值r。需要注意的是,在生产者完成执行`continueDoingSomethingUnrelated()` 方法这个任务之前,消费者可以获得这个结果值。 - -正如前面提到的,promises具有单赋值语义。因此,它们仅能被实现一次。在一个已经计算完成的promise或者failed的promise上调用success方法将会抛出一个IllegalStateException异常。 - -下面的这个例子显示了如何fail a promise。 - - val p = promise[T] - val f = p.future - - val producer = Future { - val r = someComputation - if (isInvalid(r)) - p failure (new IllegalStateException) - else { - val q = doSomeMoreComputation(r) - p success q - } - } - -如上,生产者计算出一个中间结果值r,并判断它的有效性。如果它不是有效的,它会通过返回一个异常实现promise p的方式fails the promise,关联的future f是failed。否则,生产者会继续它的计算,最终使用一个有效的结果值实现future f,同时实现 promise p。 - -Promises也能通过一个complete方法来实现,这个方法采用了一个`potential value Try[T]`,这个值要么是一个类型为`Failure[Throwable]`的失败的结果值,要么是一个类型为`Success[T]`的成功的结果值。 - -类似success方法,在一个已经完成(completed)的promise对象上调用failure方法和complete方法同样会抛出一个IllegalStateException异常。 - -应用前面所述的promises和futures方法的一个优点是,这些方法是单一操作的并且是没有副作用(side-effects)的,因此程序是具有确定性的(deterministic)。确定性意味着,如果该程序没有抛出异常(future的计算值被获得),无论并行的程序如何调度,那么程序的结果将会永远是一样的。 - -在一些情况下,客户端也许希望能够只在promise没有完成的情况下完成该promise的计算(例如,如果有多个HTTP请求被多个不同的futures对象来执行,并且客户端只关心地一个HTTP应答(response),该应答对应于地一个完成该promise的future)。因为这个原因,future提供了tryComplete,trySuccess和tryFailure方法。客户端需要意识到调用这些的结果是不确定的,调用的结果将以来从程序执行的调度。 - -completeWith方法将用另外一个future完成promise计算。当该future结束的时候,该promise对象得到那个future对象同样的值,如下的程序将打印1: - - val f = Future { 1 } - val p = promise[Int] - - p completeWith f - - p.future onSuccess { - case x => println(x) - } - -当让一个promise以异常失败的时候,三总子类型的Throwable异常被分别的处理。如果中断该promise的可抛出(Throwable)一场是`scala.runtime.NonLocalReturnControl`,那么该promise将以对应的值结束;如果是一个Error的实例,`InterruptedException`或者`scala.util.control.ControlThrowable`,那么该可抛出(Throwable)异常将会封装一个ExecutionException异常,该ExectionException将会让该promise以失败结束。 - -通过使用promises,futures的onComplete方法和future的构造方法,你能够实现前文描述的任何函数式组合组合器(compition combinators)。让我们来假设一下你想实现一个新的组合起,该组合器首先使用两个future对象f和,产生第三个future,该future能够用f或者g来完成,但是只在它能够成功完成的情况下。 - -这里有个关于如何去做的实例: - - def first[T](f: Future[T], g: Future[T]): Future[T] = { - val p = promise[T] - - f onSuccess { - case x => p.trySuccess(x) - } - - g onSuccess { - case x => p.trySuccess(x) - } - - p.future - } - -注意,在这种实现方式中,如果f与g都不是成功的,那么`first(f, g)`将不会实现(即返回一个值或者返回一个异常)。 - -## 工具(Utilities) - -为了简化在并发应用中处理时序(time)的问题,`scala.concurrent`引入了Duration抽象。Duration不是被作为另外一个通常的时间抽象存在的。他是为了用在并发(concurrency)库中使用的,Duration位于`scala.concurrent`包中。 - -Duration是表示时间长短的基础类,其可以是有限的或者无限的。有限的duration用FiniteDuration类来表示,并通过时间长度`(length)`和`java.util.concurrent.TimeUnit`来构造。无限的durations,同样扩展了Duration,只在两种情况下存在,`Duration.Inf`和`Duration.MinusInf`。库中同样提供了一些Durations的子类用来做隐式的转换,这些子类不应被直接使用。 - -抽象的Duration类包含了如下方法: - -到不同时间单位的转换`(toNanos, toMicros, toMillis, toSeconds, toMinutes, toHours, toDays and toUnit(unit: TimeUnit))`。 -durations的比较`(<,<=,>和>=)`。 -算术运算符`(+, -, *, / 和单值运算_-)` -duration的最大最小方法`(min,max)`。 -测试duration是否是无限的方法`(isFinite)`。 -Duration能够用如下方法实例化`(instantiated)`: - -隐式的通过Int和Long类型转换得来 `val d = 100 millis`。 -通过传递一个`Long length`和`java.util.concurrent.TimeUnit`。例如`val d = Duration(100, MILLISECONDS)`。 -通过传递一个字符串来表示时间区间,例如 `val d = Duration("1.2 µs")`。 -Duration也提供了unapply方法,因此可以i被用于模式匹配中,例如: - - import scala.concurrent.duration._ - import java.util.concurrent.TimeUnit._ - - // instantiation - val d1 = Duration(100, MILLISECONDS) // from Long and TimeUnit - val d2 = Duration(100, "millis") // from Long and String - val d3 = 100 millis // implicitly from Long, Int or Double - val d4 = Duration("1.2 µs") // from String - - // pattern matching - val Duration(length, unit) = 5 millis - diff --git a/zh-cn/overviews/core/implicit-classes.md b/zh-cn/overviews/core/implicit-classes.md deleted file mode 100644 index a68d2c2758..0000000000 --- a/zh-cn/overviews/core/implicit-classes.md +++ /dev/null @@ -1,83 +0,0 @@ ---- -layout: overview -overview: implicit-classes -label-color: success -label-text: Available -language: zh-cn -title: Implicit Classes - -discourse: false ---- - -**Josh Suereth 著** - -## 介绍 - -Scala 2.10引入了一种叫做隐式类的新特性。隐式类指的是用implicit关键字修饰的类。在对应的作用域内,带有这个关键字的类的主构造函数可用于隐式转换。 - -隐式类型是在[SIP-13](http://docs.scala-lang.org/sips/pending/implicit-classes.html)中提出的。 - -## 用法 - -创建隐式类时,只需要在对应的类前加上implicit关键字。比如: - - object Helpers { - implicit class IntWithTimes(x: Int) { - def times[A](f: => A): Unit = { - def loop(current: Int): Unit = - if(current > 0) { - f - loop(current - 1) - } - loop(x) - } - } - } - -这个例子创建了一个名为IntWithTimes的隐式类。这个类包含一个int值和一个名为times的方法。要使用这个类,只需将其导入作用域内并调用times方法。比如: - - scala> import Helpers._ - import Helpers._ - - scala> 5 times println("HI") - HI - HI - HI - HI - HI - -使用隐式类时,类名必须在当前作用域内可见且无歧义,这一要求与隐式值等其他隐式类型转换方式类似。 - -## 限制条件 - -隐式类有以下限制条件: - -1. 只能在别的trait/类/对象内部定义。 - -```` - object Helpers { - implicit class RichInt(x: Int) // 正确! - } - implicit class RichDouble(x: Double) // 错误! -```` - -2. 构造函数只能携带一个非隐式参数。 -```` - implicit class RichDate(date: java.util.Date) // 正确! - implicit class Indexer[T](collecton: Seq[T], index: Int) // 错误! - implicit class Indexer[T](collecton: Seq[T])(implicit index: Index) // 正确! -```` - -虽然我们可以创建带有多个非隐式参数的隐式类,但这些类无法用于隐式转换。 - -3. 在同一作用域内,不能有任何方法、成员或对象与隐式类同名。 - -注意:这意味着隐式类不能是case class。 - - object Bar - implicit class Bar(x: Int) // 错误! - - val x = 5 - implicit class x(y: Int) // 错误! - - implicit case class Baz(x: Int) // 错误! diff --git a/zh-cn/overviews/core/string-interpolation.md b/zh-cn/overviews/core/string-interpolation.md deleted file mode 100644 index 971b557d01..0000000000 --- a/zh-cn/overviews/core/string-interpolation.md +++ /dev/null @@ -1,122 +0,0 @@ ---- -layout: overview -discourse: true -language: zh-cn -label-color: success -label-text: New in 2.10 -overview: string-interpolation -title: 字符串插值 - -discourse: false ---- - -**Josh Suereth 著** - -## 简介 - -自2.10.0版本开始,Scala提供了一种新的机制来根据数据生成字符串:字符串插值。字符串插值允许使用者将变量引用直接插入处理过的字面字符中。如下例: - - val name="James" - println(s"Hello,$name")//Hello,James - -在上例中, s"Hello,$name" 是待处理字符串字面,编译器会对它做额外的工作。待处理字符串字面通过“号前的字符来标示(例如:上例中是s)。字符串插值的实现细节在 [SIP-11](http://docs.scala-lang.org/sips/pending/string-interpolation.html) 中有全面介绍。 - -## 用法 - -Scala 提供了三种创新的字符串插值方法:s,f 和 raw. - -### s 字符串插值器 - -在任何字符串前加上s,就可以直接在串中使用变量了。你已经见过这个例子: - - val name="James" - println(s"Hello,$name")//Hello,James -此例中,$name嵌套在一个将被s字符串插值器处理的字符串中。插值器知道在这个字符串的这个地方应该插入这个name变量的值,以使输出字符串为Hello,James。使用s插值器,在这个字符串中可以使用任何在处理范围内的名字。 - -字符串插值器也可以处理任意的表达式。例如: - - println(s"1+1=${1+1}") -将会输出字符串1+1=2。任何表达式都可以嵌入到${}中。 - -### f 插值器 - -在任何字符串字面前加上 f,就可以生成简单的格式化串,功能相似于其他语言中的 printf 函数。当使用 f 插值器的时候,所有的变量引用都应当后跟一个printf-style格式的字符串,如%d。看下面这个例子: - - val height=1.9d - val name="James" - println(f"$name%s is $height%2.2f meters tall")//James is 1.90 meters tall -f 插值器是类型安全的。如果试图向只支持 int 的格式化串传入一个double 值,编译器则会报错。例如: - - val height:Double=1.9d - - scala>f"$height%4d" - :9: error: type mismatch; - found : Double - required: Int - f"$height%4d" - ^ -f 插值器利用了java中的字符串数据格式。这种以%开头的格式在 [Formatter javadoc] 中有相关概述。如果在具体变量后没有%,则格式化程序默认使用 %s(串型)格式。 - -### raw 插值器 - -除了对字面值中的字符不做编码外,raw 插值器与 s 插值器在功能上是相同的。如下是个被处理过的字符串: - - scala>s"a\nb" - res0:String= - a - b -这里,s 插值器用回车代替了\n。而raw插值器却不会如此处理。 - - scala>raw"a\nb" - res1:String=a\nb -当不想输入\n被转换为回车的时候,raw 插值器是非常实用的。 - -除了以上三种字符串插值器外,使用者可以自定义插值器。 - -### 高级用法 - -在Scala中,所有处理过的字符串字面值都进行了简单编码转换。任何时候编译器遇到一个如下形式的字符串字面值: - - id"string content" -它都会被转换成一个StringContext实例的call(id)方法。这个方法在隐式范围内仍可用。只需要简单得 -建立一个隐类,给StringContext实例增加一个新方法,便可以定义我们自己的字符串插值器。如下例: - - //注意:为了避免运行时实例化,我们从AnyVal中继承。 - //更多信息请见值类的说明 - implicit class JsonHelper(val sc:StringContext) extends AnyVal{ - def json(args:Any*):JSONObject=sys.error("TODO-IMPLEMENT") - } - - def giveMeSomeJson(x:JSONObject):Unit=... - - giveMeSomeJson(json"{name:$name,id:$id}") -在这个例子中,我们试图通过字符串插值生成一个JSON文本语法。隐类 JsonHelper 作用域内使用该语法,且这个JSON方法需要一个完整的实现。只不过,字符串字面值格式化的结果不是一个字符串,而是一个JSON对象。 - -当编译器遇到"{name:$name,id:$id"}",它将会被重写成如下表达式: - - new StringContext("{name:",",id:","}").json(name,id) - -隐类则被重写成如下形式 - - new JsonHelper(new StringContext("{name:",",id:","}")).json(name,id) - -所以,JSON方法可以访问字符串的原生片段而每个表达式都是一个值。这个方法的一个简单但又令人迷惑的例子: - - implicit class JsonHelper(val sc:StringContext) extends AnyVal{ - def json(args:Any*):JSONObject={ - val strings=sc.parts.iterator - val expressions=args.iterator - var buf=new StringBuffer(strings.next) - while(strings.hasNext){ - buf append expressions.next - buf append strings.next - } - parseJson(buf) - } - } - -被处理过的字符串的每部分都是StringContext的成员。每个表达式的值都将传入到JSON方法的args参数。JSON方法接受这些值并合成一个大字符串,然后再解析成JSON格式。有一种更复杂的实现可以避免合成字符串的操作,它只是简单的直接通过原生字符串和表达式值构建JSON。 - -## 限制 - -字符串插值目前对模式匹配语句不适用。此特性将在2.11版本中生效。 diff --git a/zh-cn/overviews/core/value-classes.md b/zh-cn/overviews/core/value-classes.md deleted file mode 100644 index 5661e08c23..0000000000 --- a/zh-cn/overviews/core/value-classes.md +++ /dev/null @@ -1,261 +0,0 @@ ---- -layout: overview -language: zh-cn -label-color: success -label-text: New in 2.10 -overview: value-classes -title: Value Classes and Universal Traits - -discourse: false ---- - -**Mark Harrah 著** - -## 引言 - -Value classes是在[SIP-15](http://docs.scala-lang.org/sips/pending/value-classes.html)中提出的一种通过继承AnyVal类来避免运行时对象分配的新机制。以下是一个最简的value class。 - - class Wrapper(val underlying: Int) extends AnyVal - -它仅有一个被用作运行时底层表示的公有val参数。在编译期,其类型为Wrapper,但在运行时,它被表示为一个Int。Value class可以带有def定义,但不能再定义额外的val、var,以及内嵌的trait、class或object: - - class Wrapper(val underlying: Int) extends AnyVal { - def foo: Wrapper = new Wrapper(underlying * 19) - } - -Value class只能继承universal traits,但其自身不能再被继承。所谓universal trait就是继承自Any的、只有def成员,且不作任何初始化工作的trait。继承自某个universal trait的value class同时继承了该trait的方法,但是(调用这些方法)会带来一定的对象分配开销。例如: - - trait Printable extends Any { - def print(): Unit = println(this) - } - class Wrapper(val underlying: Int) extends AnyVal with Printable - - val w = new Wrapper(3) - w.print() // 这里实际上会生成一个Wrapper类的实例 - -本文后续篇幅将介绍相关用例和与对象分配时机相关的细节,并给出一些有关value class自身限制的具体实例。 - -## 扩展方法 - -关于value类的一个用例,是将它们和隐含类联合([SIP-13](http://docs.scala-lang.org/sips/pending/implicit-classes.html))以获得免分配扩展方法。使用隐含类可以提供便捷的语法来定义扩展方法,同时 value 类移除运行时开销。一个好的例子是在标准库里的RichInt类。RichInt 继承自Int类型并附带一些方法。由于它是一个 value类,使用RichInt 方法时不需要创建一个RichInt 的实例。 - -下面有关RichInt的代码片段示范了RichInt是如何继承Int来允许3.toHexString的表达式: - - implicit class RichInt(val self: Int) extends AnyVal { - def toHexString: String = java.lang.Integer.toHexString(self) - } - -在运行时,表达式3.toHexString 被优化并等价于静态对象的方法调用 (RichInt$.MODULE$.extension$toHexString(3)),而不是创建一个新实例对象,再调用其方法。 - -## 正确性 - -关于value类的另一个用例是:不增加运行时开销的同时,获得数据类型的类型安全。例如,一个数据类型片断代表一个距离 ,如: - - class Meter(val value: Double) extends AnyVal { - def +(m: Meter): Meter = new Meter(value + m.value) - } - -代码:对两个距离进行相加,例如: - - val x = new Meter(3.4) - val y = new Meter(4.3) - val z = x + y - -实际上不会分配任何Meter实例,而是在运行时仅使用原始双精浮点数(double) 。 - -注意:在实践中,可以使用条件类(case)and/or 扩展方法来让语句更清晰。 - -## 必须进行分配的情况 - -由于JVM不支持value类,Scala 有时需要真正实例化value类。详细细节见[SIP-15]。 - -### 分配概要 - -value类在以下情况下,需要真正实例化: - -1. value类作为另一种类型使用时。 -2. value类被赋值给数组。 -3. 执行运行时类型测试,例如模式匹配。 - -### 分配细节 - -无论何时,将value类作为另一种类型进行处理时(包括universal trait),此value类实例必须被实例化。例如,value类Meter : - - trait Distance extends Any - case class Meter(val value: Double) extends AnyVal with Distance - -接收Distance类型值的方法需要一个正真的Meter实例。下面的例子中,Meter类真正被实例化。 - - def add(a: Distance, b: Distance): Distance = ... - add(Meter(3.4), Meter(4.3)) - -如果替换add方法的签名: - - def add(a: Meter, b: Meter): Meter = ... - -那么就不必进行分配了。此规则的另一个例子是value类作为类型参数使用。例如:即使是调用identity方法,也必须创建真正的Meter实例。 - - def identity[T](t: T): T = t - identity(Meter(5.0)) - -必须进行分配的另一种情况是:将它赋值给数组。即使这个数组就是value类数组,例如: - - val m = Meter(5.0) - val array = Array[Meter](m) - -数组中包含了真正的Meter 实例,并不只是底层基本类型double。 - -最后是类型测试。例如,模式匹配中的处理以及asInstanceOf方法都要求一个真正的value类实例: - - case class P(val i: Int) extends AnyVal - - val p = new P(3) - p match { // 在这里,新的P实例被创建 - case P(3) => println("Matched 3") - case P(x) => println("Not 3") - } - -## 限制 - -目前Value类有一些限制,部分原因是JVM不提供value类概念的原生支持。value类的完整实现细节及其限制见[SIP-15]。 - -### 限制概要 - -一个value类 ... - -1. ... 必须只有一个public的构造函数。并有且只有一个public的,类型不为value类的val参数。 -2. ... 不能有特殊的类型参数. -3. ... 不能有嵌套或本地类、trait或对象。 -4. ... 不能定义equals或hashCode方法。 -5. ... 必须是一个顶级类,或静态访问对象的一个成员 -6. ... 仅能有def为成员。尤其是,成员不能有惰性val、val或者var 。 -7. ... 不能被其它类继承。 - -### 限制示例 - -本章节列出了许多限制下具体影响, 而在“必要分配”章节已提及的部分则不再敖述。 - -构造函数不允许有多个参数: - - class Complex(val real: Double, val imag: Double) extends AnyVal - -则Scala编译器将生成以下的错误信息: - - Complex.scala:1: error: value class needs to have exactly one public val parameter - (Complex.scala:1: 错误:value类只能有一个public的val参数。) - (译者注:鉴于实际中编译器输出的可能是英文信息,在此提供双语。) - class Complex(val real: Double, val imag: Double) extends AnyVal - ^ - -由于构造函数参数必须是val,而不能是一个按名(by-name)参数: - - NoByName.scala:1: error: `val' parameters may not be call-by-name - (NoByName.scala:1: 错误: `val' 不能为 call-by-name) - class NoByName(val x: => Int) extends AnyVal - ^ - -Scala不允许惰性val作为构造函数参数, 所以value类也不允许。并且不允许多个构造函数。 - - class Secondary(val x: Int) extends AnyVal { - def this(y: Double) = this(y.toInt) - } - - Secondary.scala:2: error: value class may not have secondary constructors - (Secondary.scala:2: 错误:value类不能有第二个构造函数。) - def this(y: Double) = this(y.toInt) - ^ - -value class不能将惰性val或val作为成员,也不能有嵌套类、trait或对象。 - - class NoLazyMember(val evaluate: () => Double) extends AnyVal { - val member: Int = 3 - lazy val x: Double = evaluate() - object NestedObject - class NestedClass - } - - Invalid.scala:2: error: this statement is not allowed in value class: private[this] val member: Int = 3 - (Invalid.scala:2: 错误: value类中不允许此表达式:private [this] val member: Int = 3) - val member: Int = 3 - ^ - Invalid.scala:3: error: this statement is not allowed in value class: lazy private[this] var x: Double = NoLazyMember.this.evaluate.apply() - (Invalid.scala:3: 错误:value类中不允许此表达式: lazy private[this] var x: Double = NoLazyMember.this.evaluate.apply()) - lazy val x: Double = evaluate() - ^ - Invalid.scala:4: error: value class may not have nested module definitions - (Invalid.scala:4: 错误: value类中不能定义嵌套模块) - object NestedObject - ^ - Invalid.scala:5: error: value class may not have nested class definitions - (Invalid.scala:5: 错误:value类中不能定义嵌套类) - class NestedClass - ^ - -注意:value类中也不允许出现本地类、trait或对象,如下: - - class NoLocalTemplates(val x: Int) extends AnyVal { - def aMethod = { - class Local - ... - } - } - -在目前value类实现的限制下,value类不能嵌套: - - class Outer(val inner: Inner) extends AnyVal - class Inner(val value: Int) extends AnyVal - - Nested.scala:1: error: value class may not wrap another user-defined value class - (Nested.scala:1:错误:vlaue类不能包含另一个用户定义的value类) - class Outer(val inner: Inner) extends AnyVal - ^ - -此外,结构类型不能使用value类作为方法的参数或返回值类型。 - - class Value(val x: Int) extends AnyVal - object Usage { - def anyValue(v: { def value: Value }): Value = - v.value - } - - Struct.scala:3: error: Result type in structural refinement may not refer to a user-defined value class - (Struct.scala:3: 错误: 结构细化中的结果类型不适用于用户定义的value类) - def anyValue(v: { def value: Value }): Value = - ^ - -value类不能继承non-universal trait,并且其本身不能被继承: - - trait NotUniversal - class Value(val x: Int) extends AnyVal with notUniversal - class Extend(x: Int) extends Value(x) - - Extend.scala:2: error: illegal inheritance; superclass AnyVal - is not a subclass of the superclass Object - of the mixin trait NotUniversal - (Extend.scala:2: 错误:非法继承:父类AnyVal不是一个父类对象(混入trait NotUniversal)的子类) - class Value(val x: Int) extends AnyVal with NotUniversal - ^ - Extend.scala:3: error: illegal inheritance from final class Value - (Extend.scala:3: 错误: 从Value类(final类)非法继承) - class Extend(x: Int) extends Value(x) - ^ - -第二条错误信息显示:虽然value类没有显式地用final关键字修饰,但依然认为value类是final类。 - -另一个限制是:一个类仅支持单个参数的话,则value类必须是顶级类,或静态访问对象的成员。这是由于嵌套value类需要第二个参数来引用封闭类。所以不允许下述代码: - - class Outer { - class Inner(val x: Int) extends AnyVal - } - - Outer.scala:2: error: value class may not be a member of another class - (Outer.scala:2: 错误:value类不能作为其它类的成员) - class Inner(val x: Int) extends AnyVal - ^ - -但允许下述代码,因为封闭对象是顶级类: - - object Outer { - class Inner(val x: Int) extends AnyVal - } - diff --git a/zh-cn/overviews/index.md b/zh-cn/overviews/index.md deleted file mode 100644 index a363ab3b38..0000000000 --- a/zh-cn/overviews/index.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -layout: guides-index -language: zh-cn -title: 目录 ---- - -
                  -

                  核心库

                  -
                  - * Scala的容器类 - * [简介](/zh-cn/overviews/collections/introduction.html) - * [Mutable和Immutable集合](/zh-cn/overviews/collections/overview.html) - * [Trait Traversable](/zh-cn/overviews/collections/trait-traversable.html) - * [Trait Iterable](/zh-cn/overviews/collections/trait-iterable.html) - * [序列trait:Seq、IndexedSeq 及 LinearSeq](/zh-cn/overviews/collections/seqs.html) - * [集合](/zh-cn/overviews/collections/sets.html) - * [映射](/zh-cn/overviews/collections/maps.html) - * [具体的不可变集实体类](/zh-cn/overviews/collections/concrete-immutable-collection-classes.html) - * [具体的可变容器类](/zh-cn/overviews/collections/concrete-mutable-collection-classes.html) - * [数组](/zh-cn/overviews/collections/arrays.html) - * [字符串](/zh-cn/overviews/collections/strings.html) - * [性能特点](/zh-cn/overviews/collections/performance-characteristics.html) - * [等价性](/zh-cn/overviews/collections/equality.html) - * [视图](/zh-cn/overviews/collections/views.html) - * [Iterators](/zh-cn/overviews/collections/iterators.html) - * [从头定义新容器](/zh-cn/overviews/collections/creating-collections-from-scratch.html) - * [Java和Scala容器的转换](/zh-cn/overviews/collections/conversions-between-java-and-scala-collections.html) - * [Scala 2.7迁移指南](/zh-cn/overviews/collections/migrating-from-scala-27.html) - * [Scala容器类体系结构](/zh-cn/overviews/core/architecture-of-scala-collections.html) - * [字符串插值](/zh-cn/overviews/core/string-interpolation.html) New in 2.10 - * [Implicit Classes](/zh-cn/overviews/core/implicit-classes.html) New in 2.10 - * [Value Classes and Universal Traits](/zh-cn/overviews/core/value-classes.html) New in 2.10 - -
                  -

                  Parallel and Concurrent Programming

                  -
                  - * [Future和Promise](/zh-cn/overviews/core/futures.html) New in 2.10 - * Scala的并行容器类 - * [概述](/zh-cn/overviews/parallel-collections/overview.html) - * [具体并行集合类](/zh-cn/overviews/parallel-collections/concrete-parallel-collections.html) - * [并行容器的转换](/zh-cn/overviews/parallel-collections/conversions.html) - * [并发字典树](/zh-cn/overviews/parallel-collections/ctries.html) - * [并行集合库的架构](/zh-cn/overviews/parallel-collections/architecture.html) - * [创建自定义并行容器](/zh-cn/overviews/parallel-collections/custom-parallel-collections.html) - * [配置并行集合](/zh-cn/overviews/parallel-collections/configuration.html) - * [测量性能](/zh-cn/overviews/parallel-collections/performance.html) - * [Scala Actors迁移指南](/zh-cn/overviews/core/actors-migration-guide.html) New in 2.10 - * [The Scala Actors API](/zh-cn/overviews/core/actors.html) Deprecated - -
                  -

                  Acknowledgements

                  -
                  -* [致谢名单](/zh-cn/overviews/thanks.html) diff --git a/zh-cn/overviews/parallel-collections/architecture.md b/zh-cn/overviews/parallel-collections/architecture.md deleted file mode 100644 index 90e14eb9a8..0000000000 --- a/zh-cn/overviews/parallel-collections/architecture.md +++ /dev/null @@ -1,61 +0,0 @@ ---- -layout: overview-large -title: 并行集合库的架构 - -discourse: false - -partof: parallel-collections -num: 5 -language: zh-cn ---- - -像正常的顺序集合库那样,Scala的并行集合库包含了大量的由不同并行集合实现的一致的集合操作。并且像顺序集合库那样,scala的并行集合库通过并行集合“模板”实现了大部分操作,从而防止了代码重复。“模板”只需要定义一次就可以通过不同的并行集合被灵活地继承。 - -这种方法的好处是大大缓解了维护和可扩展性。对于维护--所有的并行集合通过继承一个有单一实现的并行集合,维护变得更容易和更健壮;bug修复传播到类层次结构,而不需要复制实现。出于同样的原因,整个库变得更易于扩展--新的集合类可以简单地继承大部分的操作。 - -### 核心抽象 - -上述的”模板“特性实现的多数并行操作都是根据两个核心抽象--分割器和组合器。 - -#### 分割器 - -Spliter的工作,正如其名,它把一个并行集合分割到了它的元素的非重要分区里面。基本的想法是将集合分割成更小的部分直到他们小到足够在序列上操作。 - - trait Splitter[T] extends Iterator[T] { - def split: Seq[Splitter[T]] - } - -有趣的是,分割器是作为迭代器实现的,这意味着除了分割,他们也被框架用来遍历并行集合(也就是说,他们继承了迭代器的标准方法,如next()和hasNext())。这种“分割迭代器”的独特之处是它的分割方法把自身(迭代器类型的分割器)进一步分割成额外的分割器,这些新的分割器能遍历到整个并行集合的不相交的元素子集。类似于正常的迭代器,分割器在调用分割方法后失效。 - -一般来说,集合是使用分割器(Splitters)分成大小大致相同的子集。在某些情况下,任意大小的分区是必须的,特别是在并行序列上,PreciseSplitter(精确的分割器)是很有用的,它是继承于Splitter和另外一个实现了精确分割的方法--psplit. - -#### 组合器 - -组合器被认为是一个来自于Scala序列集合库的广义构造器。每一个并行集合都提供一个单独的组合器,同样,每一个序列集合也提供一个构造器。 - -而对于序列集合,元素可以被增加到一个构造器中去,并且集合可以通过调用结果方法生成,对于并行集合,组合器有一个叫做combine的方法,它调用其他的组合器进行组合并产生包含两个元素的并集的新组合器。当调用combine方法后,这两个组合器都会变成无效的。 - -trait Combiner[Elem, To] extends Builder[Elem, To] { - def combine(other: Combiner[Elem, To]): Combiner[Elem, To] -} -这两个类型参数Elem,根据上下文分别表示元素类型和结果集合的类型。 - -注意:鉴于两个组合器,c1和c2,在c1=c2为真(意味它们是同一个组合器),调用c1.combine(c2)方法总是什么都不做并且简单的返回接收的组合器c1。 - -### 层级 - -Scala的并行集合吸收了很多来自于Scala的(序列)集合库的设计灵感--事实上,它反映了规则地集合框架的相应特征,如下所示。 - -![parallel-collections-hierarchy.png](/resources/images/parallel-collections-hierarchy.png) - -Scala集合的层次和并行集合库 - -当然我们的目标是尽可能紧密集成并行集合和序列集合,以允许序列集合和并行集合之间的简单替代。 - -为了能够获得一个序列集合或并行集合的引用(这样可以通过par和seq在并行集合和序列集合之间切换),两种集合类型存在一个共同的超型。这是上面所示的“通用”特征的起源,GenTraversable, GenIterable, GenSeq, GenMap and GenSet,不保证按次序或挨个的遍历。相应的序列或并行特征继承于这些。例如,一个ParSeq和Seq都是一个通用GenSeq的子类型,但是他们之间没有相互公认的继承关系。 - -更详细的讨论序列集合和并行集合器之间的层次共享,请参见技术报告。[[1](http://infoscience.epfl.ch/record/165523/files/techrep.pdf)] - -引用 - -1. [On a Generic Parallel Collection Framework, Aleksandar Prokopec, Phil Bawgell, Tiark Rompf, Martin Odersky, June 2011](http://infoscience.epfl.ch/record/165523/files/techrep.pdf) diff --git a/zh-cn/overviews/parallel-collections/concrete-parallel-collections.md b/zh-cn/overviews/parallel-collections/concrete-parallel-collections.md deleted file mode 100644 index 55eb24c369..0000000000 --- a/zh-cn/overviews/parallel-collections/concrete-parallel-collections.md +++ /dev/null @@ -1,158 +0,0 @@ ---- -layout: overview-large -title: 具体并行集合类 - -discourse: false - -partof: parallel-collections -num: 2 -language: zh-cn ---- - -### 并行数组(Parallel Range) - -一个并行数组由线性、连续的数组元素序列。这意味着这些元素可以高效的被访问和修改。正因为如此,遍历元素也是非常高效的。在此意义上并行数组就是一个大小固定的数组。 - - scala> val pa = scala.collection.parallel.mutable.ParArray.tabulate(1000)(x => 2 * x + 1) - pa: scala.collection.parallel.mutable.ParArray[Int] = ParArray(1, 3, 5, 7, 9, 11, 13,... - - scala> pa reduce (_ + _) - res0: Int = 1000000 - - scala> pa map (x => (x - 1) / 2) - res1: scala.collection.parallel.mutable.ParArray[Int] = ParArray(0, 1, 2, 3, 4, 5, 6, 7,... - -在内部,分离一个并行数组[分离器](http://docs.scala-lang.org/overviews/parallel-collections/architecture.html#core_abstractions)相当于使用它们的下标迭代器更新来创建两个分离器。[组合](http://docs.scala-lang.org/overviews/parallel-collections/architecture.html#core_abstractions)稍微负责一点。因为大多数的分离方法(如:flatmap, filter, takeWhile等)我们不能预先知道元素的个数(或者数组的大小),每一次组合本质上来说是一个数组缓冲区的一种变量根据分摊时间来进行加减的操作。不同的处理器进行元素相加操作,对每个独立并列数组进行组合,然后根据其内部连结在再进行组合。在并行数组中的基础数组只有在知道元素的总数之后才能被分配和填充。基于此,变换方法比存取方法要稍微复杂一些。另外,请注意,最终数组分配在JVM上的顺序进行,如果映射操作本身是很便宜,这可以被证明是一个序列瓶颈。 - -通过调用seq方法,并行数组(parallel arrays)被转换为对应的顺序容器(sequential collections) ArraySeq。这种转换是非常高效的,因为新创建的ArraySeq 底层是通过并行数组(parallel arrays)获得的。 - -### 并行向量(Parallel Vector) - -这个并行向量是一个不可变数列,低常数因子数的访问和更新的时间。 - - scala> val pv = scala.collection.parallel.immutable.ParVector.tabulate(1000)(x => x) - pv: scala.collection.parallel.immutable.ParVector[Int] = ParVector(0, 1, 2, 3, 4, 5, 6, 7, 8, 9,... - - scala> pv filter (_ % 2 == 0) - res0: scala.collection.parallel.immutable.ParVector[Int] = ParVector(0, 2, 4, 6, 8, 10, 12, 14, 16, 18,... -不可变向量表现为32叉树,因此[分离器]通过将子树分配到每个分离器(spliter)来分离。[组合(combiners)]存储元素的向量并通过懒惰(lazily)拷贝来组合元素。因此,转换方法相对于并行数组来说可伸缩性较差。一旦串联操作在将来scala的发布版本中成为可变的,组合器将会使得串联和变量器方法更加有效率。 - -并行向量是一个连续[向量]的并行副本,因此两者之间的转换需要一定的时间。 - -### 并行范围(Parallel Range) - -一个ParRange表示的是一个有序的等差整数数列。一个并行范围(parallel range)的创建方法和一个顺序范围的创建类似。 - - scala> 1 to 3 par - res0: scala.collection.parallel.immutable.ParRange = ParRange(1, 2, 3) - - scala> 15 to 5 by -2 par - res1: scala.collection.parallel.immutable.ParRange = ParRange(15, 13, 11, 9, 7, 5) - -正如顺序范围有没有创建者(builders),平行的范围(parallel ranges)有没有组合者(combiners)。映射一个并行范围的元素来产生一个并行向量。顺序范围(sequential ranges)和并行范围(parallel ranges)能够被高效的通过seq和par方法进行转换。 - -### 并行哈希表(Parallel Hash Tables) - -并行哈希表存储在底层数组的元素,并将它们放置在由各自元素的哈希码的位置。并行不变的哈希集(set)([mutable.ParHashSet](http://www.scala-lang.org/api/2.10.0/scala/collection/parallel/mutable/ParHashSet.html))和并行不变的哈希映射([mutable.ParHashMap](http://www.scala-lang.org/api/2.10.0/scala/collection/parallel/mutable/ParHashMap.html)) 是基于哈希表的。 - - scala> val phs = scala.collection.parallel.mutable.ParHashSet(1 until 2000: _*) - phs: scala.collection.parallel.mutable.ParHashSet[Int] = ParHashSet(18, 327, 736, 1045, 773, 1082,... - - scala> phs map (x => x * x) - res0: scala.collection.parallel.mutable.ParHashSet[Int] = ParHashSet(2181529, 2446096, 99225, 2585664,... - -并行哈希表组合器元素排序是依据他们的哈希码前缀在桶(buckets)中进行的。它们通过简单地连接这些桶在一起。一旦最后的哈希表被构造出来(如:组合结果的方法被调用),基本数组分配和从不同的桶元素复制在平行于哈希表的数组不同的相邻节段。 - -连续的哈希映射和散列集合可以被转换成并行的变量使用par方法。并行哈希表内在上要求一个映射的大小在不同块的哈希表元素的数目。这意味着,一个连续的哈希表转换为并行哈希表的第一时间,表被遍历并且size map被创建,因此,第一次调用par方法的时间是和元素个数成线性关系的。进一步修改的哈希表的映射大小保持状态,所以以后的转换使用PAR和序列具有常数的复杂性。使用哈希表的usesizemap方法,映射大小的维护可以开启和关闭。重要的是,在连续的哈希表的修改是在并行哈希表可见,反之亦然。 - -### 并行散列Tries(Parallel Hash Tries) - -并行hash tries是不可变(immutable)hash tries的并行版本,这种结果可以用来高效的维护不可变集合(immutable set)和不可变关联数组(immutable map)。他们都支持类[immutable.ParHashSet](http://www.scala-lang.org/api/2.10.0/scala/collection/parallel/immutable/ParHashSet.html)和[immutable.ParHashMap](http://www.scala-lang.org/api/2.10.0/scala/collection/parallel/immutable/ParHashMap.html)。 - - scala> val phs = scala.collection.parallel.immutable.ParHashSet(1 until 1000: _*) - phs: scala.collection.parallel.immutable.ParHashSet[Int] = ParSet(645, 892, 69, 809, 629, 365, 138, 760, 101, 479,... - - scala> phs map { x => x * x } sum - res0: Int = 332833500 - -类似于平行散列哈希表,parallel hash trie在桶(buckets)里预排序这些元素和根据不同的处理器分配不同的桶(buckets) parallel hash trie的结果,这些构建subtrie是独立的。 - -并行散列试图可以来回转换的,顺序散列试图利用序列和时间常数的方法。 - -### 并行并发tries(Parallel Concurrent Tries) - -[ concurrent.triemap ](http://www.scala-lang.org/api/2.10.0/scala/collection/concurrent/TrieMap.html)是竞争对手的线程安全的地图,而[ mutable.partriemap ](http://www.scala-lang.org/api/2.10.0/scala/collection/parallel/mutable/ParTrieMap.html) 是他的并行副本。如果这个数据结构在遍历的过程中被修改了,大多数竞争对手的数据结构不能确保一致遍历,尝试确保在下一次迭代中更新是可见的。这意味着,你可以在尝试遍历的时候改变这些一致性,如下例子所示输出1到99的平方根。 - - scala> val numbers = scala.collection.parallel.mutable.ParTrieMap((1 until 100) zip (1 until 100): _*) map { case (k, v) => (k.toDouble, v.toDouble) } - numbers: scala.collection.parallel.mutable.ParTrieMap[Double,Double] = ParTrieMap(0.0 -> 0.0, 42.0 -> 42.0, 70.0 -> 70.0, 2.0 -> 2.0,... - - scala> while (numbers.nonEmpty) { - | numbers foreach { case (num, sqrt) => - | val nsqrt = 0.5 * (sqrt + num / sqrt) - | numbers(num) = nsqrt - | if (math.abs(nsqrt - sqrt) < 0.01) { - | println(num, nsqrt) - | numbers.remove(num) - | } - | } - | } - (1.0,1.0) - (2.0,1.4142156862745097) - (7.0,2.64576704419029) - (4.0,2.0000000929222947) - ... - -合成器是引擎盖下triemaps实施——因为这是一个并行数据结构,只有一个组合构建整个变压器的方法调用和所有处理器共享。 - -与所有的并行可变容器(collections),Triemaps和并行partriemaps通过调用序列或PAR方法得到了相同的存储支持,所以修改在一个在其他可见。转换发生在固定的时间。 - -### 性能特征 - -顺序类型(sequence types)的性能特点: - -| | head | tail | apply | update| prepend | append | insert | -| -------- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | -| `ParArray` | C | L | C | C | L | L | L | -| `ParVector` | eC | eC | eC | eC | eC | eC | - | -| `ParRange` | C | C | C | - | - | - | - | - -性能特征集(set)和映射类型: - -| | lookup | add | remove | -| -------- | ---- | ---- | ---- | -| **immutable** | | | | -| `ParHashSet`/`ParHashMap`| eC | eC | eC | -| **mutable** | | | | -| `ParHashSet`/`ParHashMap`| C | C | C | -| `ParTrieMap` | eC | eC | eC | - - -####Key - -上述两个表的条目,说明如下: - -|C |该操作需要的时间常数(快)| -|-----|------------------------| -|eC|该操作需要有效的常数时间,但这可能依赖于一些假设,如一个向量或分配哈希键的最大长度。| -|aC|该操作需要分期常量时间。一些调用的操作可能需要更长的时间,但是如果很多操作都是在固定的时间就可以使用每个操作的平均了。| -|Log|该操作需要collection大小的对数时间比例。| -|L|这个操作是线性的,需要collection大小的时间比例。| -|-|这个操作是不被支持的。| -第一个表处理序列类型--可变和不可变--使用以下操作:| - -| head | 选择序列的第一个元素。| -|-----|------------------------| -|tail|产生一个由除了第一个元素的所有元素组成的新的序列。| -|apply|标引,索引| -|update|对于不可变(immutable sequence)执行函数式更新(functional update),对于可变数据执行带有副作用(side effect)的更新。| -|prepend|在序列的前面添加一个元素。 针对不可变的序列,这将产生一个新的序列,针对可变序列这将修改已经存在的序列。| -|append|在序列结尾添加一个元素。针对不可变的序列,这将产生一个新的序列,针对可变序列这将修改已经存在的序列。| -|insert|在序列中的任意位置插入一个元素。这是可变序列(mutable sequence)唯一支持的操作。| - -第二个表处理可变和不可变集合(set)与关联数组(map)使用以下操作: - -|lookup| 测试一个元素是否包含在集合,或选择一个键所关联的值。| -|-----|------------------------| -|add | 新增一个元素到集合(set)或者键/值匹配映射。| -|remove|从集合(set)或者关键映射中移除元素。| -|min|集合(set)中最小的元素,或者关联数组(map)中的最小的键(key)。| diff --git a/zh-cn/overviews/parallel-collections/configuration.md b/zh-cn/overviews/parallel-collections/configuration.md deleted file mode 100644 index 6d6e4885dd..0000000000 --- a/zh-cn/overviews/parallel-collections/configuration.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -layout: overview-large -title: 配置并行集合 - -discourse: false - -partof: parallel-collections -num: 7 -language: zh-cn ---- - - -### 任务支持 - -并行集合是以操作调度的方式建模的。每一个并行集合都有一个任务支持对象作为参数,该对象负责处理器任务的调度和负载均衡。 - -任务支持对象内部有一个线程池实例的引用,并且决定着任务细分成更小任务的方式和时机。更多关于这方面的实现细节,请参考技术手册 [[1](http://infoscience.epfl.ch/record/165523/files/techrep.pdf)]。 - -并行集合的任务支持现在已经有一些可用的实现。ForkJoinTaskSupport内部使用fork-join池,并被默认用与JVM1.6以及更高的版本。ThreadPoolTaskSupport 效率更低,是JVM1.5和不支持fork-join池的JVM的回退。ExecutionContextTaskSupport 使用在scala.concurrent中默认的执行上下文实现,并且它重用在scala.concurrent使用的线程池(根据JVM版本,可能是fork join 池或线程池执行器)。执行上下文的任务支持被默认地设置到每个并行集合中,所以并行集合重用相同的fork-join池。 - -以下是一种改变并行集合的任务支持的方法: - - scala> import scala.collection.parallel._ - import scala.collection.parallel._ - - scala> val pc = mutable.ParArray(1, 2, 3) - pc: scala.collection.parallel.mutable.ParArray[Int] = ParArray(1, 2, 3) - - scala> pc.tasksupport = new ForkJoinTaskSupport(new scala.concurrent.forkjoin.ForkJoinPool(2)) - pc.tasksupport: scala.collection.parallel.TaskSupport = scala.collection.parallel.ForkJoinTaskSupport@4a5d484a - - scala> pc map { _ + 1 } - res0: scala.collection.parallel.mutable.ParArray[Int] = ParArray(2, 3, 4) - -以上代码配置并行集合使用parallelism 级别为2的fork-join池。配置并行集合使用线程池执行器: - - scala> pc.tasksupport = new ThreadPoolTaskSupport() - pc.tasksupport: scala.collection.parallel.TaskSupport = scala.collection.parallel.ThreadPoolTaskSupport@1d914a39 - - scala> pc map { _ + 1 } - res1: scala.collection.parallel.mutable.ParArray[Int] = ParArray(2, 3, 4) - -当一个并行集合被序列化,它的任务支持域免于序列化。当对一个并行集合反序列化时,任务支持域被设置为默认值——执行上下文的任务支持。 - -通过继承TaskSupport 特征并实现下列方法,可实现一个典型的任务支持: - - def execute[R, Tp](task: Task[R, Tp]): () => R - - def executeAndWaitResult[R, Tp](task: Task[R, Tp]): R - - def parallelismLevel: Int - -execute方法异步调度任务并且返回等待计算结果的未来状态。executeAndWait 方法功能一样,但只当任务完成时才返回。parallelismLevel 简单地返回任务支持用于调度任务的处理器目标数量。 - -**引用** - -1. [On a Generic Parallel Collection Framework, June 2011](http://infoscience.epfl.ch/record/165523/files/techrep.pdf) - diff --git a/zh-cn/overviews/parallel-collections/conversions.md b/zh-cn/overviews/parallel-collections/conversions.md deleted file mode 100644 index 7cd62142d5..0000000000 --- a/zh-cn/overviews/parallel-collections/conversions.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -layout: overview-large -title: 并行容器的转换 - -discourse: false - -partof: parallel-collections -num: 3 -language: zh-cn ---- - -### 顺序容器和并行容器之间的转换 - -每个顺序容器都可以使用par方法转换成它的并行形式。某些顺序容器具有直接的并行副本。对于这些容器,转换是非常高效的(它所需的时间是个常量),因为顺序容器和并行容器具有相同的数据结构上的表现形式(其中一个例外是可变hash map和hash set,在第一次调用par进行转换时,消耗略高,但以后对par的调用将只消耗常数时间)。要注意的是,对于可变容器,如果顺序容器和并行容器共享底层数据结构,那么对顺序容器的修改也会在他的并行副本中可见。 - -| 顺序 |并行 | -|------|-----| -|可变性(mutable)| | -|Array|ParArray| -|HashMap| ParHashMap| -|HashSet| ParHashSet| -|TrieMap| ParTrieMap| -|不可变性(immutable)| | -|Vector | ParVector| -|Range | ParRange| -|HashMap | ParHashMap| -|HashSet | ParHashSet| - -其他容器,如列表(list),队列(queue)及流(stream),从“元素必须逐个访问”这个意义上来讲,天生就是顺序容器。它们可以通过将元素拷贝到类似的并行容器的方式转换成其并行形式。例如,函数式列表可以转换成标准的非可变并行序列,即并行向量。 - -所有的并行容器都可以用 seq 方法转换成其顺序形式。从并行容器转换成顺序容器的操作总是很高效(耗费常数时间)。在可变并行容器上调用seq方法,会生成一个顺序容器,并使用相同的存储空间。对其中一个容器的更新会同时反映到另一个容器上。 - -### 不同类型容器之间的转换 - -通过顺序容器和并行容器之间的正交变换,容器可以在不同容器类型之间相互转换。例如,调用toSeq会将顺序集合转变成顺序序列,而在并行集合上调用toSeq,会将它转换成一个并行序列。基本规律是:如果存在一个X的并行版本,那么toX方法会将容器转换成ParX容器。 - -下面是对所有转换方法的总结: - -|方法 | 返回值类型 | -|----------|-----------| -|toArray | Array | -|toList | List | -|toIndexedSeq | IndexedSeq | -|toStream | Stream | -|toIterator | Iterator | -|toBuffer | Buffer | -|toTraversable | GenTraverable | -|toIterable | ParIterable | -|toSeq | ParSeq | -|toSet | ParSet | -|toMap | ParMap | - diff --git a/zh-cn/overviews/parallel-collections/ctries.md b/zh-cn/overviews/parallel-collections/ctries.md deleted file mode 100644 index dbc1365ca7..0000000000 --- a/zh-cn/overviews/parallel-collections/ctries.md +++ /dev/null @@ -1,111 +0,0 @@ ---- -layout: overview-large -title: 并发字典树 - -discourse: false - -partof: parallel-collections -num: 4 -language: zh-cn ---- - - -对于大多数并发数据结构,如果在遍历中途数据结构发生改变,都不保证遍历的一致性。实际上,大多数可变容器也都是这样。并发字典树允许在遍历时修改Trie自身,从这个意义上来讲是特例。修改只影响后续遍历。顺序并发字典树及其对应的并行字典树都是这样处理。它们之间唯一的区别是前者是顺序遍历,而后者是并行遍历。 - -这是一个很好的性质,可以让一些算法实现起来更加的容易。常见的是迭代处理数据集元素的一些算法。在这样种场景下,不同的元素需要不同次数的迭代以进行处理。 - -下面的例子用于计算一组数据的平方根。每次循环反复更新平方根值。数据的平方根收敛,就把它从映射中删除。 - - case class Entry(num: Double) { - var sqrt = num - } - - val length = 50000 - - // 准备链表 - val entries = (1 until length) map { num => Entry(num.toDouble) } - val results = ParTrieMap() - for (e <- entries) results += ((e.num, e)) - - // 计算平方根 - while (results.nonEmpty) { - for ((num, e) <- results) { - val nsqrt = 0.5 * (e.sqrt + e.num / e.sqrt) - if (math.abs(nsqrt - e.sqrt) < 0.01) { - results.remove(num) - } else e.sqrt = nsqrt - } - } - -注意,在上面的计算平方根的巴比伦算法([3](http://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method))中,某些数据会比别的数据收敛的更快。基于这个因素,我们希望能够尽快把他们从结果中剔除,只遍历那些真正需要耗时处理的元素。 - -另一个例子是广度优先搜索算法,该算法迭代地在末端节点遍历,直到找到通往目标的路径,或遍历完所有周围节点。一个二维地图上的节点定义为Int的元组。map定义为二维布尔值数组,用来表示各个位置是否已经到达。然后,定义2个并行字典树映射,open和closed。其中,映射open保存接着需要被遍历的末端节点。映射closed保存所有已经被遍历过的节点。映射open使用恰当节点来初始化,用以从地图的一角开始搜索,并找到通往地图中心的路径。随后,并行地对映射open中的所有节点迭代遍历,直到没有节点可以遍历。每次一个节点被遍历时,将它从映射open中移除,并放置在映射closed中。一旦执行完成,输出从目标节点到初始节点的路径。 -(译者注:如扫雷,不断判断当前位置(末端节点)上下左右是否为地雷(二维布尔数组),从起始位置逐渐向外扩张。) - - val length = 1000 - - //定义节点类型 - type Node = (Int, Int); - type Parent = (Int, Int); - - //定义节点类型上的操作 - def up(n: Node) = (n._1, n._2 - 1); - def down(n: Node) = (n._1, n._2 + 1); - def left(n: Node) = (n._1 - 1, n._2); - def right(n: Node) = (n._1 + 1, n._2); - - // 创建一个map及一个target - val target = (length / 2, length / 2); - val map = Array.tabulate(length, length)((x, y) => (x % 3) != 0 || (y % 3) != 0 || (x, y) == target) - def onMap(n: Node) = n._1 >= 0 && n._1 < length && n._2 >= 0 && n._2 < length - - //open列表 - 前节点 - // closed 列表 - 已处理的节点 - val open = ParTrieMap[Node, Parent]() - val closed = ParTrieMap[Node, Parent]() - - // 加入一对起始位置 - open((0, 0)) = null - open((length - 1, length - 1)) = null - open((0, length - 1)) = null - open((length - 1, 0)) = null - - // 贪婪广度优先算法路径搜索 - while (open.nonEmpty && !open.contains(target)) { - for ((node, parent) <- open) { - def expand(next: Node) { - if (onMap(next) && map(next._1)(next._2) && !closed.contains(next) && !open.contains(next)) { - open(next) = node - } - } - expand(up(node)) - expand(down(node)) - expand(left(node)) - expand(right(node)) - closed(node) = parent - open.remove(node) - } - } - - // 打印路径 - var pathnode = open(target) - while (closed.contains(pathnode)) { - print(pathnode + "->") - pathnode = closed(pathnode) - } - println() -例如,GitHub上个人生游戏的示例,就是使用Ctries去选择性地模拟人生游戏中当前活跃的机器人([4](https://github.com/axel22/ScalaDays2012-TrieMap))。它还基于Swing实现了模拟的人生游戏的视觉化,以便很直观地观察到调整参数是如何影响执行。 - -并发字典树也支持线性化、无锁、及定时快照操作。这些操作会利用特定时间点上的所有元素来创建新并发 字典树。因此,实际上捕获了特定时间点上的字典树状态。快照操作仅仅为并发字典树生成一个新的根。子序列采用惰性更新的策略,只重建与更新相关的部分,其余部分保持原样。首先,这意味着,由于不需要拷贝元素,自动快照操作资源消耗较少。其次,写时拷贝优化策略只拷贝并发字典树的部分,后续的修改可以横向展开。readOnlySnapshot方法比Snapshot方法效率略高,但它返回的是无法修改的只读的映射。并发字典树也支持线性化,定时清除操作基于快照机制。了解更多关于并发字典树及快照的工作方式,请参阅 ([1](http://infoscience.epfl.ch/record/166908/files/ctries-techreport.pdf)) 和 ([2](http://lampwww.epfl.ch/~prokopec/ctries-snapshot.pdf)). - -并发字典树的迭代器基于快照实现。在迭代器对象被创建之前,会创建一个并发字典树的快照,所以迭代器只在字典树的快照创建时的元素中进行遍历。当然,迭代器使用只读快照。 - -size操作也基于快照。一种直接的实现方式是,size调用仅仅生成一个迭代器(也就是快照),通过遍历来计数。这种方式的效率是和元素数量线性相关的。然而,并发字典树通过缓存其不同部分优化了这个过程,由此size方法的时间复杂度降低到了对数时间。实际上这意味着,调用过一次size方法后,以后对call的调用将只需要最少量的工作。典型例子就是重新计算上次调用size之后被修改了的字典数分支。此外,并行的并发字典树的size计算也是并行的。 - -**引用** - -[缓存感知无锁并发哈希字典树][1](http://infoscience.epfl.ch/record/166908/files/ctries-techreport.pdf) -[具有高效非阻塞快照的并发字典树][2](http://lampwww.epfl.ch/~prokopec/ctries-snapshot.pdf) -[计算平方根的方法][3](http://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method) -[人生游戏模拟程序][4](https://github.com/axel22/ScalaDays2012-TrieMap)(译注:类似大富翁的棋盘游戏) - diff --git a/zh-cn/overviews/parallel-collections/custom-parallel-collections.md b/zh-cn/overviews/parallel-collections/custom-parallel-collections.md deleted file mode 100644 index 50c0d191f3..0000000000 --- a/zh-cn/overviews/parallel-collections/custom-parallel-collections.md +++ /dev/null @@ -1,192 +0,0 @@ ---- -layout: overview-large -title: 创建自定义并行容器 - -discourse: false - -partof: parallel-collections -num: 6 -language: zh-cn ---- - -### 没有组合器的并行容器 - -正如可以定义没有构造器的个性化序列容器一样,我们也可以定义没有组合器的并行容器。没有组合器的结果就是容器的transformer方法(比如,map,flatMap,collect,filter,……)将会默认返回一个标准的容器类型,该类型是在继承树中与并行容器最接近的一个。举个例子,range类没有构造器,因此一个range类的元素映射会生成一个vector。 - -在接下来的例子中,我们定义了一个并行字符串容器。因为字符串在逻辑上是非可变序列,我们让并行字符串继承 immutable.ParSeq[Char]: - - class ParString(val str: String) - extends immutable.ParSeq[Char] { - -接着,我们定义非可变序列必须实现的方法: - - def apply(i: Int) = str.charAt(i) - - def length = str.length - -我们还得定义这个并行容器的序列化副本。这里,我们返回WrappedString类: - - def seq = new collection.immutable.WrappedString(str) - -最后,我们必须为并行字符串容器定义一个splitter。我们给这个splitter起名ParStringSplitter,让它继承一个序列splitter,即,SeqSplitter[Char]: - - def splitter = new ParStringSplitter(str, 0, str.length) - - class ParStringSplitter(private var s: String, private var i: Int, private val ntl: Int) - extends SeqSplitter[Char] { - - final def hasNext = i < ntl - - final def next = { - val r = s.charAt(i) - i += 1 - r - } - -上面的代码中,ntl为字符串的总长,i是当前位置,s是字符串本身。 - -除了next和hasNext方法,并行容器迭代器,或者称它为splitter,还需要序列容器迭代器中的一些其他的方法。首先,他们有一个方法叫做 remaining,它返回这个分割器尚未遍历的元素数量。其次,需要dup方法用于复制当前的分割器。 - - def remaining = ntl - i - - def dup = new ParStringSplitter(s, i, ntl) - -最后,split和psplit方法用于创建splitter,这些splitter 用来遍历当前分割器的元素的子集。split方法,它返回一个分割器的序列,它用来遍历互不相交,互不重叠的分隔器元素子集,其中没有一个是空的。如果当前分割器有1个或更少的元素,然后就返回一个序列的分隔器。psplit方法必须返回和指定sizes参数个数一致的分割器序列。如果sizes参数指定元素数量小于当前分配器,然后一个带有额外的分配器就会附加在分配器的尾部。如果sizes参数比在当前分配器的剩余元素大很多,需要更多的元素,它将为每个分配器添加一个空的分配器。最后,调用split或psplit方法使得当前分配器无效。 - - def split = { - val rem = remaining - if (rem >= 2) psplit(rem / 2, rem - rem / 2) - else Seq(this) - } - - def psplit(sizes: Int*): Seq[ParStringSplitter] = { - val splitted = new ArrayBuffer[ParStringSplitter] - for (sz <- sizes) { - val next = (i + sz) min ntl - splitted += new ParStringSplitter(s, i, next) - i = next - } - if (remaining > 0) splitted += new ParStringSplitter(s, i, ntl) - splitted - } - } - } - -综上所述,split方法是通过psplit来实现的,它常用于并行序列计算中。由于不需要psplit,并行映射、集合、迭代器的实现,通常就更容易些。 - -因此,我们得到了一个并行字符串类。它唯一的缺点是,调用类似filter等转换方法不是生成并行串,而是生成并行向量,这可能是个折中的选择 - filter方法如果生成串而非向量,代价也许是昂贵的。 - -### 带组合子的并行容器 - -假设我们想要从并行字符串中过滤掉某些字符,例如,去除其中的逗号。如上所述,调用filter方法会生成一个并行向量,但是我们需要得到的是一个并行串(因为API中的某些接口可能需要一个连续的字符串来作为参数)。 - -为了避免这种情况的发生,我们需要为并行串容器写一个组合子。同时,我们也将继承ParSeqLike trait,以确保filter的返回类型是更具体的类型 - - ParString而不是ParSeq[Char]。ParSeqLike的第三个参数,用于指定并行容器对应的序列的类型(这点和序列化的 *Like trait 不同,它们只有两个类型参数)。 - - class ParString(val str: String) - extends immutable.ParSeq[Char] - with ParSeqLike[Char, ParString, collection.immutable.WrappedString] - -所有的方法仍然和以前一样,只是我们会增加一个额外的protected方法newCombiner,它在内部被filter方法调用。 - - protected[this] override def newCombiner: Combiner[Char, ParString] = new ParStringCombiner - -接下来我们定义ParStringCombiner类。组合子是builders的子类型,它们引进了名叫combine的方法,该方法接收另一个组合子作为参数,并返回一个新的组合子,该新的组合子包含了当前组合子和参数中的组合子中的所有元素。当前组合子和参数中的组合子在调用combine方法之后将会失效。如果参数中的组合子和当前的组合子是同一个对象,那么combine方法仅仅返回当前的组合子。该方法通常情况下是高效的,最坏情况下时间复杂度为元素个数的对数,因为它在一次并行计算中会被多次调用。 - -我们的ParStringCombiner会在内部维护一个字符串生成器的序列。它通过在序列的最后一个字符串builder中增加一个元素的方式,来实现+=方法。并且通过串联当前和参数中的组合子的串builder列表来实现combine方法。result方法,在并行计算结束后被调用,它会通过将所有字符串生成器添加在一起来产生一个并行串。这样一来,元素只在末端被复制一次,避免了每调一次combine方法就被复制一次。理想情况下,我们想并行化这一进程,并在它们并行时候进行复制(并行数组正在被这样做),但没有办法检测到的字符串的内部表现,这是我们能做的最好的 - 我们不得不忍受这种顺序化的瓶颈。 - - private class ParStringCombiner extends Combiner[Char, ParString] { - var sz = 0 - val chunks = new ArrayBuffer[StringBuilder] += new StringBuilder - var lastc = chunks.last - - def size: Int = sz - - def +=(elem: Char): this.type = { - lastc += elem - sz += 1 - this - } - - def clear = { - chunks.clear - chunks += new StringBuilder - lastc = chunks.last - sz = 0 - } - - def result: ParString = { - val rsb = new StringBuilder - for (sb <- chunks) rsb.append(sb) - new ParString(rsb.toString) - } - - def combine[U <: Char, NewTo >: ParString](other: Combiner[U, NewTo]) = if (other eq this) this else { - val that = other.asInstanceOf[ParStringCombiner] - sz += that.sz - chunks ++= that.chunks - lastc = chunks.last - this - } - } - -### 大体上我如何来实现一个组合子? - -没有现成的秘诀——它的实现依赖于手头上的数据结构,通常在实现上也需要一些创造性。但是,有几种方法经常被采用: - -1. 连接和合并。一些数据结构在这些操作上有高效的实现(经常是对数级的)。如果手头的容器可以由这样的一些数据结构支撑,那么它们的组合子就可以是容器本身。 Finger trees,ropes和各种堆尤其适合使用这种方法。 - -2. 两阶段赋值,是在并行数组和并行哈希表中采用的方法,它假设元素子集可以被高效的划分到连续的排序桶中,这样最终的数据结构就可以并行的构建。第一阶段,不同的处理器独立的占据这些桶,并把这些桶连接在一起。第二阶段,数据结构被分配,不同的处理器使用不相交的桶中的元素并行地占据部分数据结构。必须注意的是,各处理器修改的部分不能有交集,否则,可能会产生微妙的并发错误。正如在前面的篇幅中介绍的,这种方法很容易应用到随机存取序列。 - -3. 一个并发的数据结构。尽管后两种方法实际上不需要数据结构本身有同步原语,它们假定数据结构能够被不修改相同内存的不同处理器,以并发的方式建立。存在大量的并发数据结构,它们可以被多个处理器安全的修改——例如,并发skip list,并发哈希表,split-ordered list,并发 avl树等等。需要注意的是,并发的数据结构应该提供水平扩展的插入方法。对于并发并行容器,组合器可以是容器本身,并且,完成一个并行操作的所有的处理器会共享一个单独的组合器实例。 - -### 使用容器框架整合 - -ParString类还没有完成。虽然我们已经实现了一个自定义的组合子,它将会被类似filter,partition,takeWhile,或者span等方式使用,但是大部分transformer方法都需要一个隐式的CanBuildFrom出现(Scala collections guide有详细的解释)。为了让ParString可能,并且完全的整合到容器框架中,我们需要为其掺入额外的一个叫做GenericParTemplate的trait,并且定义ParString的伴生对象。 - - class ParString(val str: String) - extends immutable.ParSeq[Char] - with GenericParTemplate[Char, ParString] - with ParSeqLike[Char, ParString, collection.immutable.WrappedString] { - - def companion = ParString -在这个伴生对象内部,我们隐式定义了CanBuildFrom。 - - object ParString { - implicit def canBuildFrom: CanCombineFrom[ParString, Char, ParString] = - new CanCombinerFrom[ParString, Char, ParString] { - def apply(from: ParString) = newCombiner - def apply() = newCombiner - } - - def newBuilder: Combiner[Char, ParString] = newCombiner - - def newCombiner: Combiner[Char, ParString] = new ParStringCombiner - - def apply(elems: Char*): ParString = { - val cb = newCombiner - cb ++= elems - cb.result - } - } - -### 进一步定制——并发和其他容器 - -实现一个并发容器(与并行容器不同,并发容器是像collection.concurrent.TrieMap一样可以被并发修改的)并不总是简单明了的。尤其是组合器,经常需要仔细想想。到目前为止,在大多数描述的并行容器中,组合器都使用两步评估。第一步元素被不同的处理器加入到组合器中,组合器被合并在一起。第二步,在所有元素完成处理后,结果容器就被创建。 - -组合器的另一种方式是把结果容器作为元素来构建。前提是:容器是线程安全的——组合器必须允许并发元素插入。这样的话,一个组合器就可以被所有处理器共享。 - -为了使一个并发容器并行化,它的组合器必须重写canBeShared方法以返回真。这会保证当一个并行操作被调用,只有一个组合器被创建。然后,+=方法必须是线程安全的。最后,如果当前的组合器和参数组合器是相同的,combine方法仍然返回当前的组合器,要不然会自动抛出异常。 - -为了获得更好的负载均衡,Splitter被分割成更小的splitter。默认情况下,remaining方法返回的信息被用来决定何时停止分割splitter。对于一些容器而言,调用remaining方法是有花销的,一些其他的方法应该被使用来决定何时分割splitter。在这种情况下,需要重写splitter的shouldSplitFurther方法。 - -如果剩余元素的数量比容器大小除以8倍并行级别更大,默认的实现将拆分splitter。 - - def shouldSplitFurther[S](coll: ParIterable[S], parallelismLevel: Int) = - remaining > thresholdFromSize(coll.size, parallelismLevel) - -同样的,一个splitter可以持有一个计数器,来计算splitter被分割的次数。并且,如果split次数超过3+log(并行级别),shouldSplitFurther将直接返回true。这避免了必须去调用remaining方法。 - -此外,对于一个指定的容器如果调用remaining方法开销不低(比如,他需要评估容器中元素的数量),那么在splitter中的方法isRemainingCheap就应该被重写并返回false。 - -最后,若果在splitter中的remaining方法实现起来极其麻烦,你可以重写容器中的isStrictSplitterCollection方法,并返回false。虽然这些容器将不能够执行一些严格依赖splitter的方法,比如,在remaining方法中返回一个正确的值。重点是,这并不影响 for-comprehension 中使用的方法。 - diff --git a/zh-cn/overviews/parallel-collections/overview.md b/zh-cn/overviews/parallel-collections/overview.md deleted file mode 100644 index d91d4c89cc..0000000000 --- a/zh-cn/overviews/parallel-collections/overview.md +++ /dev/null @@ -1,174 +0,0 @@ ---- -layout: overview-large -title: 概述 - -discourse: false - -partof: parallel-collections -num: 1 -language: zh-cn ---- - -**Aleksandar Prokopec, Heather Miller** - -## 动机 - -近年来,处理器厂家在单核向多核架构迁移的过程中,学术界和工业界都认为当红的并行编程仍是一个艰巨的挑战。 - -在Scala标准库中包含的并行容器通过免去并行化的底层细节,以方便用户并行编程,同时为他们提供一个熟悉而简单的高层抽象。希望隐藏在抽象容器之后的隐式并行性将带来可靠的并行执行,并进一步靠近主流开发者的工作流程。 - -原理其实很简单-容器是抽象编程中被广泛熟识和经常使用的类,并且考虑到他们的规则性,容器能够使程序高效且透明的并行化。通过使用户能够在并行操作有序容器的同时改变容器序列,Scala的并行容器在使代码能够更容易的并行化方面做了很大改进。 - -下面是个序列的例子,这里我们在某个大的容器上执行一个一元运算: - - val list = (1 to 10000).toList - list.map(_ + 42) -为了并行的执行同样的操作,我们只需要简单的调用序列容器(列表)的par方法。这样,我们就可以像通常使用序列容器的方式那样来使用并行容器。上面的例子可以通过执行下面的来并行化: - - list.par.map(_ + 42) -Scala的并行容器库设计创意般的同Scala的(序列)容器库(从2.8引入)密切的整合在一起。在Scala(序列)容器库中,它提供了大量重要的数据结构对应的东西,包括: - -- ParArray -- ParVector -- mutable.ParHashMap -- mutable.ParHashSet -- immutable.ParHashMap -- immutable.ParHashSet -- ParRange -- ParTrieMap (collection.concurrent.TrieMaps are new in 2.10) - -在通常的架构之外,Scala的并行容器库也同序列容器库(sequential collections)一样具有可扩展性。这就是说,像通常的序列容器那样,用户可以整合他们自己的容器类型,并且自动继承所有的可用在别的并行容器(在标准库里的)的预定义(并行)操作。 - -### 下边是一些例子 - -为了说明并行容器的通用性和实用性,我们提供几个简单的示例用法,所有这些都被透明的并行执行。 - -提示:随后某些例子操作小的容器,实际中不推荐这样做。他们提供的示例仅为演示之用。一般来说,当容器的尺寸比较巨大(通常为成千上万个元素时)时,加速才会比较明显。(关于并行容器的尺寸和性能更多的信息,请参见) - -#### map - -使用parallel map来把一个字符串容器变为全大写字母: - - scala> val lastNames = List("Smith","Jones","Frankenstein","Bach","Jackson","Rodin").par - astNames: scala.collection.parallel.immutable.ParSeq[String] = ParVector(Smith, Jones, Frankenstein, Bach, Jackson, Rodin) - - scala> lastNames.map(_.toUpperCase) - res0: scala.collection.parallel.immutable.ParSeq[String] = ParVector(SMITH, JONES, FRANKENSTEIN, BACH, JACKSON, RODIN) - -#### fold - -通过fold计算一个ParArray中所有数的累加值: - - scala> val parArray = (1 to 10000).toArray.par - parArray: scala.collection.parallel.mutable.ParArray[Int] = ParArray(1, 2, 3, ... - - scala> parArray.fold(0)(_ + _) - res0: Int = 50005000 - -#### filter - -使用并行过滤器来选择按字母顺序排在“K”之后的姓名。(译者注:这个例子有点问题,应该是排在“J”之后的) - - scala> val lastNames = List("Smith","Jones","Frankenstein","Bach","Jackson","Rodin").par - astNames: scala.collection.parallel.immutable.ParSeq[String] = ParVector(Smith, Jones, Frankenstein, Bach, Jackson, Rodin) - - scala> lastNames.filter(_.head >= 'J') - res0: scala.collection.parallel.immutable.ParSeq[String] = ParVector(Smith, Jones, Jackson, Rodin) - -## 创建一个并行容器 - -并行容器(parallel collections)同顺序容器(sequential collections)完全一样的被使用,唯一的不同是要怎样去获得一个并行容器。 - -通常,我们有两种方法来创建一个并行容器: - -第一种,通过使用new关键字和一个适当的import语句: - - import scala.collection.parallel.immutable.ParVector - val pv = new ParVector[Int] - -第二种,通过从一个顺序容器转换得来: - - val pv = Vector(1,2,3,4,5,6,7,8,9).par - -这里需要着重强调的是这些转换方法:通过调用顺序容器(sequential collections)的par方法,顺序容器(sequential collections)可以被转换为并行容器;通过调用并行容器的seq方法,并行容器可以被转换为顺序容器。 - -注意:那些天生就有序的容器(意思是元素必须一个接一个的访问),像lists,queues和streams,通过拷贝元素到类似的并行容器中被转换为它们的并行对应物。例如List--被转换为一个标准的不可变的并行序列中,就是ParVector。当然,其他容器类型不需要这些拷贝的开销,比如:Array,Vector,HashMap等等。 - -关于并行容器的转换的更多信息请参见 [conversions](http://docs.scala-lang.org/overviews/parallel-collections/conversions.html) 和 [concrete parallel collections classes](http://docs.scala-lang.org/overviews/parallel-collections/concrete-parallel-collections.html)章节 - -## 语义(semantic) - -尽管并行容器的抽象概念很像通常的顺序容器,重要的是要注意它的语义的不同,特别是关于副作用(side-effects)和无关操作(non-associative operations)。 - -为了看看这是怎样的情况,首先,我们设想操作是如何被并行的执行。从概念上讲,Scala的并行容器框架在并行容器上通过递归的“分解"给定的容器来并行化一个操作,在并行中,容器的每个部分应用一个操作,然后“重组”所有这些并行执行的结果。 - -这些并发和并行容器的“乱序”语义导致以下两个影响: - -1. 副作用操作可能导致结果的不确定性 -2. 非关联(non-associative)操作导致不确定性 - -### 副作用操作 - -为了保持确定性,考虑到并行容器框架的并发执行的语义,一般应该避免执行那些在容器上引起副作用的操作。一个简单的例子就是使用访问器方法,像在 foreach 之外来增加一个 var 定义然后传递给foreach。 - - scala> var sum = 0 - sum: Int = 0 - - scala> val list = (1 to 1000).toList.par - list: scala.collection.parallel.immutable.ParSeq[Int] = ParVector(1, 2, 3,… - - scala> list.foreach(sum += _); sum - res01: Int = 467766 - - scala> var sum = 0 - sum: Int = 0 - - scala> list.foreach(sum += _); sum - res02: Int = 457073 - - scala> var sum = 0 - sum: Int = 0 - - scala> list.foreach(sum += _); sum - res03: Int = 468520 - -从上述例子我们可以看到虽然每次 sum 都被初始化为0,在list的foreach每次调用之后,sum都得到不同的值。这个不确定的源头就是数据竞争 -- 同时读/写同一个可变变量(mutable variable)。 - -在上面这个例子中,可能同时有两个线程在读取同一个sum的值,某些操作花了些时间后,它们又试图写一个新的值到sum中,可能的结果就是某个有用的值被覆盖了(因此丢失了),如下表所示: - - 线程A: 读取sum的值, sum = 0 sum的值: 0 - 线程B: 读取sum的值, sum = 0 sum的值: 0 - 线程A: sum 加上760, 写 sum = 760 sum的值: 760 - 线程B: sum 加上12, 写 sum = 12 sum的值: 12 - -上面的示例演示了一个场景:两个线程读相同的值:0。在这种情况下,线程A读0并且累计它的元素:0+760,线程B,累计0和它的元素:0+12。在各自计算了和之后,它们各自把计算结果写入到sum中。从线程A到线程B,线程A写入后,马上被线程B写入的值覆盖了,值760就完全被覆盖了(因此丢失了)。 - -### 非关联(non-associative)操作 - -对于”乱序“语义,为了避免不确定性,也必须注意只执行相关的操作。这就是说,给定一个并行容器:pcoll,我们应该确保什么时候调用一个pcoll的高阶函数,例如:pcoll.reduce(func),被应用到pcoll元素的函数顺序是任意的。一个简单但明显不可结合(non-associative)例子是减法运算: - - scala> val list = (1 to 1000).toList.par - list: scala.collection.parallel.immutable.ParSeq[Int] = ParVector(1, 2, 3,… - - scala> list.reduce(_-_) - res01: Int = -228888 - - scala> list.reduce(_-_) - res02: Int = -61000 - - scala> list.reduce(_-_) - res03: Int = -331818 - -在上面这个例子中,我们对 ParVector[Int]调用 reduce 函数,并给他 _-_ 参数(简单的两个非命名元素),从第二个减去第一个。因为并行容器(parallel collections)框架创建线程来在容器的不同部分执行reduce(-),而由于执行顺序的不确定性,两次应用reduce(-)在并行容器上很可能会得到不同的结果。 - -注意:通常人们认为,像不可结合(non-associative)作,不可交换(non-commutative)操作传递给并行容器的高阶函数同样导致非确定的行为。但和不可结合是不一样的,一个简单的例子是字符串联合(concatenation),就是一个可结合但不可交换的操作: - - scala> val strings = List("abc","def","ghi","jk","lmnop","qrs","tuv","wx","yz").par - strings: scala.collection.parallel.immutable.ParSeq[java.lang.String] = ParVector(abc, def, ghi, jk, lmnop, qrs, tuv, wx, yz) - - scala> val alphabet = strings.reduce(_++_) - alphabet: java.lang.String = abcdefghijklmnopqrstuvwxyz - -并行容器的“乱序”语义仅仅意味着操作被执行是没有顺序的(从时间意义上说,就是非顺序的),并不意味着结果的重“组合”也是乱序的(从空间意义上)。恰恰相反,结果一般总是按序组合的 -- 一个并行容器被分成A,B,C三部分,按照这个顺序,将重新再次按照A,B,C的顺序组合。而不是某种其他随意的顺序如B,C,A。 - -关于并行容器在不同的并行容器类型上怎样进行分解和组合操作的更多信息,请参见。 diff --git a/zh-cn/overviews/parallel-collections/performance.md b/zh-cn/overviews/parallel-collections/performance.md deleted file mode 100644 index 45eb1fec8b..0000000000 --- a/zh-cn/overviews/parallel-collections/performance.md +++ /dev/null @@ -1,181 +0,0 @@ ---- -layout: overview-large -title: 测量性能 - -discourse: false - -partof: parallel-collections -num: 8 -language: zh-cn ---- - -### 在JVM上的性能 - -对JVM性能模型的评论常常令人费解,其结论也往往不易理解。由于种种原因,代码也可能不像预期的那样高性能、可扩展。在这里,我们提供了一些示例。 - -其中一个原因是JVM应用程序的编译过程不同于静态编译语言(见[[2](http://www.ibm.com/developerworks/library/j-jtp12214/)])。Java和Scala的编译器将源代码转换为JVM的字节码,做了非常少的优化。大多数现代JVM,运行时,会把字节码转化成相应机器架构的机器代码。这个过程被称为即时编译。由于追求运行速度,所以实时编译的代码优化程度较低。为了避免重新编译,所谓的HotSpot编译器只优化了部分经常被运行的代码。这对于基准程序作者来说,这意味着程序每次运行时的性能都可能不同。在同一个JVM实例中多次执行一段相同的代码(比如一个方法)可能会得到非常不同的性能结果,这取决于这段代码在运行过程中是否被优化。另外,在测量某些代码的执行时间时其中可能包含JIT编译器对代码进行优化的时间,因此可能得到不一致的结果。 - -另一个在JVM上隐藏执行的是内存自动管理。每隔一段时间,程序的运行就被阻塞并且启动垃圾收集器。如果被进行基准测试的程序分配了任何堆内存(大部分JVM程序都会分配),垃圾收集器将会工作,因此可能会影响测量结果。为了缓冲垃圾收集的影响,被测量的程序应该运行多次以便触发多次垃圾回收。 - -性能恶化的常见原因之一是将原始类型作为参数传递给泛型方法时发生的隐式装箱和拆箱。在运行时,原始类型被转换为封装对象,这样它们就可以作为参数传给有泛型类型参数的方法。这会导致额外的空间分配并且运行速度会更慢,也会在堆中产生额外的垃圾。 - -就并行性能而言,一个常见的问题是存储冲突,因为程序员针对对象的内存分配没有做明确的控制。事实上,由于GC的影响,冲突可以发生在应用程序生命期的最后,在对象被移出内存后。在编写基准测试时这种影响需要被考虑到。 - -### 微基准测试的例子 - -有几种方法可以在测试中避免上述影响。首先,目标微基准测试必须被执行足够多次来确保实时编译器将程序编译为机器码并被优化过。这就是所谓的预热阶段。 - -微基准测试本身需要被运行在单独的JVM实例中,以便减少在程序不同部分或不相关的实时编译过程中针对对象分配的垃圾收集所带来的干扰。 - -微基准测试应该跑在会做更多积极优化的服务器版本的HotSpot JVM上。 - -最后,为了减少在基准测试中间发生垃圾回收的可能性,理想的垃圾回收周期应该发生在基准测试之前,并尽可能的推迟下一个垃圾回收周期。 - -scala.testing.Benchmark trait 是在Scala标准库中被预先定义的,并按前面提到的方式设计。下面是一个用于测试并行算法中映射操作的例子: - - import collection.parallel.mutable.ParTrieMap - import collection.parallel.ForkJoinTaskSupport - - object Map extends testing.Benchmark { - val length = sys.props("length").toInt - val par = sys.props("par").toInt - val partrie = ParTrieMap((0 until length) zip (0 until length): _*) - - partrie.tasksupport = new ForkJoinTaskSupport(new scala.concurrent.forkjoin.ForkJoinPool(par)) - - def run = { - partrie map { - kv => kv - } - } - } - -run方法包含了基准测试代码,重复运行时测量执行时间。上面的Map对象扩展了scala.testing.Benchmark trait,同时,参数par为系统的并行度,length为trie中元素数量的长度。 - -在编译上面的程序之后,可以这样运行: - - java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=1 -Dlength=300000 Map 10 - -server参数指定需要使用server类型的虚拟机。cp参数指定了类文件的路径,包含当前文件夹的类文件以及以及scala类库的jar包。参数-Dpar和-Dlength分别对应并行度和元素数量。最后,10意味着基准测试需要在同一个JVM中运行的次数。 - -在i7四核超线程处理器上将par的值设置为1、2、4、8并获得对应的执行时间。 - - Map$ 126 57 56 57 54 54 54 53 53 53 - Map$ 90 99 28 28 26 26 26 26 26 26 - Map$ 201 17 17 16 15 15 16 14 18 15 - Map$ 182 12 13 17 16 14 14 12 12 12 - -我们从上面的结果可以看到运行时间在最初的几次运行中是较高的,但是在代码被优化后时间就缩短了。另外,我们可以看到在这个例子中超线程带来的好处并不明显,从4线程到8线程的结果说明性能只有小幅提升。 - -### 多大的容器才应该使用并发? - -这是一个经常被问到的问题。答案是有些复杂的。 - -collection的大小所对应的实际并发消耗取决于很多因素。部分原因如下: - -- 硬件架构。不同的CPU类型具有不同的性能和可扩展能力。取决于硬件是否为多核或者是否有多个通过主板通信的处理器。 -- JVM的供应商及版本。在运行时不同的虚拟机应用不同的代码优化。它们的内存管理实现不同,同步技术也不同。有些不支持ForkJoinPool,转而使用ThreadPoolExecutor,这会导致更多的开销。 -- 元素负载。用于并行操作的函数或断言决定了每个元素的负载有多大。负载越小,并发运行时用来提高运行速度的元素数量就越高。 -- 特定的容器。例如:ParArray和ParTrieMap的分离器在遍历容器时有不同的速度,这意味着在遍历过程中要有更多的per-element work。 -- 特定的操作。例如:ParVector在转换方法中(比如filter)要比在存取方法中(比如foreach)慢得多。 -- 副作用。当同时修改内存区域或者在foreach、map等语句中使用同步时,就会发生竞争。 -- 内存管理。当分配大量对象时垃圾回收机制就会被触发。GC循环会消耗多长时间取决于新对象的引用如何进行传递。 - -即使单独的来看,对上面的问题进行推断并给出关于容器应有大小的明确答案也是不容易的。为了粗略的说明容器的应有大小,我们给出了一个无副作用的在i7四核处理器(没有使用超线程)和JDK7上运行的并行矢量减(在这个例子中进行的是求和)处理性能的例子: - - import collection.parallel.immutable.ParVector - - object Reduce extends testing.Benchmark { - val length = sys.props("length").toInt - val par = sys.props("par").toInt - val parvector = ParVector((0 until length): _*) - - parvector.tasksupport = new collection.parallel.ForkJoinTaskSupport(new scala.concurrent.forkjoin.ForkJoinPool(par)) - - def run = { - parvector reduce { - (a, b) => a + b - } - } - } - - object ReduceSeq extends testing.Benchmark { - val length = sys.props("length").toInt - val vector = collection.immutable.Vector((0 until length): _*) - - def run = { - vector reduce { - (a, b) => a + b - } - } - - } -首先我们设定在元素数量为250000的情况下运行基准测试,在线程数设置为1、2、4的情况下得到了如下结果: - - java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=1 -Dlength=250000 Reduce 10 10 - Reduce$ 54 24 18 18 18 19 19 18 19 19 - java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=2 -Dlength=250000 Reduce 10 10 - Reduce$ 60 19 17 13 13 13 13 14 12 13 - java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=4 -Dlength=250000 Reduce 10 10 - Reduce$ 62 17 15 14 13 11 11 11 11 9 -然后我们将元素数量降低到120000,使用4个线程来比较序列矢量减运行的时间: - - java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=4 -Dlength=120000 Reduce 10 10 - Reduce$ 54 10 8 8 8 7 8 7 6 5 - java -server -cp .:../../build/pack/lib/scala-library.jar -Dlength=120000 ReduceSeq 10 10 - ReduceSeq$ 31 7 8 8 7 7 7 8 7 8 -在这个例子中,元素数量为120000时看起来正处于阈值附近。 - -在另一个例子中,我们使用mutable.ParHashMap和map方法(一个转换方法),并在同样的环境中运行下面的测试程序: - - import collection.parallel.mutable.ParHashMap - - object Map extends testing.Benchmark { - val length = sys.props("length").toInt - val par = sys.props("par").toInt - val phm = ParHashMap((0 until length) zip (0 until length): _*) - - phm.tasksupport = new collection.parallel.ForkJoinTaskSupport(new scala.concurrent.forkjoin.ForkJoinPool(par)) - - def run = { - phm map { - kv => kv - } - } - } - - object MapSeq extends testing.Benchmark { - val length = sys.props("length").toInt - val hm = collection.mutable.HashMap((0 until length) zip (0 until length): _*) - - def run = { - hm map { - kv => kv - } - } - } -在元素数量为120000、线程数量从1增加至4的时候,我们得到了如下结果: - - java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=1 -Dlength=120000 Map 10 10 - Map$ 187 108 97 96 96 95 95 95 96 95 - java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=2 -Dlength=120000 Map 10 10 - Map$ 138 68 57 56 57 56 56 55 54 55 - java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=4 -Dlength=120000 Map 10 10 - Map$ 124 54 42 40 38 41 40 40 39 39 - -现在,如果我们将元素数量降低到15000来跟序列化哈希映射做比较: - - java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=1 -Dlength=15000 Map 10 10 - Map$ 41 13 10 10 10 9 9 9 10 9 - java -server -cp .:../../build/pack/lib/scala-library.jar -Dpar=2 -Dlength=15000 Map 10 10 - Map$ 48 15 9 8 7 7 6 7 8 6 - java -server -cp .:../../build/pack/lib/scala-library.jar -Dlength=15000 MapSeq 10 10 - MapSeq$ 39 9 9 9 8 9 9 9 9 9 - -对这个容器和操作来说,当元素数量大于15000的时候采用并发是有意义的(通常情况下,对于数组和向量来说使用更少的元素来并行处理hashmap和hashset是可行的但不是必须的)。 - -**引用** - -1. [Anatomy of a flawed microbenchmark,Brian Goetz](http://www.ibm.com/developerworks/java/library/j-jtp02225/index.html) -2. [Dynamic compilation and performance measurement, Brian Goetz](http://www.ibm.com/developerworks/library/j-jtp12214/) - diff --git a/zh-tw/tutorials/scala-for-java-programmers.md b/zh-tw/tutorials/scala-for-java-programmers.md deleted file mode 100755 index 596463df6a..0000000000 --- a/zh-tw/tutorials/scala-for-java-programmers.md +++ /dev/null @@ -1,387 +0,0 @@ ---- -layout: overview -title: 給 Java 程式設計師的 Scala 入門教學 -overview: scala-for-java-programmers - -discourse: false -multilingual-overview: true -language: zh-tw ---- - -Michel Schinz 與 Philipp Haller 著 -Chikei Lee 譯 - -## 介紹 - -此教學將對 Scala 語言以及編譯器做一個簡易介紹。設定的讀者為具有程設經驗且想要看 Scala 功能概要的人。內文假設讀者有著基本、特別是 Java 上的物件導向程設知識。 - -## 第一個例子 - -這邊用標準的 *Hello world* 程式作為第一個例子。雖然它很無趣,可是這讓我們在僅用少量語言特性下演示 Scala 工具。程式如下: - - object HelloWorld { - def main(args: Array[String]) { - println("Hello, world!") - } - } - -Java 程式員應該對這個程式結構感到熟悉:有著一個 `main` 函式,該函式接受一個字串陣列引數,也就是命令列引數;函式內容為呼叫已定義好的函式 `println` 並用 Hello world 字串當引數。 `main` 函式沒有回傳值 (它是程序函式)。因此並不需要宣告回傳型別。 - -Java 程式員不太熟悉的是包著 `main` 函式的 `object` 宣告。這種宣告引入我們一般稱之 *Singleton* 的東西,也就是只有一個實體的類別。所以上面的宣告同時宣告了一個 `HelloWorld` 類別跟一個這類別的實體,也叫做 `HelloWorld`。該實體會在第一次被使用到的時候即時產生。 - -眼尖的讀者可能已經注意到這邊 `main` 函式的宣告沒有帶著 `static`。這是因為 Scala 沒有靜態成員 (函式或資料欄)。Scala 程式員將這成員宣告在單實例物件中,而不是定義靜態成員。 - -### 編譯這例子 - -我們用 Scala 編譯器 `scalac`來編譯這個例子。`scalac` 就像大多數編譯器一樣,它接受原碼檔當引數,並接受額外的選項,然後產生一個或多個物件檔。它產出的物件檔為標準 Java class 檔案。 - -如果我們將上面的程式存成 `HelloWorld.scala` 檔,編譯指令為( `>` 是提示字元,不用打): - - > scalac HelloWorld.scala - -這會在當前目錄產生一些 class 檔案。其中一個會叫做 `HelloWorld.class`,裡面包含著可被 `scala` 直接執行的類別。 - -### 執行範例 - -一旦編譯過後,Scala 程式可以用 `scala` 指令執行。其使用方式非常像執行 Java 程式的 `java` 指令,並且接受同樣選項。上面的範例可以用以下指令來執行並得到我們預期的輸出: - - > scala -classpath . HelloWorld - - Hello, world! - -## 與 Java 互動 - -Scala 的優點之一是它非常容易跟 Java 程式碼溝通。預設匯入所有 `java.lang` 底下之類別,其他類別則需要明確匯入。 - -讓我們看個展示這點的範例。取得當下日期並根據某個特定國家調整成該國格式,如法國。 - -Java 的標準函式庫定義了一些有用的工具類別,如 `Date` 跟 `DateFormat`。因為 Scala 可以無縫的跟 Jav a互動,這邊不需要以 Scala 實作同樣類別-我們只需要匯入對應的Java套件: - - import java.util.{Date, Locale} - import java.text.DateFormat - import java.text.DateFormat._ - - object FrenchDate { - def main(args: Array[String]) { - val now = new Date - val df = getDateInstance(LONG, Locale.FRANCE) - println(df format now) - } - } - -Scala 的匯入陳述式跟 Java 非常像,但更為強大。如第一行,同一個 package 下的多個類別可以用大括號括起來一起導入。另外一個差別是,當要匯入套件或類別下所有名稱時,用下標 (`_`) 而不是星號 (`*`)。這是因為星號在 Scala 是一個合法的識別符號 (如函式名稱)。 - -所以第三行的陳述式導入所有 `DateFormat` 類別的成員。這讓靜態函式 `getDateInstance` 跟靜態資料欄 `LONG` 可直接被使用。 - -在 `main` 函式中我們先創造一個 Java 的 `Date` 類別實體,該實體預設擁有現在的日期。接下來用 `getDateInstance` 函式定義日期格式。最後根據地區化的 `DateFormat` 實體對現在日期設定格式並印出。最後一行展現了一個 Scala 有趣特點。只需要一個引數的函式可以用中綴語法呼叫。就是說,這個表示式 - - df format now - -是比較不詳細版本的這個表示式 - - df.format(now) - -這點也許看起來只是語法上的小細節,但是它有著重要的後果,其中一個將會在下一節做介紹。 - -最後值得一提的是,Scala 可以直接繼承 Java 類別跟實作 Java 介面。 - -## 萬物皆物件 - -Scala 是一個純粹的物件導向語言,這句話的意思是說,*所有東西*都是物件,包括數字、函式。因為 Java 將基本型別 (如 `boolean` 與 `int` ) 跟參照型別分開,而且沒有辦法像操作變數一樣操作函式,從這角度來看 Scala 跟 Java 是不同的。 - -### 數字是物件 - -因為數字是物件,它們也有函式。事實上,一個像底下的算數表示式: - - 1 + 2 * 3 / x - -只有使用函式呼叫,因為像前一節一樣,該式等價於 - - (1).+(((2).*(3))./(x)) - -這也表示著 `+`、`*` 之類的在 Scala 裡是合法的識別符號。 - -因為Scala的詞法分析器對於符號採用最長匹配,在第二版的表示式當中,那些括號是必要的。也就是說分析器會把這個表示式: - - 1.+(2) - -拆成 `1.`、`+`、`2` 這三個符號。會這樣拆分是因為 `1.` 既是合法匹配同時又比 `1` 長。 `1.` 會被解釋成字面常數 `1.0`,使得它被視為 `Double` 而不是 `Int`。把表示式寫成: - - (1).+(2) - -可以避免 `1` 被解釋成 `Double`。 - -### 函式是物件 - -可能令 Java 程式員更為驚訝的會是,Scala 中函式也是物件。因此,將函式當做引數傳遞、把它們存入變數、從其他函式返回函式都是可能的。能夠像操作變數一樣的操作函式這點是*函數編程*這一非常有趣的程設典範的基石之一。 - -為何把函式當做變數一樣的操作會很有用呢,讓我們考慮一個定時函式,功能是每秒執行一些動作。我們要怎麼將這動作傳給它?最直接的便是將這動作視為函式傳入。應該有不少程式員對這種簡單傳遞函式的行為很熟悉:通常在使用者介面相關的程式上,用以註冊一些當事件發生時被呼叫的回呼函式。 - -在接下來的程式中,定時函式叫做 `oncePerSecond` ,它接受一個回呼函式做參數。該函式的型別被寫作 `() => Unit` ,這個型別便是所有無引數且無返回值函式的型別( `Unit` 這個型別就像是 C/C++ 的 `void` )。此程式的主函式只是呼叫定時函式並帶入回呼函式,回呼函式輸出一句話到終端上。也就是說這個程式會不斷的每秒輸出一次 "time flies like an arrow"。 - - object Timer { - def oncePerSecond(callback: () => Unit) { - while (true) { callback(); Thread sleep 1000 } - } - def timeFlies() { - println("time flies like an arrow...") - } - def main(args: Array[String]) { - oncePerSecond(timeFlies) - } - } - -值得注意的是,這邊輸出時我們使用 Scala 的函式 `println`,而不是 `System.out` 裡的函式。 - -#### 匿名函式 - -這程式還有改進空間。第一點,函式 `timeFlies` 只是為了能夠被傳遞進 `oncePerSecond` 而定義的。賦予一個只被使用一次的函式名字似乎是沒有必要的,最好能夠在傳入 `oncePerSecond` 時構造出這個函式。Scala 可以藉由*匿名函式*來達到這點。利用匿名函式的改進版本程式如下: - - object TimerAnonymous { - def oncePerSecond(callback: () => Unit) { - while (true) { callback(); Thread sleep 1000 } - } - def main(args: Array[String]) { - oncePerSecond(() => - println("time flies like an arrow...")) - } - } - -這例子中的右箭頭 `=>` 告訴我們有一個匿名函式,右箭頭將函式引數跟函式內容分開。這個例子中,在箭頭左邊那組空的括號告訴我們引數列是空的。函式內容則是跟先前的 `timeFlies` 裡一樣。 - -## 類別 - -之前已講過,Scala 是一個物件導向語言,因此它有著類別的概念 (更精確的說,的確有一些物件導向語言沒有類別的概念,但是 Scala 不是這類)。Scala 宣告類別的語法跟 Java 很接近。一個重要的差別是,Scala 的類別可以有參數。這邊用底下複數的定義來展示: - - class Complex(real: Double, imaginary: Double) { - def re() = real - def im() = imaginary - } - -這個複數類別接受兩個參數,分別為實跟虛部。在創造 `Complex` 的實體時,必須傳入這些參數: `new Complex(1.5, 2.3)`。這個類別有兩個函式分別叫做 `re` 跟 `im` 讓我們取得這兩個部分。 - -值得注意的是,這兩個函式的回傳值並沒有被明確給定。編譯器將會自動的推斷,它會查看這些函式的右側並推導出這兩個函式都會回傳型別為 `Double` 的值。 - -編譯器並不一定每次都能夠推斷出型別,而且很不幸的是我們並沒有簡單規則以分辨哪種情況能推斷,哪種情況不能。因為當編譯器無法推斷未明確給定的型別時它會回報錯誤,實務上這通常不是問題。Scala 初學者在遇到那些看起來很簡單就能推導出型別的情況時,應該嘗試著忽略型別宣告並看看編譯器是不是也覺得可以推斷。多嘗試幾次之後程式員應該能夠體會到何時忽略型別、何時該明確指定。 - -### 無引數函式 - -函式 `re`、`im` 有個小問題,為了呼叫函式,我們必須在函式名稱後面加上一對空括號,如這個例子: - - object ComplexNumbers { - def main(args: Array[String]) { - val c = new Complex(1.2, 3.4) - println("imaginary part: " + c.im()) - } - } - -最好能夠在不需要加括號的情況下取得實虛部,這樣便像是在取資料欄。Scala完全可以做到這件事,需要的只是在定義函式的時候*不要定義引數*。這種函式跟零引數函式是不一樣的,不論是定義或是呼叫,它們都沒有括號跟在名字後面。我們的 `Complex` 可以改寫成: - - class Complex(real: Double, imaginary: Double) { - def re = real - def im = imaginary - } - -### 繼承與覆寫 - -Scala 中所有的類別都繼承自一個母類別。像前一節的 `Complex` 這種沒有指定的例子,Scala 會暗中使用 `scala.AnyRef`。 - -Scala 中可以覆寫繼承自母類別的函式。但是為了避免意外覆寫,必須加上 `override` 修飾字來明確表示要覆寫函式。我們以覆寫 `Complex` 類別中來自 `Object` 的 `toString` 作為範例。 - - class Complex(real: Double, imaginary: Double) { - def re = real - def im = imaginary - override def toString() = - "" + re + (if (im < 0) "" else "+") + im + "i" - } - - -## Case Class 跟模式匹配(pattern matching) - -樹是常見的資料結構。如:解譯器跟編譯器內部常見的表示程式方式便是樹;XML文件是樹;還有一些容器是根基於樹,如紅黑樹。 - -接下來我們會藉由一個小型計算機程式來看看 Scala 是如何呈現並操作樹。這個程式的功能將會是足以操作簡單、僅含有整數常數、整數變數跟加法的算術式。`1+2` 跟 `(x+x)+(7+y)` 為兩個例子。 - -我們得先決定這種表示式的表示法。最自然表示法便是樹,其中節點是操作、葉節點是值。 - -Java 中我們會將這個樹用一個抽象母類別表示,然後每種節點跟葉節點分別有各自的實際類別。在函數編程裡會用代數資料類型。Scala 則是提供了介於兩者之間的 *case class*。將它運用在這邊會是如下: - - abstract class Tree - case class Sum(l: Tree, r: Tree) extends Tree - case class Var(n: String) extends Tree - case class Const(v: Int) extends Tree - -`Sum`、`Var`、`Const` 類別定義成 case class 代表著它們跟一般類別有所差別: - -- 在創建類別實體時不需要用 `new` (也就是說我們可以寫 `Const(5)`,而不是 `new Const(5)`)。 -- 對應所有建構式參數,Scala 會自動定義對應的取值函式 (即,對於 `Const` 類別的實體,我們可以直接用 `c.v` 來取得建構式中的 `v` 參數)。 -- `equals` 跟 `hashCode` 會有預設定義。該定義會根據實體的*結構*而不是個別實體的識別來運作。 -- `toString` 會有預設定義。會印出"原始型態" (即,`x+1` 的樹會被印成`Sum(Var(x),Const(1))`)。 -- 這些類別的實體可以藉由*模式匹配*來拆解。 - -現在我們有了算術表示式的資料型別,可以開始定義各種運算。我們將從一個可以在*環境*內對運算式求值的函式起頭。環境的用處是賦值給變數。舉例來說,運算式 `x+1` 在一個將 `x` 賦與 `5` 的環境 (寫作 `{ x -> 5 }` ) 下求值會得到 `6`。 - -因此我們需要一個表示環境的方法。當然我們可以用一些像是雜湊表的關連性資料結構,但是我們也可以直接用函式!環境就只是一個將值對應到 (變數) 名稱的函式。之前提到的環境 `{ x -> 5 }` 在 Scala 中可以簡單的寫作: - - { case "x" => 5 } - -這串符號定義了一個當輸入是字串 `"x"` 時回傳整數 `5`,其他輸入則是用例外表示失敗的函式。 - -開始實作之前,讓我們先給環境型別一個名字。當然,我們可以直接用 `String => Int`,但是給這型別名字可以讓我們簡化程式,而且在未來要改動時較為簡便。在 Scala 我們是這樣表示這件事: - - type Environment = String => Int - -於是型別 `Environment` 便可以當做輸入 `String` 回傳 `Int` 函式的型別之代名。 - -現在我們可以給出求值函式實作。概念上非常簡單:兩個表示式和的值是兩個表示式值的和;變數的值直接從環境取值;常數的值就是常數本身。表示這些在 Scala 裡並不困難: - - def eval(t: Tree, env: Environment): Int = t match { - case Sum(l, r) => eval(l, env) + eval(r, env) - case Var(n) => env(n) - case Const(v) => v - } - -這個求值函式藉由對樹 `t` 做*模式匹配*來求值。上述實作的意思應該從直觀上便很明確: - -1. 首先檢查樹 `t` 是否為 `Sum`,如果是的話將左跟右側子樹綁定到新變數 `l`跟 `r`,然後再對箭頭後方的表示式求值;這一個表示式可以使用(而且這邊也用到)根據箭頭左側模式所綁定的變數,也就是 `l` 跟 `r`, -2. 如果第一個檢查失敗,也就是說樹不是 `Sum`,接下來檢查 `t` 是否為 `Var`,如果是的話將 `Var` 所帶的名稱綁定到變數 `n` 並求值右側表示式, -3. 如果第二個檢查也失敗,表示樹不是 `Sum` 也不是 `Var`,那便檢查是不是 `Const`,如果是的話將 `Const` 所帶的名稱綁定到變數 `v` 並求值右側表示式, -4. 最後,如果全部檢查都失敗,會丟出例外表示匹配失敗;這只會在有更多 `Tree` 的子類別時發生。 - -如上,模式匹配基本上就是嘗試將一個值對一系列模式做匹配,並在一個模式成功匹配時抽取並命名該值的各部分,最後對一些程式碼求值,而這些程式碼通常會利用被命名到的部位。 - -一個經驗豐富的物件導向程式員也許會疑惑為何我們不將 `eval` 定義成 `Tree` 類別跟子類的*函式*。由於 Scala 允許在 case class 中跟一般類別一樣定義函式,事實上我們可以這樣做。要用模式匹配或是函式只是品味的問題,但是這會對擴充性有重要影響。 - -- 當使用函式時,只要定義新的 `Tree` 子類便新增新節點,相當容易。另一方面,增加新操作需要修改所有子類,很麻煩。 -- 當使用模式匹配時情況則反過來:增加新節點需要修改所有對樹做模式匹配的函式將新節點納入考慮;增加新操作則很簡單,定義新函式就好。 - -讓我們定義新操作以更進一步的探討模式匹配:對符號求導數。讀者們可能還記得這個操作的規則: - -1. 和的導數是導數的和 -2. 如果是對變數 `v` 取導數,變數 `v` 的導數是1,不然就是0 -3. 常數的導數是0 - -這些規則幾乎可以從字面上直接翻成 Scala 程式碼: - - def derive(t: Tree, v: String): Tree = t match { - case Sum(l, r) => Sum(derive(l, v), derive(r, v)) - case Var(n) if (v == n) => Const(1) - case _ => Const(0) - } - -這個函式引入兩個關於模式匹配的新觀念。首先,變數的 `case` 運算式有一個*看守*,也就是 `if` 關鍵字之後的表示式。除非表示式求值為真,不然這個看守會讓匹配直接失敗。在這邊是用來確定我們只在取導數變數跟被取導數變數名稱相同時才回傳常數 `1`。第二個新特徵是可以匹配任何值的*萬用字元* `_`。 - -我們還沒有探討完模式匹配的全部功能,不過為了讓這份文件保持簡短,先就此打住。我們還是希望能看到這兩個函式在真正的範例如何作用。因此讓我們寫一個簡單的 `main` 函數,對表示式 `(x+x)+(7+y)` 做一些操作:先在環境 `{ x -> 5, y -> 7 }` 下計算結果,然後在對 `x` 接著對 `y` 取導數。 - - def main(args: Array[String]) { - val exp: Tree = Sum(Sum(Var("x"),Var("x")),Sum(Const(7),Var("y"))) - val env: Environment = { case "x" => 5 case "y" => 7 } - println("Expression: " + exp) - println("Evaluation with x=5, y=7: " + eval(exp, env)) - println("Derivative relative to x:\n " + derive(exp, "x")) - println("Derivative relative to y:\n " + derive(exp, "y")) - } - -執行這程式,得到預期的輸出: - - Expression: Sum(Sum(Var(x),Var(x)),Sum(Const(7),Var(y))) - Evaluation with x=5, y=7: 24 - Derivative relative to x: - Sum(Sum(Const(1),Const(1)),Sum(Const(0),Const(0))) - Derivative relative to y: - Sum(Sum(Const(0),Const(0)),Sum(Const(0),Const(1))) - -研究這輸出我們可以發現,取導數的結果應該在輸出前更進一步化簡。用模式匹配實作一個基本化簡函數是一個很有趣 (但是意外的棘手) 的問題,在這邊留給讀者當練習。 - -## 特質 (Traits) - -除了由母類別繼承行為以外,Scala 類別還可以從一或多個*特質*導入行為。 - -對一個 Java 程式員最簡單去理解特質的方式應該是視它們為帶有實作的介面。在 Scala 裡,當一個類別繼承特質時,它實作了該特質的介面並繼承所有特質帶有的功能。 - -為了理解特質的用處,讓我們看一個經典範例:有序物件。大部分情況下,一個類別所產生出來的物件之間可以互相比較大小是很有用的,如排序它們。在Java裡可比較大小的物件實作 `Comparable` 介面。在Scala中藉由定義等價於 `Comparable` 的特質 `Ord`,我們可以做的比Java稍微好一點。 - -當在比較物件的大小時,有六個有用且不同的謂詞 (predicate):小於、小於等於、等於、不等於、大於等於、大於。但是把六個全部都實作很煩,尤其是當其中有四個可以用剩下兩個表示的時候。也就是說,(舉例來說) 只要有等於跟小於謂詞,我們就可以表示其他四個。在 Scala 中這些觀察可以很漂亮的用下面的特質宣告呈現: - - trait Ord { - def < (that: Any): Boolean - def <=(that: Any): Boolean = (this < that) || (this == that) - def > (that: Any): Boolean = !(this <= that) - def >=(that: Any): Boolean = !(this < that) - } - -這份定義同時創造了一個叫做 `Ord` 的新型別,跟 Java 的 `Comparable` 介面有著同樣定位,且給了一份以第一個抽象謂詞表示剩下三個謂詞的預設實作。因為所有物件預設都有一份等於跟不等於的謂詞,這邊便沒有定義。 - -上面使用了一個 `Any` 型別,在 Scala 中這個型別是所有其他型別的母型別。因為它同時也是基本型別如 `Int`、`Float` 的母型別,可以將其視為更為一般化的 Java `Object` 型別。 - -因此只要定義測試相等性跟小於的謂詞,並且加入 `Ord`,就可以讓一個類別的物件們互相比較大小。讓我們實作一個表示陽曆日期的 `Date` 類別來做為例子。這種日期是由日、月、年組成,我們將用整數來表示這三個資料。因此我們可以定義 `Date` 類別為: - - class Date(y: Int, m: Int, d: Int) extends Ord { - def year = y - def month = m - def day = d - override def toString(): String = year + "-" + month + "-" + day - -這邊要注意的是宣告在類別名稱跟參數之後的 `extends Ord`。這個語法宣告了 `Date` 繼承 `Ord` 特質。 - -然後我們重新定義來自 `Object` 的 `equals` 函式好讓這個類別可以正確的根據每個資料欄來比較日期。因為在 Java 中 `equals` 預設實作是直接比較實際物件本身,並不能在這邊用。於是我們有下面的實作: - - override def equals(that: Any): Boolean = - that.isInstanceOf[Date] && { - val o = that.asInstanceOf[Date] - o.day == day && o.month == month && o.year == year - } - -這個函式使用了預定義函式 `isInstanceOf` 跟 `asInstanceOf`。`isInstanceOf` 對應到 Java 的 `instanceof` 運算子,只在當使用它的物件之型別跟給定型別一樣時傳回真。 `asInstanceOf` 對應到 Java 的轉型運算子,如果物件是給定型別的實體,該物件就會被視為給定型別,不然就會丟出 `ClassCastException` 。 - -最後我們需要定義測試小於的謂詞如下。 - - def <(that: Any): Boolean = { - if (!that.isInstanceOf[Date]) - error("cannot compare " + that + " and a Date") - - val o = that.asInstanceOf[Date] - (year < o.year) || - (year == o.year && (month < o.month || - (month == o.month && day < o.day))) - } - -這邊使用了另外一個預定義函式 `error`,它會丟出帶著給定錯誤訊息的例外。這便完成了 `Date` 類別。這個類別的實體可被視為日期或是可比較物件。而且它們通通都定義了之前所提到的六個比較謂詞: `equals` 跟 `<` 直接出現在類別定義當中,其他則是繼承自 `Ord` 特質。 - -特質在其他場合也有用,不過詳細探討它們的用途並不在本文件目標內。 - -## 泛型 - -在這份教學裡,我們最後要探討的 Scala 特性是泛型。Java 程式員應該相當清楚在 Java 1.5 之前缺乏泛型所導致的問題。 - -泛型指的是能夠將型別也作為程式參數。舉例來說,當程式員在為鏈結串列寫函式庫時,它必須決定串列的元素型別為何。由於這串列是要在許多不同場合使用,不可能決定串列的元素型別為如 `Int` 一類。這樣限制太多。 - -Java 程式員採用所有物件的母類別 `Object`。這個解決辦法並不理想,一方面這並不能用在基礎型別 (`int`、`long`、`float` 之類),再來這表示必須靠程式員手動加入大量的動態轉型。 - -Scala 藉由可定義泛型類別 (跟函式) 來解決這問題。讓我們藉由最簡單的類別容器來檢視這點:參照,它可以是空的或者指向某型別的物件。 - - class Reference[T] { - private var contents: T = _ - def set(value: T) { contents = value } - def get: T = contents - } - -類別 `Reference` 帶有一個型別參數 `T`,這個參數會是容器內元素的型別。此型別被用做 `contents` 變數的型別、 `set` 函式的引數型別、 `get` 函式的回傳型別。 - -上面程式碼使用的 Scala 變數語法應該不需要過多的解釋。值得注意的是賦與該變數的初始值是 `_`,該語法表示預設值。數值型別預設值是0,`Boolean` 型別是 `false`, `Unit` 型別是 `()` ,所有的物件型別是 `null`。 - -為了使用 `Reference` 類型,我們必須指定 `T`,也就是這容器所包容的元素型別。舉例來說,創造並使用該容器來容納整數,我們可以這樣寫: - - object IntegerReference { - def main(args: Array[String]) { - val cell = new Reference[Int] - cell.set(13) - println("Reference contains the half of " + (cell.get * 2)) - } - } - -如例子中所展現,並不需要先將 `get` 函式所回傳的值轉型便能當做整數使用。同時因為被宣告為儲存整數,也不可能存除了整數以外的東西到這一個容器中。 - -## 結語 - -本文件對Scala語言做了快速的概覽並呈現一些基本的例子。對 Scala 有更多興趣的讀者可以閱讀有更多進階範例的 *Scala By Example*,並在需要的時候參閱 *Scala Language Specification* 。